var arr = [
{name:'lilei', age:18, eggs:10},
{name:'haozi', age:20, eggs:7},
{name:'lilei', age:18, eggs:5},
];
比如这个我想遍历一下这个数组,然后根据对象属性找到名字和age都相同的,然后把拥有的eggs数量相加,怎么算呢?
var arr = [
{name:'lilei', age:18, eggs:10},
{name:'haozi', age:20, eggs:7},
{name:'lilei', age:18, eggs:5},
];
比如这个我想遍历一下这个数组,然后根据对象属性找到名字和age都相同的,然后把拥有的eggs数量相加,怎么算呢?
最直接的就是用一个两层的对象做记录,第一层是 name
第二层是 age
:
const record = {}
arr.forEach(({ name, age, eggs }) => {
if (!record[name]) {
record[name] = {}
}
const nameRecord = record[name]
if (!nameRecord[age]) {
nameRecord[age] = 0
}
nameRecord[age] += eggs
})
record.lilei['18'] // 15
这其实就是 groupBy
算法,这里直接大白话写的,你可以自己思考一下怎么支持任意 N
层的 groupBy
~
var newArr = arr.reduce(
(prev, curr) => (
!prev.some((item) => item.name === curr.name && item.age === curr.age && (item.eggs += curr.eggs)) &&
prev.push(curr),
prev
),
[]
);
console.log(newArr);
可以分几步实现:
const grouped = Object.values(
arr.groupBy(({name, age}) => JSON.stringify([name, age])),
);
这里用了Stage 3的Array.prototype.groupBy
,可以自己实现,或者使用Polyfill。
const result = grouped.map(
items => items.reduce(
(accumulator, item) => ({
...accumulator,
...item,
eggs: accumulator.eggs + item.eggs,
}),
),
);
现在我们可以将他们合并到一起,并参数化支持多个数值的匹配:
/**
* @template T
* @param {T[]} target
* @param {keyof T} key - The key of value to reduced
* @param {(keyof T)[]} keys - Keys to be grouped by
* @returns {T[]}
*/
function groupAndSumWithKey (target, key, keys) {
return Object
.values(
target.groupBy(
item => JSON.stringify(
keys.map(key => item[key]),
),
),
)
.map(
items => items.reduce(
(accumulator, item) => ({
...accumulator,
...item,
[key]: (accumulator[key] ?? 0) + (item[key] ?? 0),
}),
),
);
}
于是我们实现了自定义累加的key
,并且可以自定义比较的key
,甚至支持累计的key
和比较的key
相同。
接下来,我们再进一扩展,支持自定义比较方法,和自定义迭代方法:
/**
* @template T
* @param {T[]} target
* @param {(element: T, index: number, target: T[]) => unknown} groupCallbackFn
* @param {(previousValue: T, currentValue: T, currentIndex: number, target: T[]) => T} reduceCallBackFn
* @returns {T[]}
*/
function groupAndReduce (target, groupCallbackFn, reduceCallBackFn) {
return Array.from(
target.groupByToMap(groupCallbackFn).values(),
items => items.reduce(reduceCallBackFn),
);
}
/**
* @template T
* @param {T} target
* @param {(keyof T)[]} keys
* @returns {string}
*/
function generateIdByKeys (target, ...keys) {
return JSON.stringify(keys.map(key => target[key]));
}
/**
* @param {typeof arr[0]} item
* @returns {string}
*/
function groupFn (item) {
return generateIdByKeys(item, "name", "age");
}
/**
* @param {typeof arr[0]} accumulator
* @param {typeof arr[0]} item
* @returns {typeof arr[0]}
*/
function reduceFn (accumulator, item) {
return {
...accumulator,
...item,
eggs: accumulator.eggs + item.eggs,
};
}
groupAndReduce(arr, groupFn, reduceFn);
如果不想用Array.prototype.groupBy
或Array.prototype.groupByToMap
也是可以的。
因为我们不需要追溯前项数据,所以可以把先分组再累加的步骤合并成边分组边累加。groupAndReduce
可以改成:
/**
* @template T
* @param {T[]} target
* @param {(element: T, index: number, target: T[]) => unknown} groupCallbackFn
* @param {(previousValue: T, currentValue: T, currentIndex: number, target: T[]) => T} reduceCallBackFn
* @returns {T[]}
*/
function groupAndReduce (target, groupCallbackFn, reduceCallBackFn) {
/** @type {Map<unknown, T>} */
const groupMap = new Map();
for (let i = 0, item = target[i]; item; item = target[i += 1]) {
const id = groupCallbackFn(item, i, target);
if (groupMap.has(id))
groupMap.set(id, reduceCallBackFn(groupMap.get(id), item, i, target));
else
groupMap.set(id, item);
}
return [...groupMap.values()];
}
function sortSum (arr=[]) {
return arr.reduce((pre, cur) => {
let key = `${cur.name}-${cur.age}`;
if (pre[key]!==undefined) {
pre[key] += cur.eggs;
} else {
pre[key] = cur.eggs;
}
return pre;
}, {});
}
返回结果:{lilei-18: 15, haozi-20: 7}
10 回答11.1k 阅读
6 回答3k 阅读
5 回答4.8k 阅读✓ 已解决
4 回答3k 阅读✓ 已解决
2 回答2.6k 阅读✓ 已解决
3 回答5.1k 阅读✓ 已解决
3 回答1.8k 阅读✓ 已解决
本来这种非常基础的代码题我都是直接跳过的,因为我觉得任何一个学了基础的语法的人稍微动动脑子就不可能不会。但闲着也是闲着就答一下吧。
我不会像很多人那样直接甩一个最终答案上来,那样你下次遇到同样的问题该不会还是不会。我们循序渐进一下。
首先是最基本的用双重 for 循环。(为什么我不愿意答也正是因为此,用 groupBy、reduce 之类的“高级”写法写不出来,难道最基本的 for 循环也不会吗?那到底学 JS 学啥了?)
代码不解释。
然后想一想,JS 中有没有内置的方法可以直接帮我们找到数组中满足条件的那一个对象?这样就不用写双重 for 循环去匹配了。
当然有!
Array.prototype.find
、Array.prototype.findIndex
、Array.prototype.filter
都可以。那我们以 find 为例:
代码简洁了不少。但还是得一个个 push 进去。有没有办法根据
arr
一下子就能返回结果、就像Array.prototype.map
那样?自然想到用
Array.prototype.reduce
:【以下是引申问题】
最后,我们还可以继续优化。这里的
reduce
+find
,本质上还是双重循环,只是没让你显式地写出来 for 而已。那么有没有只循环一次的办法呢?这个问题留给你自己思考。