如何一次迭代 Stream 的两个项目?

How can I iterate over two items of a Stream at once?

我有一个向量列表,我想在它们之间使用流绘制线(目标是一条连续的线穿过所有点)。 目前,我的设置看起来像这样:

l.points().stream()
            .map(v -> vectorOfTile((int) v.x, (int) v.y))
            .reduce(l.points().get(0), (v1, v2) -> {
                line(v1.x, v1.y, v2.x, v2.y);
                return v2;
});

不幸的是,这明显误用了 reduce 方法,因为我使用它来迭代每个项目两次,一次针对流中的每个邻居。

有没有办法在流中使用一些二元运算符来实现相同的行为?我如何实现这样的运算符?


正如@Piotr 所指出的,使用通用的 for 循环可能只是可行的方法。我现在解决问题的方法如下:

PVector[] a = l.points().stream().map(v -> vectorOfTile((int) v.x, (int) v.y)).toArray(PVector[]::new);
for (int i = 0; i < a.length - 1; i++) {
    line(a[i].x, a[i].y, a[i + 1].x, a[i + 1].y);
}

一般来说,流范式是建立在一些不太符合在流中分组项目的目的的基本思想之上的。流范式通常假设:

  • 为了 parallelisation/caching;
  • ,流可以在任意点 拆分
  • 归约操作与加法具有相同的基本语义(换句话说,两个顺序项的排序不应该对归约结果产生影响,并且归约操作本身可以任意拆分,整体结果将一样)。

如果您真的想按照您建议的方式处理 Stream 中的项目,那么——无论是好的做法还是其他方式(剧透:它是 'otherwise'...)——你 可以编写如下方法:

public static <T, V> void forEachCombined(Stream<T> sourceStream, BiFunction<T, T, V> combiner, Consumer<V> op) {
    assert(!sourceStream.isParallel());

    T[] pair = (T[]) new Object[2];
    int[] pos = new int[1];

    sourceStream.forEachOrdered(obj -> {
        pair[pos[0]] = obj;
        if (++pos[0] == 2) {
            V combined = combiner.apply(pair[0], pair[1]);
            op.accept(combined);
            pos[0] = 0;
        }
    });
}

请注意,我们必须走一些丑陋的长度才能让一次调用 forEachOrdered() 记住上一次调用的状态,这是有充分理由的:它通常会打破函数式编程范式,在单独的函数之间创建“状态”以这种方式调用。但结果是您可以采用顺序对并按如下方式组合成点:

    Stream<Integer> coordStream = Stream.of(1, 2, 3, 4);

    forEachCombined(coordStream, Point::new, point -> {
        // ... Do something with 'point'
    });

对于调用者,只要将 forEachCombined() 方法隐藏在带有警告 'not to try this at home' 的实用程序 class 中,生成的语法可以说还不错。不过,它确实打破了许多人认为好的设计原则。