How to reduce given list by using Lambda expression .reduce() method

前端 未结 2 582
梦谈多话
梦谈多话 2020-12-18 08:56
List integers = Arrays.asList(1, 2, 3, 5, 6, 8, 9, 10);
integers.stream().filter((integer) -> integer % 2 == 0).collect(Collectors.toList());


        
相关标签:
2条回答
  • 2020-12-18 09:37

    You can use the Stream.reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) method, which takes three parameters:

    • identity: The identity element is both the initial value of the reduction and the default result if there are no elements in the stream. In your case, it will be an empty list.
    • accumulator: The accumulator function takes two parameters: a partial result of the reduction and the next element of the stream (in this example, an integer). It applies a check for modulo 2 and then returns a new partial result.
    • combiner: It's purpose is to combine the internal temporary collector-accumulators of the stream batches that are being processes in parallel.

    For example:

    BinaryOperator<ArrayList<Integer>> combiner = (x, y) -> { x.addAll(y); return x; };
    BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>> accumulator = (x, y) -> {
        if (y % 2 == 0) {
            x.add(y);
        }
        return x;
    };
    List<Integer> list = Stream.of(1, 2, 3, 5, 6, 8, 9, 10).reduce(new ArrayList<Integer>(),
                                                                   accumulator,
                                                                   combiner);
    System.out.println(list);
    

    Note that this solution may not work for parallel Streams. Also, it's way too easier to stick to the .filter() approach, so I strongly advice you to do so.

    0 讨论(0)
  • 2020-12-18 09:54

    Your understanding of reduction is wrong. reduce will apply a function on all elements repeatedly to get one single result.

    You seem to think of reduce like doing

    1, 2, 3, 5, 6, 8, 9, 10
    │  │  │  │  │  │  │  │
    └op┘  └op┘  └op┘  └op┘
      │     │     │     │
        result list
    

    whereas, in fact, it does

    1, 2, 3, 5, 6, 8, 9, 10
    │  │  │  │  │  │  │  │
    └op┘  └op┘  └op┘  └op┘
      │    │      │    │
      └─op─┘      └─op─┘
         │          │
         └────op────┘
               │
       final result value
    

    Though, this is a conceptional view, the exact order of operations is unspecified. A sequential execution will be like (((1 op 2) op 3) op 4)… while a parallel execution will be a mixture of an execution like the tree above and partial sequential execution(s).


    You can abuse reduce to create a result list if you first convert each element into a List and then use a list operation which concatenates each list, however, there are two problems with this:

    • It doesn’t provide the desired “skip each second element (of the original list)” logic; if you look at the tree above, it should become clear, that it is impossible to formulate a correct op function which does that in every possible execution scenario
    • creating temporary lists and concatenating them is very inefficient

    The latter point can be solved by using collect, which is a mutable reduction, hence, allows you to use mutable lists to which you can add items, however, it does not address the first point, including the desired filter would violate the contract and only work in a sequential execution.

    So the solution is to define a filter for all elements in the scope of the source list, followed by a mutable reduction to create the result list using collect, and, big surprise, that’s exactly what you original code does:

    … .filter(integer -> integer % 2 == 0).collect(Collectors.toList());
    
    0 讨论(0)
提交回复
热议问题