Is it possible to iterate a stream only once and perform 2 or more operations?

给你一囗甜甜゛ 提交于 2019-12-07 17:00:48

问题


Given the code

List<Integer> numbers = Arrays.asList(2, 4, 3);

int sumTotal = numbers.stream().reduce(-3, (x, y) -> x + y + 3);
int multiplyTotal = numbers.stream().reduce(1, (x, y) -> x * y);

Is it possible to perform both operations while iterating the stream only once?

Also, notice each reduce has a different identity: -3 and 1.


回答1:


You can create a custom class, and use a mutable reduction:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(2, 4, 3);

    Result result = numbers.stream().collect(Result::new, Result::consume, Result::combine);
    System.out.println("result = " + result);
}

private static class Result {
    private int sum = 0;
    private int product = 1;

    public void consume(int i) {
        sum += i + 3;
        product *= i;
    }

    public void combine(Result r) {
        // READ note below
        sum += r.sum + 3;
        product *= r.product;
    }

    @Override
    public String toString() {
        return "Result{" +
            "sum=" + sum +
            ", product=" + product +
            '}';
    }
}

But I doubt you will gain much by doing that over simply iterating twice as you're doing.

EDIT:

Note that the combine() method above, invoked when using a parallel stream, will produce results that are inconsistent with a sequential stream. The problem is caused, as Holger mentions in the comments, by the fact that the operation doesn't respect the identity preconditions of a reduction: applying the reduction on any value V with the identity is supposed to produce V, but it produces V + 3 with 0 as identity.

Make sure not to use a parallel stream to compute that result.




回答2:


You can use a pairing collector which I wrote in this answer:

int[] result = numbers.stream().collect(
        pairing(
                Collectors.reducing(0, (x, y) -> x + y + 3),
                Collectors.reducing(1, (x, y) -> x * y),
                (sum, prod) -> new int[] { sum, prod }));

Of course you can combine the results in any other way. Putting them to array is just an example.

This collector is readily available in my StreamEx library (MoreCollectors.pairing). Alternatively you may use jOOL library: it has Tuple.collectors method which works in similar manner.




回答3:


In JS, one solution is to iterate over an object instead of a number. Ex:

  numbers.stream().reduce({sum:0,prod:1}, (memo, cur) ->
       memo.sum = memo.sum + cur + 3;
       memo.prod = memo.prod * cur;
       return memo;
  )

Note that in your example, x is the "memo" (or accumulator) and y is the current value.

The same idea should also work in Java.



来源:https://stackoverflow.com/questions/31480819/is-it-possible-to-iterate-a-stream-only-once-and-perform-2-or-more-operations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!