How to interleave (merge) two Java 8 Streams?

前端 未结 6 1753
有刺的猬
有刺的猬 2020-12-16 16:43
 Stream a = Stream.of(\"one\", \"three\", \"five\");
 Stream b = Stream.of(\"two\", \"four\", \"six\");

What do I need

相关标签:
6条回答
  • 2020-12-16 17:21

    One solution with 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);
    

    Or simply by the solution in abacus-util provided by me:

     AtomicInteger idx = new AtomicInteger();
     StreamEx.merge(a, b, (s1, s2) -> idx.getAndIncrement() % 2 == 0 ? Nth.FIRST : Nth.SECOND).forEach(Fn.println()); 
    
    0 讨论(0)
  • 2020-12-16 17:23

    As you can see from the question comments, I gave this a go using 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);
        }
    
    0 讨论(0)
  • 2020-12-16 17:29

    This may not be a good answer because
    (1) it collects to map, which you don't want to do I guess and
    (2) it is not completely stateless as it uses AtomicIntegers.

    Still adding it because
    (1) it is readable and
    (2) community can get an idea from this and try to improve it.

    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
    

    Output

    one
    two
    three
    four
    five
    six
    

    @Holger's edit

    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
    
    0 讨论(0)
  • 2020-12-16 17:33

    Using Guava's Streams.zip and Stream.flatMap:

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

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

    Prints:

    one
    two
    three
    four
    five
    six
    
    0 讨论(0)
  • 2020-12-16 17:40

    A much dumber solution than Holger did, but may be it would fit your requirements:

    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();
    }
    
    0 讨论(0)
  • 2020-12-16 17:41

    I’d use something like this:

    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);
    }
    

    It retains the characteristics of the input streams as far as possible, which allows certain optimizations (e.g. for count()and toArray()). Further, it adds the ORDERED even when the input streams might be unordered, to reflect the interleaving.

    When one stream has more elements than the other, the remaining elements will appear at the end.

    0 讨论(0)
提交回复
热议问题