You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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);

代码解释

  1. groupBy(prop('id')):和原来一样,先把每个年份下的values按id分组。
  2. map(reduce(...)):对每个id的分组数组,用reduce累加计算两个值:totalHours(总小时数)和weightedPoints(所有points*hours的总和)。
  3. toPairs:把分组后的对象转换成[id, 统计数据]的数组形式,方便后续处理。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:58:10