前言 {#前言}
距离 4 月 12 日博客建立以来已过 3 个月,连一篇文章都没有写,总想着第一篇文章要写的尽善尽美,力求做到干货满满。但是平时 crud 写多了也没什么积累一时也不知道该写些啥,再加上懒病上身,就这么一拖再拖拖拖。现在觉着,得先解决写不写的问题,再解决好不好的问题。先权当给自己记流水账,代码备忘了。
简介 {#简介}
Reduce 指将一系列的值 通过某种操作逐步合并为单个值的过程。这个操作可以是加法、乘法、逻辑运算或者其他任何能够组合两个值的操作。
也就是说你最终想要在集合或数组的一系列值中得到一个值,那么这个操作就可以用 Reduce 来实现。
Java Stream reduce {#java-stream-reduce}
Java Stream 的 reduce 有 3 个重载方法,分别是:
- 一个参数的 reduce
Optional<T> reduce(BinaryOperator<T> accumulator);
参数:BinaryOperator<T> accumulator
继承BiFunction<T,T,T>
,实现其中的T apply(T t1, T t2);
接口即可。其中T
为集合中的类型。
返回值:Optional<T>
,由于集合为空会返回空值所以需要使用Optional
- 两个参数的 reduce
T reduce(T identity, BinaryOperator<T> accumulator);
参数:
BinaryOperator<T> accumulator
与一个参数的 reduce 相同T identity
作用是设置一个初始值(默认值)。当集合为空时,就返回这个默认值,当集合不为空时,该值也会参与计算。其中T
为集合中的类型。
返回值:T
,由于指定了初始值(默认值),不存在返回空值的情况。
- 三个参数的 reduce
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
其中T
为集合中的类型,U
为自己指定的其他类型。
参数:
U identity
作用是设置一个初始值(默认值)。注意:可以指定其它类型的值。BiFunction<U, ? super T, U> accumulator
需要实现其中的U apply(U u, T t);
接口。BinaryOperator<U> combiner
供parallelStream()
并行流使用,将各个线程中计算的结果合并起来。需要注意的是每个线程都会加一次U identity
初始值(默认值)。比方说用parallelStream()
并行流对集合进行求和,初始值设置为 1,开启了 3 个线程进行计算,那么这 3 个线程都会加上一次初始值 1,最终合并出来的结果会比实际结果多 2 。
返回值:U
,即指定的初始值类型
也就是说,三个参数的reduce()
可以返回与集合中的元素不同类型的值,方便我们对复杂对象做计算式和转换。
JavaScript reduce {#javascript-reduce}
- 一个参数的 reduce
reduce(callbackFn)
参数:callbackFn
为数组中每个元素执行的函数。其返回值将作为下一次调用 callbackFn
时的 accumulator
参数。对于最后一次调用,返回值将作为 reduce()
的返回值。该函数被调用时将传入以下参数:
accumulator
:上一次调用callbackFn
的结果。在第一次调用时,如果指定了initialValue
则为指定的值,否则为array[0]
的值。currentValue
:当前元素的值。在第一次调用时,如果指定了initialValue
,则为array[0]
的值,否则为array[1]
。currentIndex
:currentValue
在数组中的索引位置。在第一次调用时,如果指定了initialValue
则为 0,否则为 1。array
:调用了reduce()
的数组本身。
即参数callbackFn
样式应为:
(accumulator, currentValue, currentIndex, array) => {
// 一些操作...
}
返回值:使用"reducer"回调函数遍历整个数组后的结果。
- 两个参数的 reduce
reduce(callbackFn, initialValue)
参数:
callbackFn
:与一个参数的 reduce 相同initialValue
:第一次调用回调时初始化accumulator
的值。如果指定了initialValue
,则callbackFn
从数组中的第一个值作为currentValue
开始执行。如果没有指定initialValue
,则accumulator
初始化为数组中的第一个值,并且callbackFn
从数组中的第二个值作为currentValue
开始执行。在这种情况下,如果数组为空(没有第一个值可以作为accumulator
返回),则会抛出错误。
例子 {#例子}
以下对数组的描述,在 Java 中对应List
类型,在 JavaScript 中对应Array
类型。
求对象数组中值的总和 {#求对象数组中值的总和}
假设待处理的值为:[{ x: 1 }, { x: 2 }, { x: 3 }]
Java {#java}
Integer result = list.stream().reduce(0, (accumulator, currentValue) -> accumulator + currentValue.getX(), Integer::sum);
System.out.println("result = " + result);
/* 输出:
result = 6
*/
JavaScript {#javascript}
const result = arr.reduce((accumulator, currentValue) => accumulator + currentValue.x, 0)
console.log('result =', result)
/* 输出:
result = 6
*/
展平嵌套数组 {#展平嵌套数组}
假设待处理的值为:[[0, 1], [2, 3], [4, 5]]
Java {#java-1}
List<Integer> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
accumulator.addAll(currentValue);
return accumulator;
}, (totalCombine, currentCombine) -> {
totalCombine.addAll(currentCombine);
return totalCombine;
});
System.out.println("result = " + result);
/* 输出:
result = [0, 1, 2, 3, 4, 5]
*/
JavaScript {#javascript-1}
const result = arr.reduce((accumulator, currentValue) => accumulator.concat(currentValue), [])
console.log('result = ', result)
/* 输出:
result = [ 0, 1, 2, 3, 4, 5 ]
*/
统计值的出现次数 {#统计值的出现次数}
假设待处理的值为:["Alice", "Bob", "Tiff", "Bruce", "Alice"]
Java {#java-2}
HashMap<String, Integer> result = list.stream().reduce(new HashMap<>(list.size() * 2), (accumulator, currentValue) -> {
if (accumulator.containsKey(currentValue)) {
accumulator.put(currentValue, accumulator.get(currentValue) + 1);
} else {
accumulator.put(currentValue, 1);
}
return accumulator;
}, (totalCombine, currentCombine) -> {
currentCombine.forEach((key, value) -> {
if (totalCombine.containsKey(key)) {
totalCombine.put(key, totalCombine.get(key) + value);
} else {
totalCombine.put(key, value);
}
});
return totalCombine;
});
System.out.println("result = " + result);
/* 输出:
result = {Bruce=1, Tiff=1, Bob=1, Alice=2}
*/
JavaScript {#javascript-2}
const result = arr.reduce((accumulator, currentValue) => {
const count = accumulator[currentValue] ?? 0
return {
...accumulator,
[currentValue]: count + 1
}
}, {})
console.log('result = ', result)
/* 输出:
result = { Alice: 2, Bob: 1, Tiff: 1, Bruce: 1 }
*/
按属性对对象进行分组 {#按属性对对象进行分组}
假设初始值为:
[
{ name: "Alice", age: 21 },
{ name: "Max", age: 20 },
{ name: "Jane", age: 20 },
]
Java {#java-3}
使用 Java Stream 中的Collectors.groupingBy()
更为简单一些
Map<Integer, List<Item>> result = list.stream().collect(Collectors.groupingBy(Item::getAge));
System.out.println("result = " + result);
/* 输出:
result = {20=[Item(name=Max, age=20), Item(name=Max, age=20)], 21=[Item(name=Alice, age=21)]}
*/
JavaScript {#javascript-3}
/**
* 按属性对对象进行分组
*
* @param {Array} array 待分组的数组
* @param {*} property 要分组的属性
*/
const groupBy = (array, property) => {
return array.reduce((accumulator, currentValue) => {
const key = currentValue[property]
const group = accumulator[key] ?? []
return {
...accumulator,
[key]: [...group, currentValue]
}
}, {})
}
const result = groupBy(arr, 'age')
console.log('result = ', result)
/* 输出:
result = {
'20': [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ],
'21': [ { name: 'Alice', age: 21 } ]
}
*/
连接包含在对象数组中的数组 {#连接包含在对象数组中的数组}
假设初始值为:
[
{
"name":"Anna",
"books":[
"Bible",
"Harry Potter"
],
"age":21
},
{
"name":"Bob",
"books":[
"War and peace",
"Romeo and Juliet"
],
"age":26
},
{
"name":"Alice",
"books":[
"The Lord of the Rings",
"The Shining"
],
"age":18
}
]
连接其中的books
对象字段
Java {#java-4}
List<String> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
accumulator.addAll(currentValue.getBooks());
return accumulator;
}, (totalCombine, currentCombine) -> {
totalCombine.addAll(currentCombine);
return totalCombine;
});
System.out.println("result = " + result);
/* 输出:
result = [Bible, Harry Potter, War and peace, Romeo and Juliet, The Lord of the Rings, The Shining]
*/
JavaScript {#javascript-4}
const result = arr.reduce((accumulator, currentValue) => [...accumulator, ...currentValue.books], [])
console.log('result = ', result)
/* 输出:
result = [
'Bible',
'Harry Potter',
'War and peace',
'Romeo and Juliet',
'The Lord of the Rings',
'The Shining'
]
*/
数组去重 {#数组去重}
假设初始值为:["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"]
Java {#java-5}
使用 Java Stream 中的distinct()
更为简单一些
List<String> result = list.stream().distinct().collect(Collectors.toList());
System.out.println("result = " + result);
/* 输出:
result = [a, b, c, e, d]
*/
JavaScript {#javascript-5}
const result = arr.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
return [...accumulator, currentValue]
}
return accumulator
}, [])
console.log('result = ', result)
/* 输出:
result = [ 'a', 'b', 'c', 'e', 'd' ]
*/
使用 reduce() 来替代 .filter().map() {#使用-reduce-来替代-filtermap}
使用filter()
和map()
会遍历数组两次,但是你可以使用reduce()
只遍历一次并实现相同的效果,从而更高效。
假设初始值为:[-5, 6, 2, 0]
假设目标为:只保留其中大于 0 的元素并乘以 2
Java {#java-6}
List<Integer> result = list.stream().reduce(new ArrayList<>(), (accumulator, currentValue) -> {
if (currentValue > 0) {
accumulator.add(currentValue * 2);
}
return accumulator;
}, (totalCombine, currentCombine) -> {
totalCombine.addAll(currentCombine);
return totalCombine;
});
System.out.println("result = " + result);
/* 输出:
result = [12, 4]
*/
JavaScript {#javascript-6}
const result = arr.reduce((accumulator, currentValue) => {
if (currentValue > 0) {
return [...accumulator, currentValue * 2]
}
return accumulator
}, [])
console.log('result = ', result)
/* 输出:
result = [ 12, 4 ]
*/