2

I am currently working through a Java 8 Lambdas book (quite a popular one) and I am confused about some syntax in the ANSWER for one of the advanced questions.

The question asks the following:

Write an implementation of the Stream function 'map' using only reduce and lambda expressions. You can return a List instead of a Stream.

I'd like to highlight that I am not interested in the "best" answer to this question, I am interested in understanding the syntax of the answer to this question given in the book. The answer is as follows:

public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
    return stream.reduce(new ArrayList<O>(), (acc, x) -> {
        // We are copying data from acc to new list instance. It is very inefficient,
        // but contract of Stream.reduce method requires that accumulator function does
        // not mutate its arguments.
        // Stream.collect method could be used to implement more efficient mutable reduction,
        // but this exercise asks to use reduce method.
        List<O> newAcc = new ArrayList<>(acc);
        newAcc.add(mapper.apply(x));
        return newAcc;
    }, (List<O> left, List<O> right) -> {
        // We are copying left to new list to avoid mutating it. 
        List<O> newLeft = new ArrayList<>(left);
        newLeft.addAll(right);
        return newLeft;
    });
}

I understand what a reduce function does, and thus I understand the instantiation of the initial ArrayList, and the part which follows - creating a new ArrayList, adding the new function to the list to accumulate, and then returning the new ArrayList as the result.

The bit I do not understand is the next part:

, (List<O> left, List<O> right) -> {
        // We are copying left to new list to avoid mutating it. 
        List<O> newLeft = new ArrayList<>(left);
        newLeft.addAll(right);
        return newLeft;
    });

What is this doing? I understand the contents of the lambda i.e. the behaviour. But I don't understand what this lambda is doing in the entire context of the reduce function? Why wasn't the first section enough? Why do we have this extra lambda here and how is it contributing to this map function that we are creating?

So far Java 8 lambdas have been pretty straightforward, I feel as though I understood all the theory in the book so far, but maybe I misunderstood something? I wonder what I am missing here?

Matthew
  • 1,905
  • 3
  • 19
  • 26
Naveed
  • 21
  • 1
  • 2
    It's calling [`reduce()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-U-java.util.function.BiFunction-java.util.function.BinaryOperator-) with a third parameter, which is the combiner. Allows the `reduce` to execute in multiple threads - the combiner property will...well, *combine* the different results. – VLAZ Apr 27 '21 at 19:05
  • Does this answer your question? [Why is a combiner needed for reduce method that converts type in java 8](https://stackoverflow.com/questions/24308146/why-is-a-combiner-needed-for-reduce-method-that-converts-type-in-java-8) – Gautham M Apr 29 '21 at 09:48

1 Answers1

0

This last part is called a combiner and is useful if your Stream is parallel.

It will create multiple intermediate results that will need to be put together in the end. This is exactly what this lambda is doing.

You can this by executing the following piece of code first, which will run a sequential Stream through your function. Notice how I added a System.out.println("Combining...") inside of the combiner.

public static void main(String[] args) {
    Stream<Integer> boxed = IntStream.rangeClosed(1, 10).limit(25).boxed();
    List<String> map = map(boxed, String::valueOf);
    System.out.println(map);
}

public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
    return stream.reduce(new ArrayList<O>(), (acc, x) -> {
        // We are copying data from acc to new list instance. It is very inefficient,
        // but contract of Stream.reduce method requires that accumulator function does
        // not mutate its arguments.
        // Stream.collect method could be used to implement more efficient mutable reduction,
        // but this exercise asks to use reduce method.
        List<O> newAcc = new ArrayList<>(acc);
        newAcc.add(mapper.apply(x));
        return newAcc;
    }, (List<O> left, List<O> right) -> {
        System.out.println("Combining...");
        // We are copying left to new list to avoid mutating it.
        List<O> newLeft = new ArrayList<>(left);
        newLeft.addAll(right);
        return newLeft;
    });
}

Prints

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Now run the following part, where I made the Stream parallel

public static void main(String[] args) {
    Stream<Integer> boxed = IntStream.rangeClosed(1, 10).parallel().limit(25).boxed();
    List<String> map = map(boxed, String::valueOf);
    System.out.println(map);
}

It prints

Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

More info about combiners

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89