Java flatmap Iterator <pair <stream <a>,Stream <b>&gt;&gt;配对<stream <a>,流<b>&gt;

时间:2017-06-24 10:49:17

标签: java java-8 java-stream

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

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

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

我目前最好的实施是

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 warns against doing this kind of thing because it may lead to a StackOverflowError
  2. 从文体上来说,如果可能的话,我宁愿它纯粹是功能性的,而不必遍历迭代器并重新分配流。
  3. 感觉Stream#flatMap应该适合(在使用Guava's Streams.stream(Iterator)将输入Iterator转换为Stream之后,但由于中间的Pair类型,它似乎不起作用。

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

3 个答案:

答案 0 :(得分:10)

番石榴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()));
}

答案 1 :(得分:7)

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

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

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

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

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

答案 2 :(得分:3)

首先,这将是一个更具功能性的&#34;你的代码版本,你说你更喜欢风格:

StackOverflowError

有关可能Stream.concat的警告仍然适用于Iterator

为了避免这种情况并考虑大型数据集的性能和内存使用,我有以下建议(根本没有功能)。您可以创建一对自定义A(适用于BStreams.stream()类型)并使用Guava的iterator来获取一对流。将这些自定义迭代器放在具有一对迭代器堆栈的类中。例如,如果在Stream<A>中的第一对中,Stream<B>的元素少于Stream<A>,那么在iterator.next()耗尽后,请调用B并推送{的迭代器{1}}进入其堆栈。这是具有堆栈对的类(添加构造函数):

class PairStreamIterator<A, B> {
    private final Iterator<Pair<Stream<A>, Stream<B>>> iterator;
    private final Queue<Iterator<A>> stackA = new ArrayDeque<>();
    private final Queue<Iterator<B>> stackB = new ArrayDeque<>();

    Iterator<A> getItA() {
        return new Iterator<A>() {
            @Override public boolean hasNext() {
                if (!stackA.isEmpty() && !stackA.peek().hasNext()) {
                    stackA.remove();
                    return hasNext();
                } else if (!stackA.isEmpty() && stackA.peek().hasNext()) {
                    return true;
                } else if (iterator.hasNext()) {
                    Pair<Stream<A>, Stream<B>> pair = iterator.next();
                    stackA.add(pair.first.iterator());
                    stackB.add(pair.second.iterator());
                    return hasNext();
                }
                return false;
            }

            @Override public A next() {
                return stackA.peek().next();
            }
        };
    }    
    // repeat for Iterator<B>
}

flatten方法:

<A, B> Pair<Stream<A>, Stream<B>> flattenIt(Iterator<Pair<Stream<A>, Stream<B>>> iterator) {
    final PairStreamIterator<A, B> pair = new PairStreamIterator<>(iterator);
    return Pair.of(Streams.stream(pair.getItA()), Streams.stream(pair.getItB()));
}

如果以相同的速率使用flatten的结果对中的2个流,则2个堆栈通常会包含1个或2个迭代器。最糟糕的情况是,如果您计划完全使用结果对中的一个流,然后另一个。在这种情况下,第二个展平流所需的所有迭代器将保留在迭代器堆栈中。我不认为我有任何可怕的方法。由于这些存储在内存中的堆中,因此您仍然无法获得StackOverflowError OutOfMemoryError

可能需要注意的是在hasNext中使用递归。如果您在输入中遇到许多连续的空流,那只会是一个问题。