Functional programming 如何减少/转换对象数组以获得每个属性的总和(理想情况下为lodash)

Functional programming 如何减少/转换对象数组以获得每个属性的总和(理想情况下为lodash),functional-programming,lodash,reduce,Functional Programming,Lodash,Reduce,我有一个营养对象数组,看起来像这样: [ { calories: {total: 0, fat: 0}, vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0}, fats: {total: 0}, minerals: {calcium: 0} }, { calories: {total: 150, fat: 40}, vitamins: {a: {total: 100}, b6: 30,

我有一个营养对象数组,看起来像这样:

[
  {
    calories: {total: 0, fat: 0},
    vitamins: {a: {total: 0, retinol: 0}, b6: 0, c: 0},
    fats: {total: 0},
    minerals: {calcium: 0}
  },
  {
    calories: {total: 150, fat: 40},
    vitamins: {a: {total: 100}, b6: 30, c: 200},
    fats: {total: 3}
  },
  {
    calories: {total: 100, fat: 60},
    vitamins: {a: {total: 120, retinol: 10}, b6: 0, c: 200},
    minerals: {calcium: 20}
  }
]
请注意,我添加了一个0填充对象,因此没有任何属性为null或未定义;并非所有属性都存在于后续对象上;这是一个相当深的物体(即obj维生素a总量)

结果应该是

{
  calories: {total: 250, fat: 100},
  vitamins: {a: {total: 220, retinol: 10}, b6: 30, c: 400},
  fats: {total: 3},
  minerals: {calcium: 20}
}

我知道
lodash
.reduce()
.transform()
函数,但我不确定如何访问
累加器上的属性理想情况下,如何使用函数式编程和lodash实现预期结果。

我可以给您一个从中获得灵感的答案。在不太深入范畴论的情况下,你需要两件事才能使幺半群起作用

  • 表示Monoid类型的空(或中性)的某个值
  • 还有一个函数,它将你的类型的两个幺半群组合在一起

和幺半群

首先,让我们看看
Sum
Monoid。有无数种方法可以在JavaScript中实现这一点,但我们只看一下这一种,这样您就可以大致了解一下

const Sum=x=>({
构造函数:Sum,
值:x,
concat:({value:y})=>Sum(x+y),
折叠:f=>f(x)
})
Sum.empty=Sum(0)
Sum(3).concat(Sum(4)).fold(x=>console.log(x))//7

Sum(3).concat(Sum(4)).concat(Sum(9)).fold(x=>console.log(x))//16
您可以使用
\uu.mergeWith()来完成。我正在使用
.spread()
创建一个
.mergeWith()
,它可以处理数组而不是单个参数

var数据=[{“卡路里”:{“总量”:0,“脂肪”:0},“维生素”:{“a”:{“总量”:0,“视黄醇”:0},“b6”:0,“c”:0},“脂肪”:{“总量”:0},“矿物质”:{“钙”:0},{“卡路里”:{“总量”:150,“脂肪”:40},“维生素”:{“a”:{“总量”:100},“b6”:30,“c”:200},“脂肪”:{“总量”:3},{“总量”:100,{“热量”:100,“脂肪”:60”,“视黄醇”:200},“矿物”:{“钙”:20}}];
var mergeWith=u0.spread(0.mergeWith);
函数deepMerge(objs){
var args=[{}].concat(objs,函数(objValue,srcValue){
如果(uu0.isNumber(objValue)){
返回objValue+srcValue;
}
});
返回合并(args);
}
var结果=深度合并(数据);
console.log(结果);

我更喜欢使用Ramda,以无点的方式,但在Lodash中请求OP,所以我们开始,简单的递归解决方案:

let data=[{卡路里:{总量:0,脂肪:0},维生素:{a:{total:0,视黄醇:0},b6:0,c:0},脂肪:{total:0},矿物质:{钙:0},{卡路里:{total:150,脂肪:40},维生素:{a:{total:100},b6:30,c:200},脂肪:{total:3},卡路里:{total:100,脂肪:60},维生素:{a:120,视黄醇:20,矿物质:}];
const recursor=result=>(值、键)=>{
如果(uu.isNumber(值)){
结果[键]=((结果[键]| |(结果[键]=0))+值);
返回结果[键];
}否则{
返回u.mapValues(值,递归器(结果[键]| |(结果[键]={}));
}
}
const reducer=(结果、值、键)=>\映射值(值、递归器(结果));
让final=u.transform(数据,减速机,{});

虽然已经指定了使用方向,但他的用法在于创建递归调用
mergeWith
的递归函数。该解决方案忽略了一个事实,即
mergeWith
本质上是一个递归合并,其中定制程序仅在返回任何不正确的值时才会转换结果值t
undefined
。此外,另一个问题是
mergeWith
会变异第一个对象参数,这在您希望保留源对象状态的情况下可能并不理想

下面的解决方案使用with
mergeWith
而不是将其包装成一个,您可以根据自己的喜好决定将其切换到
spread
。为了解决源对象变异的问题,我们提供一个空对象作为第一个参数,然后将其与其他源对象连接。最后,我们连接CUtomizer函数,如果
value
是一个数字,则返回参数之和

var result = _.mergeWith.apply(
  null, 
  [{}].concat(source).concat(function(value, src) {
    if(_.isNumber(value)) {
      return value + src;
    }
  })
);
var源=[
{
卡路里:{总量:0,脂肪:0},
维生素:{a:{总量:0,视黄醇:0},b6:0,c:0},
脂肪:{总数:0},
矿物:{钙:0}
},
{
卡路里:{总量:150,脂肪:40},
维生素:{a:{total:100},b6:30,c:200},
脂肪:{总数:3}
},
{
卡路里:{总量:100,脂肪:60},
维生素:{a:{总量:120,视黄醇:10},b6:0,c:200},
矿物:{钙:20}
}
];
var result=\uu0.mergeWith.apply(
无效的
[{}].concat(源).concat(函数(值,src){
如果(uu.isNumber(值)){
返回值+src;
}
})
);
console.log(结果);
body>div{
最小高度:100%;
排名:0;
}

可能使
deepMerge
接受组合函数作为参数?如果({.isNumber(objValue)){…}
变为
否则返回f(objValue,srcValue)
–这将允许
f
控制术语的组合方式,使
deepMerge
更具可重用性,否则这是一个很好的答案–比我使用Lodash所能想象的要好得多。@naomik-好主意。我添加了另一个外部自定义程序选项。谢谢你们两位。我认为自定义程序可能是s的一部分解决方案,但没有从lodash文档中理解它们。Ori的解决方案看起来更可读、更优雅,更容易转换为typescript。@自然可以使用“customizer”这个词“只是一个接受函数的参数的名称。我自己也在努力学习范畴理论,老实说,我真的以为我在