Reducing

sum(), max(), and min() are known as special cases of reduction. By reduction, we mean an abstraction based on two main statements:

  • Take an initial value (T)
  • Take a BinaryOperator<T> to combine two elements and produce a new value

Reductions can be accomplished via a terminal operation named reduce(), which follows this abstraction and defines two signatures (the second one doesn't use an initial value):

  • T reduce​(T identity, BinaryOperator<T> accumulator)
  • Optional<T> reduce​(BinaryOperator<T> accumulator)

With that being said, we can rely on the reduce() terminal operation to compute the sum of the elements, as follows (the initial value is 0, and the lambda is (m1, m2) -> m1 + m2)):

int total = melons.stream()
.map(Melon::getWeight)
.reduce(0, (m1, m2) -> m1 + m2);

The following diagram depicts how the reduce() operation works:

So, how does the reduce() operation work?

Let's take a look at the following steps to figure this out:

  1. First, 0 is used as the first parameter of the lambda (m1), and 2,000 is consumed from the stream and used as the second parameter (m2). 0 + 2000 produces 2000, and this becomes the new accumulated value.
  2. Then, the lambda is called again with the accumulated value and the next element of the stream, 1,600, which produces the new accumulated value, 3,600.
  3. Moving forward, the lambda is called again with the accumulated value and the next element, 3,000, which produces 6,600.
  4. If we step forward again, the lambda is called again with the accumulated value and the next element, 2,000, which produces 8,600.
  5. Finally, the lambda is called with 8,600 and the last element of the stream, 1,700, which produces the final value, 10,300.

The maximum and minimum can be calculated as well:

int max = melons.stream()
.map(Melon::getWeight)
.reduce(Integer::max)
.orElse(-1);

int min = melons.stream()
.map(Melon::getWeight)
.reduce(Integer::min)
.orElse(-1);

The advantage of using reduce() is that we can easily change the computation by simply passing another lambda. For example, we can quickly replace the sum with the product, as shown in the following example:

List<Double> numbers = Arrays.asList(1.0d, 5.0d, 8.0d, 10.0d);

double total = numbers.stream()
.reduce(1.0 d, (x1, x2) -> x1 * x2);

Nevertheless, pay attention to cases that can lead to unwanted results. For example, if we want to compute the harmonic mean of the given numbers then there is not an out of the box special case of reduction, and so we can only rely on reduce(), as follows:

List<Double> numbers = Arrays.asList(1.0d, 5.0d, 8.0d, 10.0d);

The harmonic mean formula is as follows:

In our case, n is the size of the list and H is 2.80701. Using a naive reduce() function will look as follows:

double hm = numbers.size() / numbers.stream()
.reduce((x1, x2) -> (1.0d / x1 + 1.0d / x2))
.orElseThrow();

This will produce 3.49809.

This explanation relies on how we have expressed the calculation. In the first step, we calculate 1.0/1.0 + 1.0/5.0 = 1.2. Then, we may expect to do 1.2 + 1.0/1.8, but actually, the calculation is 1.0/1.2 + 1.0/1.8. Obviously, this is not what we want.

We can fix this by using mapToDouble(), as follows:

double hm = numbers.size() / numbers.stream()
.mapToDouble(x -> 1.0d / x)
.reduce((x1, x2) -> (x1 + x2))
.orElseThrow();

This will produce the expected result, that is, 2.80701.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset