【问题标题】:How to interleave (merge) two Java 8 Streams?如何交错(合并)两个 Java 8 流?
【发布时间】:2019-04-17 20:39:46
【问题描述】:
 Stream<String> a = Stream.of("one", "three", "five");
 Stream<String> b = Stream.of("two", "four", "six");

我需要做什么才能使输出如下?

// one
// two
// three
// four
// five
// six

我查看了concat,但正如 javadoc 解释的那样,它只是一个接一个地附加,它不会交错/穿插。

Stream<String> out = Stream.concat(a, b);
out.forEach(System.out::println);

创建一个惰性连接流,其元素都是 第一个流的元素,然后是 第二个流。

错误地给予

 // one
 // three
 // five
 // two
 // four
 // six

如果我收集它们并进行迭代可以做到,但希望有更多的 Java8-y、Streamy :-)

注意

我不想压缩流

“zip”操作将从每个集合中取出一个元素并将它们组合起来。

zip 操作的结果是这样的:(不需要的)

 // onetwo
 // threefour
 // fivesix

【问题讨论】:

  • zip是用来组合元素的,我不想组合元素,我想保持元素总数不变
  • 为什么 zip 不能保持相同的元素总数?
  • 读取另一个线程,zip 总是采用一个 zipper 函数来组合每个流中的一个元素以创建一个新元素。我只想交错而不是压缩
  • 对于未来来到这里的任何人,这里是 cmets + 重定向答案:gist.github.com/blundell/3f062b8ec55fd1906c68e6ec8d848683
  • 我喜欢 interleave 方法的创建,它基本上包装了 zip 方法以提高可读性等。我已投票决定重新开放,因此您可以在此处发布而不是在外部发布...

标签: java java-8 functional-programming java-stream


【解决方案1】:

使用 Guava 的 Streams.zipStream.flatMap

Stream<String> interleaved = Streams
        .zip(a, b, (x, y) -> Stream.of(x, y))
        .flatMap(Function.identity());

interleaved.forEach(System.out::println);

打印:

one
two
three
four
five
six

【讨论】:

    【解决方案2】:

    我会使用这样的东西:

    public static <T> Stream<T> interleave(Stream<? extends T> a, Stream<? extends T> b) {
        Spliterator<? extends T> spA = a.spliterator(), spB = b.spliterator();
        long s = spA.estimateSize() + spB.estimateSize();
        if(s < 0) s = Long.MAX_VALUE;
        int ch = spA.characteristics() & spB.characteristics()
               & (Spliterator.NONNULL|Spliterator.SIZED);
        ch |= Spliterator.ORDERED;
    
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(s, ch) {
            Spliterator<? extends T> sp1 = spA, sp2 = spB;
    
            @Override
            public boolean tryAdvance(Consumer<? super T> action) {
                Spliterator<? extends T> sp = sp1;
                if(sp.tryAdvance(action)) {
                    sp1 = sp2;
                    sp2 = sp;
                    return true;
                }
                return sp2.tryAdvance(action);
            }
        }, false);
    }
    

    它尽可能地保留输入流的特征,从而允许进行某些优化(例如,对于count()toArray())。此外,即使输入流可能是无序的,它也会添加ORDERED,以反映交错。

    当一个流的元素多于另一个时,剩余的元素将出现在最后。

    【讨论】:

    • Stream&lt;? extends T&gt; a 用于更通用的解决方案不是更好吗?只是问一下,因为我在另一个答案here 中引用了这个。
    • @Naman 当然。必须在整个代码中携带? extends 有点烦人。但这应该是为了一个好的 API。
    【解决方案3】:

    Iterator 的一个解决方案

    final Iterator<String> iterA = a.iterator();
    final Iterator<String> iterB = b.iterator();
    
    final Iterator<String> iter = new Iterator<String>() {
      private final AtomicInteger idx = new AtomicInteger();
      @Override
      public boolean hasNext() { 
        return iterA.hasNext() || iterB.hasNext();
      }
      @Override
      public String next() {
        return idx.getAndIncrement() % 2 == 0 && iterA.hasNext() ? iterA.next() : iterB.next();
      }
    };
    
     // Create target Stream with StreamEx from: https://github.com/amaembo/streamex    
     StreamEx.of(iter).forEach(System.out::println);
    
     // Or Streams from Google Guava
     Streams.stream(iter).forEach(System.out::println);
    

    或者简单地通过我提供的abacus-util中的解决方案:

     AtomicInteger idx = new AtomicInteger();
     StreamEx.merge(a, b, (s1, s2) -> idx.getAndIncrement() % 2 == 0 ? Nth.FIRST : Nth.SECOND).forEach(Fn.println()); 
    

    【讨论】:

      【解决方案4】:

      这可能不是是一个好的答案,因为
      (1) 它收集到地图,我猜你不想这样做
      (2) 它不是完全无状态的,因为它使用 AtomicIntegers。

      仍然添加它,因为
      (1) 可读性强
      (2) 社区可以从中得到一个想法并尝试改进它。

      Stream<String> a = Stream.of("one", "three", "five");
      Stream<String> b = Stream.of("two", "four", "six");
      
      AtomicInteger i = new AtomicInteger(0);
      AtomicInteger j = new AtomicInteger(1);
      
      Stream.of(a.collect(Collectors.toMap(o -> i.addAndGet(2), Function.identity())),
              b.collect(Collectors.toMap(o -> j.addAndGet(2), Function.identity())))
              .flatMap(m -> m.entrySet().stream())
              .sorted(Comparator.comparing(Map.Entry::getKey))
              .forEach(e -> System.out.println(e.getValue())); // or collect
      

      输出

      one
      two
      three
      four
      five
      six
      

      @Holger 的编辑

      Stream.concat(a.map(o -> new AbstractMap.SimpleEntry<>(i.addAndGet(2), o)),
              b.map(o -> new AbstractMap.SimpleEntry<>(j.addAndGet(2), o)))
              .sorted(Map.Entry.comparingByKey())
              .forEach(e -> System.out.println(e.getValue())); // or collect
      

      【讨论】:

      • 你不需要收集到地图中,因为你只对获取条目流感兴趣,所以你可以简单地使用Stream.concat( a.map(o -&gt; new AbstractMap.SimpleEntry&lt;&gt;(i.addAndGet(2),o)), b.map(o -&gt; new AbstractMap.SimpleEntry&lt;&gt;(j.addAndGet(2),o)) )来获取它。然后,您可以链接.sorted(Map.Entry.comparingByKey())。但你是对的,不鼓励这种可变状态。最值得注意的是,它会出现并行执行的问题。
      • @Holger 谢谢,将其添加到答案中。我之前想过,但找不到EntrySet 构造函数,懒得谷歌搜索如何创建EntrySet :(
      • 它不是创建EntrySet,而是创建Entry 实例流。现成的实现确实不容易找到(AbstractMap 中还有一个SimpleImmutableEntry)。从 Java 9 开始,您可以简单地使用 Map.entry(key, value) 来获取不可变的 Entry 实例,但您必须注意它不支持 null 键或值,因此您只能在可以排除 @ 时使用它987654334@.
      【解决方案5】:

      从问题 cmets 中可以看出,我尝试使用 zip:

      Stream<String> a = Stream.of("one", "three", "five");
      Stream<String> b = Stream.of("two", "four", "six");
      
      Stream<String> out = interleave(a, b);
      
      
          public static <T> Stream<T> interleave(Stream<T> streamA, Stream<T> streamB) {
              return zip(streamA, streamB, (o1, o2) -> Stream.of(o1, o2)).flatMap(s -> s);
          }
      
          /**
          * https://stackoverflow.com/questions/17640754/zipping-streams-using-jdk8-with-lambda-java-util-stream-streams-zip
          **/
          private static <A, B, C> Stream<C> zip(Stream<A> streamA, Stream<B> streamB, BiFunction<A, B, C> zipper) {
              final Iterator<A> iteratorA = streamA.iterator();
              final Iterator<B> iteratorB = streamB.iterator();
              final Iterator<C> iteratorC = new Iterator<C>() {
                  @Override
                  public boolean hasNext() {
                      return iteratorA.hasNext() && iteratorB.hasNext();
                  }
      
                  @Override
                  public C next() {
                      return zipper.apply(iteratorA.next(), iteratorB.next());
                  }
              };
              final boolean parallel = streamA.isParallel() || streamB.isParallel();
              return iteratorToFiniteStream(iteratorC, parallel);
          }
      
          private static <T> Stream<T> iteratorToFiniteStream(Iterator<T> iterator, boolean parallel) {
              final Iterable<T> iterable = () -> iterator;
              return StreamSupport.stream(iterable.spliterator(), parallel);
          }
      

      【讨论】:

        【解决方案6】:

        一个比 Holger 更笨的解决方案,但它可能符合您的要求:

        private static <T> Stream<T> interleave(Stream<T> left, Stream<T> right) {
            Spliterator<T> splLeft = left.spliterator();
            Spliterator<T> splRight = right.spliterator();
        
            T[] single = (T[]) new Object[1];
        
            Stream.Builder<T> builder = Stream.builder();
        
            while (splRight.tryAdvance(x -> single[0] = x) && splLeft.tryAdvance(builder)) {
                builder.add(single[0]);
            }
        
            return builder.build();
        }
        

        【讨论】:

        • 这不一致地包括left的所有元素,当它的元素多于right时,但当它的元素多于left时,将删除right的元素。你应该决定。要包含所有常见元素,请使用do {} while(splLeft.tryAdvance(builder) &amp;&amp; spRight.tryAdvance(builder));,然后再决定。如果要在流具有不同大小时包含所有元素,请在循环后执行(splLeft.tryAdvance(builder)? splLeft: spRight).forEachRemaining(builder);。好吧,Stream.Builder&lt;T&gt; 确实已经很方便地实现了Consumer&lt;T&gt;
        • @Holger 确实我只想要常见的,但现在问题更糟了,因为do {} while(splLeft.tryAdvance(builder) &amp;&amp; spRight.tryAdvance(builder)); 仍然会从left 中获取一个元素,所以它仍然不正确:(。更大的问题我现在看到我想了——这是不是在作弊,Stream.Builder 仍然使用隐藏的集合来收集元素...
        • 从左边多取一个元素不会与“交错”模式 (ababa) 相矛盾。如果您不想这样,请考虑跟踪计数并将limit 应用于结果流。这比处理附加 存储操作更简单,尤其是通用数组。 builder 意味着一个存储,我想你已经知道了,因为这是实现 spliterator 的主要(唯一)缺点。
        • @Holger 我也想过limit,但不知道,越想越喜欢你的做法
        • @FedericoPeraltaSchaffner 对,这就是关于内存收集的确切点,总的来说,只需使用 Holger 放置的任何东西......
        猜你喜欢
        • 1970-01-01
        • 2019-03-05
        • 2021-06-08
        • 2010-10-20
        • 1970-01-01
        • 2015-10-20
        • 1970-01-01
        • 2017-03-02
        • 1970-01-01
        相关资源
        最近更新 更多