Java 平面地图迭代器<对<流<A>, 流<B>>> 到配对<流<A>, 流<B>>

2022-09-04 01:44:51

我正在尝试使用以下签名实现方法:

public static <A,B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>>> iterator);

其中,该方法的目标是将每个流类型平展为单个流,并将输出包装成一对。我只有一个迭代器(不是迭代器),我无法更改方法签名,因此我必须在一次迭代中执行平展。

我目前最好的实现是

public static <A,B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>> iterator) {
    Stream<A> aStream = Stream.empty();
    Stream<B> bStream = Stream.empty();
    while(iterator.hasNext()) {
        Pair<Stream<A>, Stream<B>> elm = iterator.next();
        aStream = Stream.concat(aStream, elm.first);
        bStream = Stream.concat(bStream, elm.second);
    }
    return Pair.of(aStream, bStream);
}

但是,虽然这在技术上是正确的,但我对此并不满意,原因有两个:

  1. Stream.concat警告不要做这种事情,因为它可能导致StackOverflowError
  2. 从风格上讲,如果可能的话,我宁愿它是纯粹的功能,而不是必须循环访问迭代器并重新分配整个流。

感觉Stream#flatMap应该适合这里(在使用Guava的Streams.stream(Iterator)将输入迭代器转换为Stream之后),但由于中间的Paper类型,它似乎不起作用。

另一个要求是,任何迭代器/流都可能非常大(例如,输入可能包含从一对非常大的流到许多一个项目流的任何地方),因此理想情况下,解决方案不应包含将结果收集到内存中集合中。


答案 1

好吧,番石榴不是魔法,它实际上只是在内部:Streams.stream

StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);

因此,可能不需要将其链接到您的方法,而您可以直接使用它。

你可以用它来做:Stream.Builder

public static <A, B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>>> iterator) {

    Stream.Builder<Stream<A>> builderA = Stream.builder();
    Stream.Builder<Stream<B>> builderB = Stream.builder();

    iterator.forEachRemaining(pair -> {
        builderA.add(pair.first);
        builderB.add(pair.second);
    });

    return Pair.of(builderA.build().flatMap(Function.identity()), builderB.build().flatMap(Function.identity()));
}

答案 2

避免收集整个(就像你在问题中实际做的那样)是相当困难的,因为你不知道结果流将如何被消耗:一个可以完全消耗,也需要完全消耗迭代器,而另一个根本不消耗,需要跟踪所有生成的对 - 有效地将它们收集在某个地方。Iterator

只有当流或多或少地以“速度”消耗时,您才能从不收集整个迭代器中受益。但是,这种消耗意味着要么使用其中一个结果流的迭代器,要么在并行线程中使用流 - 这将需要额外的同步。

因此,我建议将所有对收集到一个中,然后从该列表中生成新的:ListPair

public static <A,B> Pair<Stream<A>, Stream<B>> flatten(Iterator<Pair<Stream<A>, Stream<B>>> iterator) {
    Iterable<Pair<Stream<A>, Stream<B>>> iterable = () -> iterator;
    final List<Pair<Stream<A>, Stream<B>>> allPairs =
        StreamSupport.stream(iterable.spliterator(), false)
            .collect(Collectors.toList());

    return Pair.of(
            allPairs.stream().flatMap(p -> p.first),
            allPairs.stream().flatMap(p -> p.second)
    );
}

这不会消耗任何原始流,同时保持一个简单的解决方案,避免嵌套流串联。


推荐