【问题标题】:Collect pairs from a stream从流中收集对
【发布时间】:2018-02-28 10:52:51
【问题描述】:

我有一个这样的对象流:

"0", "1", "2", "3", "4", "5",

如何将其转换为对流:

{ new Pair("0", "1"), new Pair("2", "3"), new Pair("4", "5")}.

流大小未知。我正在从一个可能很大的文件中读取数据。我只有迭代器来收集,我使用拆分器将此迭代器转换为流。 我知道这是使用 StreamEx 处理相邻对的答案: Collect successive pairs from a stream 这可以在 java 或 StreamEx 中完成吗? 谢谢

【问题讨论】:

  • 长度为奇数时会发生什么?对应该是什么?
  • 长度总是偶数。 Pair 是一个简单的 pojo,包含两个对象(字符串)。
  • 所以@niemer 输入流的长度总是均匀的。我的问题是当它很奇怪时会发生什么?您想如何在代码中管理它?
  • 假设奇数个元素没有问题。
  • @niemar Vinay 的评论很重要:使用Stream API(它允许延迟填充和限制大小的操作)有点不符合“流总是有偶数个元素”的概念”。当链流第一个元素时会发生什么?第三 ?等等?

标签: java java-stream streamex


【解决方案1】:

这不是天作之合,但你可以做到

List input = ...
List<Pair> pairs = IntStream.range(0, input.size() / 2)
                            .map(i -> i * 2)
                            .mapToObj(i -> new Pair(input.get(i), input.get(i + 1)))
                            .collect(Collectors.toList());

要在流中创建 Pairs,您需要一个有状态的 lambda,通常应该避免但可以这样做。注意:这仅适用于流是单线程的。即不平行。

Stream<?> stream = 
assert !stream.isParallel();
Object[] last = { null };
List<Pair> pairs = stream.map(a -> {
        if (last[0] == null) {
            last[0] = a;
            return null;
        } else {
            Object t = last[0];
            last[0] = null;
            return new Pair(t, a);
        }
     }).filter(p -> p != null)
       .collect(Collectors.toList());
assert last[0] == null; // to check for an even number input.

【讨论】:

  • 不幸的是,我从文件中读取数据并按顺序处理每个元素。换句话说,我不知道元素的大小。我有可以转换为流的迭代器。
  • 如果列表中的元素数量为奇数,则此代码将失败。
  • @VinayPrajapati 这取决于您期望它对奇数做什么。你可以像我一样忽略它。
  • @PeterLawrey 或我猜围绕给定迭代器的自定义拆分器
  • @PeterLawrey 这就是我的意思 btw stackoverflow.com/a/49029144/1059372
【解决方案2】:

假设有一个Pairleftright 和getter 和一个构造函数:

 static class Paired<T> extends AbstractSpliterator<Pair<T>> {

    private List<T> list = new ArrayList<>(2);

    private final Iterator<T> iter;

    public Paired(Iterator<T> iter) {
        super(Long.MAX_VALUE, 0);
        this.iter = iter;
    }

    @Override
    public boolean tryAdvance(Consumer<? super Pair<T>> consumer) {
        getBothIfPossible(iter);
        if (list.size() == 2) {
            consumer.accept(new Pair<>(list.remove(0), list.remove(0)));
            return true;
        }
        return false;
    }

    private void getBothIfPossible(Iterator<T> iter) {
        while (iter.hasNext() && list.size() < 2) {
            list.add(iter.next());
        }
    }

}

用法是:

 Iterator<Integer> iterator = List.of(1, 2, 3, 4, 5).iterator();
 Paired<Integer> p = new Paired<>(iterator);
 StreamSupport.stream(p, false)
            .forEach(pair -> System.out.println(pair.getLeft() + "  " + pair.getRight()));

【讨论】:

    【解决方案3】:

    如果你不想收集元素

    问题的标题是从流中collect对,所以我假设你想实际收集这些,但你评论说:

    您的解决方案有效,问题在于它将数据从文件加载到 PairList,然后我可以使用该集合中的流来处理对。我不能这样做,因为数据可能太大而无法存储在内存中。

    所以这是一种不收集元素的方法。

    Iterator 转换为 Iterator> 相对简单,然后将流转换为对流。

      /**
       * Returns an iterator over pairs of elements returned by the iterator.
       * 
       * @param iterator the base iterator
       * @return the paired iterator
       */
      public static <T> Iterator<List<T>> paired(Iterator<T> iterator) {
        return new Iterator<List<T>>() {
          @Override
          public boolean hasNext() {
            return iterator.hasNext();
          }
    
          @Override
          public List<T> next() {
            T first = iterator.next();
            if (iterator.hasNext()) {
              return Arrays.asList(first, iterator.next());
            } else {
              return Arrays.asList(first);
            }
          }
        };
      }
    
      /**
       * Returns an stream of pairs of elements from a stream.
       * 
       * @param stream the base stream
       * @return the pair stream
       */
      public static <T> Stream<List<T>> paired(Stream<T> stream) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(paired(stream.iterator()), Spliterator.ORDERED),
            false);
      }
    
      @Test
      public void iteratorAndStreamsExample() {
        List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f");
        Iterator<List<String>> pairs = paired(strings.iterator());
        while (pairs.hasNext()) {
          System.out.println(pairs.next());
          // [a, b]
          // [c, d]
          // [e, f]
        }
    
        paired(Stream.of(1, 2, 3, 4, 5, 6, 7, 8)).forEach(System.out::println);
        // [1, 2]
        // [3, 4]
        // [5, 6]
        // [7, 8]
      }
    

    如果你想收集元素...

    我会通过收集到一个列表中来做到这一点,并使用 AbstractList 以成对的形式提供元素的视图

    首先,PairList。这是一个包含偶数个元素的 any 列表的简单 AbstractList 包装器。 (一旦指定了所需的行为,这可以很容易地适应处理奇数长度的列表。)

      /**
       * A view on a list of its elements as pairs.
       * 
       * @param <T> the element type
       */
      static class PairList<T> extends AbstractList<List<T>> {
        private final List<T> elements;
    
        /**
         * Creates a new pair list.
         * 
         * @param elements the elements
         * 
         * @throws NullPointerException if elements is null
         * @throws IllegalArgumentException if the length of elements is not even
         */
        public PairList(List<T> elements) {
          Objects.requireNonNull(elements, "elements must not be null");
          this.elements = new ArrayList<>(elements);
          if (this.elements.size() % 2 != 0) {
            throw new IllegalArgumentException("number of elements must have even size");
          }
        }
    
        @Override
        public List<T> get(int index) {
          return Arrays.asList(elements.get(index), elements.get(index + 1));
        }
    
        @Override
        public int size() {
          return elements.size() / 2;
        }
      }
    

    然后我们可以定义我们需要的收集器。这本质上是collectingAndThen(toList(), PairList::new) 的简写:

      /**
       * Returns a collector that collects to a pair list.
       * 
       * @return the collector
       */
      public static <E> Collector<E, ?, PairList<E>> toPairList() {
        return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
      }
    

    请注意,对于我们知道支持列表是新生成的用例(如本例中),定义一个不会防御性地复制列表的 PairList 构造函数可能是值得的。不过,这现在并不是很重要。但是一旦我们这样做了,这个方法就是collectingAndThen(toCollection(ArrayList::new), PairList::newNonDefensivelyCopiedPairList)

    现在我们可以使用它了:

      /**
       * Creates a pair list with collectingAndThen, toList(), and PairList::new
       */
      @Test
      public void example() {
        List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
            .collect(toPairList());
        System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
    
        List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
            .collect(toPairList());
        System.out.println(stringPairs); // [[a, b], [b, c]]
      }
    

    这是一个完整的源文件,其中包含一个可运行的示例(作为 JUnit 测试):

    package ex;
    
    import java.util.AbstractList;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collector;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import org.junit.Test;
    
    public class PairCollectors {
    
      /**
       * A view on a list of its elements as pairs.
       * 
       * @param <T> the element type
       */
      static class PairList<T> extends AbstractList<List<T>> {
        private final List<T> elements;
    
        /**
         * Creates a new pair list.
         * 
         * @param elements the elements
         * 
         * @throws NullPointerException if elements is null
         * @throws IllegalArgumentException if the length of elements is not even
         */
        public PairList(List<T> elements) {
          Objects.requireNonNull(elements, "elements must not be null");
          this.elements = new ArrayList<>(elements);
          if (this.elements.size() % 2 != 0) {
            throw new IllegalArgumentException("number of elements must have even size");
          }
        }
    
        @Override
        public List<T> get(int index) {
          return Arrays.asList(elements.get(index), elements.get(index + 1));
        }
    
        @Override
        public int size() {
          return elements.size() / 2;
        }
      }
    
      /**
       * Returns a collector that collects to a pair list.
       * 
       * @return the collector
       */
      public static <E> Collector<E, ?, PairList<E>> toPairList() {
        return Collectors.collectingAndThen(Collectors.toList(), PairList::new);
      }
    
      /**
       * Creates a pair list with collectingAndThen, toList(), and PairList::new
       */
      @Test
      public void example() {
        List<List<Integer>> intPairs = Stream.of(1, 2, 3, 4, 5, 6)
            .collect(toPairList());
        System.out.println(intPairs); // [[1, 2], [2, 3], [3, 4]]
    
        List<List<String>> stringPairs = Stream.of("a", "b", "c", "d")
            .collect(toPairList());
        System.out.println(stringPairs); // [[a, b], [b, c]]
      }    
    }
    

    【讨论】:

    • 注意:OP 希望使用长度未知的Stream 作为输入。
    • @PeterLawrey 是的,这在这种情况下有效。这提供了一个可以处理任意长度的流的收集器。所有这一切都在定义一个PairList 类,它是其元素列表上的一个视图,它们是成对的。一旦我们有了它,我们真正需要的是collectingAndThen(toList(), PairList::new)
    • 您的解决方案有效,问题在于它将数据从文件加载到 PairList,然后我可以使用该集合中的流来处理对。我不能这样做,因为数据可能太大而无法存储在内存中。
    • @niemar 问题的标题是“collect pairs from a stream”。您是否不想真正收集它们,而只是将Stream&lt;T&gt; 变成Stream&lt;Pair&lt;T&gt;&gt;? (将Iterator&lt;T&gt; 转换为Iterator&lt;Pair&lt;T&gt;&gt;,然后将 that 转换为流也可能更容易。
    • @niemar 我添加了一种方法,无需将内容收集到内存即可获取对。它将 Iterator 转换为 Iterator>,并且可以对流执行相同的操作。这是否更适合您的用例?
    【解决方案4】:

    我知道我迟到了,但所有答案似乎都非常复杂,或者有很多 GC 开销/短期对象(这对于现代 JVM 来说并不是什么大问题),但为什么不呢?就这么简单吗?

    public class PairCollaterTest extends TestCase {
        static class PairCollater<T> implements Function<T, Stream<Pair<T, T>>> {
            T prev;
    
            @Override
            public Stream<Pair<T, T>> apply(T curr) {
                if (prev == null) {
                    prev = curr;
                    return Stream.empty();
                }
                try {
                    return Stream.of(Pair.of(prev, curr));
                } finally {
                    prev = null;
                }
            }
        }
    
        public void testPairCollater() {
            Stream.of("0", "1", "2", "3", "4", "5").sequential().flatMap(new PairCollater<>()).forEach(System.out::println);
        }
    }
    

    打印:

    (0,1)
    (2,3)
    (4,5)
    

    【讨论】:

      【解决方案5】:

      只需将IntStream.range(1, 101) 替换为您的流(您无需知道流的大小)-

      import java.util.ArrayList;
      import java.util.List;
      import java.util.stream.IntStream;
      
      public class TestClass {
      
          public static void main(String[] args) {
      
              final Pair pair = new Pair();
              final List<Pair> pairList = new ArrayList<>();
      
              IntStream.range(1, 101)
                      .map(i -> {
                          if (pair.a == null) {
                              pair.a = i;
                              return 0;
                          } else {
                              pair.b = i;
                              return 1;
                          }
                      })
                      .filter(i -> i == 1)
                      .forEach(i -> {
                          pairList.add(new Pair(pair));
                          pair.reset();
                      });
      
              pairList.stream().forEach(p -> System.out.print(p + " "));
          }
      
          static class Pair {
              public Object a;
              public Object b;
      
              public Pair() {
              }
      
              public Pair(Pair orig) {
                  this.a = orig.a;
                  this.b = orig.b;
              }
      
              void reset() {
                  a = null;
                  b = null;
              }
      
              @Override
              public String toString() {
                  return "{" + a + "," + b + '}';
              }
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多