【问题标题】:Split list into multiple lists with fixed number of elements in java 8在java 8中将列表拆分为具有固定数量元素的多个列表
【发布时间】:2015-03-28 10:42:40
【问题描述】:

我想要一些类似于 scala 分组函数的东西。基本上,一次选择 2 个元素并处理它们。这是相同的参考:

Split list into multiple lists with fixed number of elements

Lambda 确实提供了诸如 groupingBy 和 partitioningBy 之类的功能,但它们似乎都不像 Scala 中的分组函数。任何指针将不胜感激。

【问题讨论】:

    标签: java scala java-8


    【解决方案1】:

    您可以使用Guava 库。

    List<Integer> bigList = ... List<List<Integer>> smallerLists = Lists.partition(bigList, 10);

    【讨论】:

      【解决方案2】:

      这听起来像是一个像Stream API 本身提供的操作一样的低级Stream 操作更好地处理的问题。一个(相对)简单的解决方案可能如下所示:

      public static <T> Stream<List<T>> chunked(Stream<T> s, int chunkSize) {
          if(chunkSize<1) throw new IllegalArgumentException("chunkSize=="+chunkSize);
          if(chunkSize==1) return s.map(Collections::singletonList);
          Spliterator<T> src=s.spliterator();
          long size=src.estimateSize();
          if(size!=Long.MAX_VALUE) size=(size+chunkSize-1)/chunkSize;
          int ch=src.characteristics();
          ch&=Spliterator.SIZED|Spliterator.ORDERED|Spliterator.DISTINCT|Spliterator.IMMUTABLE;
          ch|=Spliterator.NONNULL;
          return StreamSupport.stream(new Spliterators.AbstractSpliterator<List<T>>(size, ch)
          {
              private List<T> current;
              @Override
              public boolean tryAdvance(Consumer<? super List<T>> action) {
                  if(current==null) current=new ArrayList<>(chunkSize);
                  while(current.size()<chunkSize && src.tryAdvance(current::add));
                  if(!current.isEmpty()) {
                      action.accept(current);
                      current=null;
                      return true;
                  }
                  return false;
              }
          }, s.isParallel());
      }
      

      简单测试:

      chunked(Stream.of(1, 2, 3, 4, 5, 6, 7), 3)
        .parallel().forEachOrdered(System.out::println);
      

      优点是你不需要所有项目的完整集合来进行后续流处理,例如

      chunked(
          IntStream.range(0, 1000).mapToObj(i -> {
              System.out.println("processing item "+i);
              return i;
          }), 2).anyMatch(list->list.toString().equals("[6, 7]")));
      

      将打印:

      processing item 0
      processing item 1
      processing item 2
      processing item 3
      processing item 4
      processing item 5
      processing item 6
      processing item 7
      true
      

      而不是处理一千个IntStream.range(0, 1000)。这也可以使用无限源Streams:

      chunked(Stream.iterate(0, i->i+1), 2).anyMatch(list->list.toString().equals("[6, 7]")));
      

      如果您对完全物化的集合感兴趣,而不是应用后续的Stream 操作,您可以简单地使用以下操作:

      List<Integer> list=Arrays.asList(1, 2, 3, 4, 5, 6, 7);
      int listSize=list.size(), chunkSize=2;
      List<List<Integer>> list2=
          IntStream.range(0, (listSize-1)/chunkSize+1)
                   .mapToObj(i->list.subList(i*=chunkSize,
                                             listSize-chunkSize>=i? i+chunkSize: listSize))
                   .collect(Collectors.toList());
      

      【讨论】:

      • 我使用了最后一个 lambda 表达式。看起来很简洁,对我有用!
      • 一旦tryAdvance 返回false,之后它会一直返回,那么为什么需要跨调用缓存列表呢?在正常使用情况下,这意味着 Spliterator 在使用后始终保留在列表中。
      • @Marko Topolnik:老实说,我不记得了。也许我遇到了一个行为不正常的拆分器,也许它是以前的实现尝试或forEachRemaining 方法的产物……但它在使用后没有引用,因为它明确地nulled。
      • @Marko Topolnik:但列表为空且未使用。我会看看是否有这样做的理由,否则我会编辑它......
      • @Lyubomyr Shaydariv:确实,这是一个不同的问题。这个答案的重点是提供一个返回 Stream 的操作,该操作可用于链接更多 Stream 操作,保持惰性。 collect 操作是终端操作,启动实际处理。这样的Collector 应该是可能的,我很确定,这样的解决方案已经存在于 SO 上。
      【解决方案3】:

      您可以创建自己的收集器。像这样的:

      class GroupingCollector<T> implements Collector<T, List<List<T>>, List<List<T>>> {
          private final int elementCountInGroup;
      
          public GroupingCollector(int elementCountInGroup) {
              this.elementCountInGroup = elementCountInGroup;
          }
      
          @Override
          public Supplier<List<List<T>>> supplier() {
              return ArrayList::new;
          }
      
          @Override
          public BiConsumer<List<List<T>>, T> accumulator() {
              return (lists, integer) -> {
                  if (!lists.isEmpty()) {
                      List<T> integers = lists.get(lists.size() - 1);
                      if (integers.size() < elementCountInGroup) {
                          integers.add(integer);
                          return;
                      }
                  }
      
                  List<T> list = new ArrayList<>();
                  list.add(integer);
                  lists.add(list);
              };
          }
      
          @Override
          public BinaryOperator<List<List<T>>> combiner() {
              return (lists, lists2) -> {
                  List<List<T>> r = new ArrayList<>();
                  r.addAll(lists);
                  r.addAll(lists2);
                  return r;
              };
          }
      
          @Override
          public Function<List<List<T>>, List<List<T>>> finisher() {
              return lists -> lists;
          }
      
          @Override
          public Set<Characteristics> characteristics() {
              return Collections.emptySet();
          }
      }
      

      然后你可以像这样使用它:

          List<List<Integer>> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).collect(new GroupingCollector<>(3));
          System.out.println(collect);
      

      将打印:

      [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

      【讨论】:

      • 这个收集器对于并行流将无法正常工作,因此combiner() 实现是无用的(最好抛出UnsupportedOperationException)。在不知道源元素索引的情况下,您无法为此任务执行有效的并行收集。
      • 是的,我知道。作者没有提及并行性
      【解决方案4】:

      递归解决方案也可以将列表转换为列表列表

      int chunkSize = 2;
      
      private <T> List<List<T>> process(List<T> list) {
          if (list.size() > chunkSize) {
              List<T> chunk = list.subList(0, chunkSize);
              List<T> rest = list.subList(chunkSize, list.size());
              List<List<T>> lists = process(rest);
              return concat(chunk, lists);
          } else {
              ArrayList<List<T>> retVal = new ArrayList<>();
              retVal.add(list);
              return retVal;
          }
      }
      
      private <T> List<List<T>> concat(List<T> chunk, List<List<T>> rest) {
          rest.add(0, chunk);
          return rest;
      }
      

      【讨论】:

      • 我还没试过。暂时我已经使用了上述解决方案。无论如何,谢谢你..
      【解决方案5】:

      您可以编写自己的收集器整理器,类似于

      final List<String> strings = Arrays.asList("Hello", "World", "I", "Am", "You");
      final int size = 3;
      
      final List<List<String>> stringLists = strings.stream()
              .collect(Collectors.collectingAndThen(Collectors.toList(), new Function<List<String>, List<List<String>>>() {
                  @Override
                  public List<List<String>> apply(List<String> strings) {
                      final List<List<String>> result = new ArrayList<>();
                      int counter = 0;
                      List<String> stringsToAdd = new ArrayList<>();
      
                      for (final String string : strings) {
                          if (counter == 0) {
                              result.add(stringsToAdd);
                          } else {
                              if (counter == size) {
                                  stringsToAdd = new ArrayList<>();
                                  result.add(stringsToAdd);
                                  counter = 0;
                              }
                          }
      
                          ++counter;
                          stringsToAdd.add(string);
                      }
      
                      return result;
                  }
              }));
      
      System.out.println("stringLists = " + stringLists); // stringLists = [[Hello, World, I], [Am, You]]
      

      【讨论】:

      • 感谢您的回复。我已经在这些方面做了一些事情。只是想知道,这是我们可以使用 lambdas 做的最好的事情吗?我想知道是否有更优雅的方式来做到这一点..
      【解决方案6】:

      带有 java 8 流 api 的简单版本:

      static <T> List<List<T>> partition(List<T> list, Integer partitionSize) {
          int numberOfLists = BigDecimal.valueOf(list.size())
              .divide(BigDecimal.valueOf(partitionSize), 0, CEILING)
              .intValue();
      
          return IntStream.range(0, numberOfLists)
              .mapToObj(it -> list.subList(it * partitionSize, Math.min((it+1) * partitionSize, list.size())))
              .collect(Collectors.toList());
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-19
        • 2022-01-04
        • 1970-01-01
        • 1970-01-01
        • 2021-08-10
        • 1970-01-01
        • 2016-06-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多