如何交错(合并)两个 Java 8 Stream?

问题描述 投票:0回答:8
 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
java java-8 functional-programming java-stream
8个回答
15
投票

我会用这样的东西:

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
,以反映交错。

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


2
投票

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

2
投票

正如您从问题评论中看到的,我使用 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);
    }

1
投票

这可能不是是一个好的答案,因为
(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

1
投票

无需任何外部库(使用jdk11)

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class MergeUtil {

    private static <T> Stream<T> zipped(List<T> lista, List<T> listb) {
        int maxSize = Math.max(lista.size(), listb.size());
        final var listStream = IntStream
                .range(0, maxSize)
                .mapToObj(i -> {
                    List<T> result = new ArrayList<>(2);
                    if (i < lista.size()) result.add(lista.get(i));
                    if (i < listb.size()) result.add(listb.get(i));
                    return result;
                });
        return listStream.flatMap(List::stream);
    }

    public static void main(String[] args) {
        var l1 = List.of(1, 2, 3);
        var l2 = List.of(4, 5, 6, 7, 8, 9);
        final var zip = zipped(l1, l2);
        System.out.println(zip.collect(Collectors.toList()));
    }

}

listStream 是一个扁平化的

Stream<List<A>>

结果是:

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


1
投票

具有

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-common中的解决方案:

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

0
投票

使用 Guava 的 Streams.zip

Stream.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

0
投票

您不需要任何锤子。对于第一个流的每个元素,使用该元素和第二个流的元素(使用迭代器提取)构造一个流,然后使用 flatMap:

Stream<String> a = Stream.of("one", "three", "five");
Stream<String> b = Stream.of("two", "four", "six");
Iterator<String> bi = b.iterator();
a.flatMap( x -> Stream.of(x, bi.next()) ).forEach(System.out::println);
© www.soinside.com 2019 - 2024. All rights reserved.