问题
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