简体   繁体   中英

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

I have a list of Vectors, between which I want to draw lines using a stream (with the goal being one continuous line going through all points). Currently, my setup looks something like this:

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

This unfortunately presents an obvious misuse of the reduce method, as I instead use it as way to iterate over each item twice, once for each neigbour in the stream.

Is there any way to achieve the same behaviour using some Binary Operator in the stream? How could I emplement such Operator?


As pointed out by @Piotr, using a generic for loop may just be the way to go. My solution to the problem is now as follows:

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

Generally, the stream paradigm is built on a few fundamental ideas that don't quite fit the purpose of grouping items in the stream. The stream paradigm typically assumes that:

  • the stream can be split at arbitrary points for the purpose of parallelisation/caching;
  • reduction operations have the same basic semantics as addition (in other words, the ordering of two sequential items should not have an effect on the result of reduction, and the reduction operation itself can be split arbitrarily and the overall result will be the same).

If you really wanted to process items from a Stream in the way you suggest, then-- be it good practice or otherwise (spoiler: it's 'otherwise'...)-- you could write a method such as the following:

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

Notice that we have to go to some ugly lengths to allow one call into forEachOrdered() to remember state from the previous call, and for good reason: it generally breaks the functional programming paradigm to create "state" between separate calls in this way. But the result is that you could take sequential pairs and combine into points as follows:

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

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

To the caller, so long as the forEachCombined() method is tucked away in a utility class with a warning 'not to try this at home', the resulting syntax arguably isn't too bad. It does break what many would see as good design principles, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM