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
本质上是一个递归合并,其中定制程序仅在返回任何不正确的值时才会转换结果值tundefined
。此外,另一个问题是mergeWith
会变异第一个对象参数,这在您希望保留源对象状态的情况下可能并不理想
下面的解决方案使用withmergeWith
而不是将其包装成一个,您可以根据自己的喜好决定将其切换到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”这个词“只是一个接受函数的参数的名称。我自己也在努力学习范畴理论,老实说,我真的以为我在