如何使用Lambda表达式的.reduce()方法来减少给定列表。

5
List<Integer> integers = Arrays.asList(1, 2, 3, 5, 6, 8, 9, 10);
integers.stream().filter((integer) -> integer % 2 == 0).collect(Collectors.toList());

如上所示,integers 是一个列表,我们需要过滤出其中的偶数。我们可以使用 .filter() 方法来实现这一点。但是,是否有可能使用 .reduce() 方法来实现相同的功能呢?希望 .reduce() 方法通过执行给定的二元操作来过滤掉所有其他元素,并返回减少后的列表。
如果我对 .reduce() 方法的理解不正确,请让我知道它到底是做什么的。

1
我不明白。你的要求是什么?你想在“List”中减少所有偶数吗?你可以将“filter”和“reduce”链接起来,因为“filter”是一个中间操作。 - Alexis C.
2
是的,这种做法确实有可能实现,但 reduce 不是正确的工具!使用 filter 就像你已经建议的那样。 - isnot2bad
我只想使用reduce()方法从列表中删除所有奇数,并返回偶数列表。 - Paramesh Korrakuti
2
@ParameshKorrakuti,你试图使用锤子来代替螺丝刀。不要让事情变得更加困难,也不要有可能出错(例如,在将流并行转换时)。只需使用“filter”和“collect”即可... - Alexis C.
1
我可以问一下,您选择使用reduce而不是collect有什么好处吗?还是说这只是一种思维练习? - a better oliver
@AlexisC,通过看到kocko使用reduce()的实现,我觉得仅使用filter()更好。最初,我认为reduce()比filter()更适合从列表中减少元素。 - Paramesh Korrakuti
2个回答

13

你对 reduction 的理解是错误的。 reduce 会重复应用一个函数到所有元素上,以获得一个 唯一的结果

你似乎认为 reduce 就像做...

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

然而,事实上它确实如此。

1, 2, 3, 5, 6, 8, 9, 10
│  │  │  │  │  │  │  │
└op┘  └op┘  └op┘  └op┘
  │    │      │    │
  └─op─┘      └─op─┘
     │          │
     └────op────┘
           │
   final result value
虽然这是一个概念性的观点,操作的确切顺序未指定。顺序执行将类似于(((1 op 2) op 3) op 4)…,而并行执行将是像上面树形图一样的执行和部分顺序执行的混合。
您可以滥用reduce创建结果列表,如果您首先将每个元素转换为List,然后使用连接每个列表的列表操作,但是,这有两个问题:
  • 它不提供所需的“跳过原始列表的每个第二个元素”的逻辑;如果您查看上面的树形图,就会变得清楚,无法制定在每种可能的执行方案中都做到这一点的正确op函数
  • 创建临时列表并将它们连接起来非常低效
后一个问题可通过使用collect解决,它是一种可变缩减,因此允许您使用可变列表,您可以向其中添加项目,但它不解决第一个问题,包括所需的过滤器将违反合同,并且仅在顺序执行中有效。
因此,解决方案是为源列表范围内的所有元素定义一个filter,然后使用collect进行可变缩减以创建结果列表,大惊小怪,这正是您的原始代码所做的事情。
… .filter(integer -> integer % 2 == 0).collect(Collectors.toList());

9
您可以使用Stream.reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)方法,该方法需要三个参数:
  • identity: 身份元素(identity element)是约简的初始值,如果流中没有元素,则它也是默认结果。在您的情况下,它将是一个空列表
  • accumulator: 累加函数取两个参数:约简的部分结果和流的下一个元素(在本例中是一个整数)。它应用模2检查,然后返回新的部分结果。
  • combiner: 它的作用是组合流中正在并行处理的内部临时收集器累加器(batch)。
例如:
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);

请注意,这个解决方案可能不适用于并行流。此外,使用.filter()方法要简单得多,因此我强烈建议你使用它。

1
你正在使用错误的reduce方法,而且Stream.of(integers)将会给你一个只有单个List元素的流。更不用说在并行计算中使用reduce会导致完全混乱...简而言之,我认为在这里使用reduce不是正确的工具。最好还是坚持使用collect(toList())... - Alexis C.
尝试使用 Stream.of(1, 2, 3, 5, 6, 8, 9, 10).parallel().reduce(...) ;-) - Alexis C.
3
尽管这个答案被接受了,但如果流水线并行运行,它将会给出错误的结果或异常。你可能需要明确指出并进行编辑。 - Alexis C.
1
你的用法对于 collect 是正确的,但对于 reduce 不正确。 - Holger
@kocko,感谢您的回复。我已经了解了reduce()的作用。 - Paramesh Korrakuti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接