如何用Ramda为JavaScript聚合后的对象新增sum计算属性
问题:使用Ramda按ID聚合数据并新增加权平均sum属性
我有如下对象数组:
const array = [{ key: '2001', values: [ { id : '123a', points: 3, hours: 3 }, { id : '123a', points: 4, hours: 2 }, { id : '4444', points: 3, hours: 2 }, { id : '4444', points: 3, hours: 5 }, ] }, { key: '2002', values: [ { id : '12d3a', points: 5, hours: 2 }, { id : '12w3a', points: 3, hours: 3 }, ] }]
我目前用这段Ramda代码按id聚合了每个key对应的总hours:
const {map, evolve, pipe, groupBy, prop, pluck, sum, toPairs, zipObj} = R; const aggregateHours = map(evolve({ values: pipe( groupBy(prop('id')), map(pluck('hours')), map(sum), toPairs, map(zipObj(['id', 'hours'])) ) }))
聚合后得到的结果是:
[{ key: '2001', values: [ { id : '123a', hours: 5 }, { id : '4444', hours: 7 } ] }, ...]
现在我需要给每个聚合后的对象新增sum属性,计算公式是:sum = 该id下所有(points*hours)的总和 / 该id的总hours。尝试修改现有Ramda函数没成功,请问怎么实现?可以用Ramda或者其他替代方案吗?
期望的最终结果是:
[{ key: '2001', values: [ { id : '123a', hours: 5, sum: 3.4 }, { id : '4444', hours: 7, sum: 3 } ] }, ...]
解决方案
用Ramda实现的方案
其实核心是修改values处理管道里的逻辑——原来只计算了总hours,现在得同时算出总hours和points*hours的总和,最后再推导sum。
咱们可以调整groupBy之后的处理步骤,不再直接取hours求和,而是对每个id分组后的数组做一次转换,同时计算两个关键值:总hours、总加权分数(也就是所有points*hours的总和),最后再把这些数据组合成目标对象。
完整代码如下:
const { map, evolve, pipe, groupBy, prop, reduce, toPairs } = R; const aggregateWithSum = map(evolve({ values: pipe( groupBy(prop('id')), // 对每个id的分组数据,累加计算总小时数和总加权分数 map(reduce((acc, curr) => ({ totalHours: acc.totalHours + curr.hours, weightedPoints: acc.weightedPoints + curr.points * curr.hours }), { totalHours: 0, weightedPoints: 0 })), // 把键值对转换成目标对象,计算sum toPairs, map(([id, { totalHours, weightedPoints }]) => ({ id, hours: totalHours, sum: weightedPoints / totalHours })) ) })); // 使用示例 const result = aggregateWithSum(array); console.log(result);
代码解释
groupBy(prop('id')):和原来一样,先把每个年份下的values按id分组。map(reduce(...)):对每个id的分组数组,用reduce累加计算两个值:totalHours(总小时数)和weightedPoints(所有points*hours的总和)。toPairs:把分组后的对象转换成[id, 统计数据]的数组形式,方便后续处理。map(([id, stats]) => ...):把每个键值对转换成目标对象,计算sum(加权分数除以总小时数),同时保留id和hours属性。
替代方案:原生JavaScript实现
如果你不想依赖Ramda,用原生JS也能轻松实现,逻辑和Ramda版本完全一致:
const aggregateWithSum = (arr) => arr.map(item => ({ ...item, values: Object.values(item.values.reduce((acc, curr) => { const { id, points, hours } = curr; if (!acc[id]) { acc[id] = { id, hours: 0, weightedPoints: 0 }; } acc[id].hours += hours; acc[id].weightedPoints += points * hours; return acc; }, {})).map(({ id, hours, weightedPoints }) => ({ id, hours, sum: weightedPoints / hours })) })); // 使用示例 const result = aggregateWithSum(array); console.log(result);
这个原生版本先用reduce按id分组并计算统计值,再把分组后的对象转换成数组,最后计算sum属性,逻辑清晰,不需要额外依赖。
内容的提问来源于stack exchange,提问作者Catalin Vasilescu
相关产品推荐
相关产品推荐

