Javascript 如何聚合对象属性?
如果我有这样(或类似)的对象: 如何按Javascript 如何聚合对象属性?,javascript,obj,ops,cb,Javascript,Obj,Ops,Cb,如果我有这样(或类似)的对象: 如何按区域聚合属性值的总和?答案可以在纯JavaScript或库中 最终结果应该与此类似: totals = {North: 250, South:120} 您是否正在寻找类似的产品(根据@Barmar的建议更新): 对于给定的根对象,这将做的是遍历它的属性,并尝试查找具有区域和值属性的对象。如果它找到了它们,它会用你的总数填充一个对象 使用这种方法,您不需要了解任何有关对象嵌套的信息。您需要知道的唯一一件事是,您正在查找的对象具有区域和值属性 这可以进一步优
区域
聚合属性值
的总和?答案可以在纯JavaScript或库中
最终结果应该与此类似:
totals = {North: 250, South:120}
您是否正在寻找类似的产品(根据@Barmar的建议更新): 对于给定的根对象,这将做的是遍历它的属性,并尝试查找具有
区域
和值
属性的对象。如果它找到了它们,它会用你的总数填充一个对象
使用这种方法,您不需要了解任何有关对象嵌套的信息。您需要知道的唯一一件事是,您正在查找的对象具有区域
和值
属性
这可以进一步优化,包括一些错误检查(
hasOwnProperty
,未定义的
,循环引用等),但应该会给你一个基本的想法。这里有一个函数,它将对一个对象中的所有属性进行求和和(递归)分组。这几乎与@MattBurland相同,除了它是完全广义的,也就是说,您可以使用任何属性作为分组依据或求和
/**
* @param {object} obj Arbitrarily nested object that must contain the
* given propName and groupPropName at some level
* @param {string} propName The property to be summed up
* @param {string} groupPropName The property to group by
* @param {object} This is used internally for recursion, feel free to pass
* in an object with existing totals, but a default one is provided
* @return {object} An object keyed by by the groupPropName where the value
* is the sum of all properties with the propName for that group
*/
function sumUp(obj, propName, groupPropName, totals) {
var totals = totals || {};
for (var prop in obj) {
if (prop === propName) {
if (!totals[obj[groupPropName]]) {
totals[obj[groupPropName]] = 0
}
totals[obj[groupPropName]] += obj[propName]
} else if (typeof obj[prop] == 'object'){
sumUp(obj[prop], propName, groupPropName, totals);
}
}
return totals;
}
此函数将处理您发布的数据或类似的内容
var sales2 = {
a: {
obs1:{
Sales1:{
Region:"North", Value: 100, OtherValue: 12},
Sales2:{
Region:"South", Value:50, OtherValue: 15}}},
b: {
obs2:{
Sales1:{
Region:"North", Value: 50, OtherValue: 18},
Sales2:{
Region:"South", Value:100, OtherValue: 20}}
}
};
console.log (sumUp(sales2, 'Value', 'Region'));
// Object {North: 150, South: 150}
console.log (sumUp(sales2, 'OtherValue', 'Value'));
// Object {50: 33, 100: 32}
为了保持代码清晰,我没有进行错误检查。正如其他人指出的那样,没有内置的JavaScript函数来做这件事(有一些高阶函数,如,但不足以完成任务)。但是,一些库(如)提供了许多实用程序来简化此类任务
var totals = _
.chain(sales) // Wraps up the object in an "underscore object",
// so methods can be chained
// First: "flatten" the sales
.map(function(v) {
return _
.chain(v)
.map(function(v2) {
return v2;
})
.value();
})
.flatten()
// Second: group the sales by region
.groupBy('Region')
// Third: sum the groups and create the object with the totals
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return m + x.Value;
}, 0)
};
})
.value(); // Unwraps the "underscore object" back to a plain JS object
资料来源:
这个答案假设您的数据的结构是已知的,与其他答案相反,其他答案侧重于概括结构。尽管上面的代码本身可以进行泛化,但只要叶子既包含要分组的属性,又包含要聚合的值,就可以删除硬编码的区域
和值
,并将嵌套级别更改为2以外的值,将聚合函数更改为sum以外的值
function aggregate(object, toGroup, toAggregate, fn, val0) {
function deepFlatten(x) {
if ( x[toGroup] !== undefined ) // Leaf
return x;
return _.chain(x)
.map(function(v) { return deepFlatten(v); })
.flatten()
.value();
}
return _.chain(deepFlatten(object))
.groupBy(toGroup)
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return fn(m, x[toAggregate]);
}, val0 || 0)
};
})
.value();
}
它的名字是这样的:
function add(a,b) { return a + b; }
var totals = aggregate(sales, "Region", "Value", add);
另一个示例(按区域查找最小值):
函数min(a,b){返回a
在JS中搜索查询选项并查看@mgibsonbr answer,似乎解决此类问题的另一个好方法是使用类似查询(尽管jFunk仍然是一个原型)和分组并减少
totals= _.chain(jF("*[Region]", sales).get()).groupBy('Region').map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return m + x.Value;
}, 0)
};
})
.value();
有很多方法可以解决这个问题,其中大部分已经解决了。因此,我决定在这里多走一步,创造一些既能解决OP问题的可行方案,又能在定义上含糊不清地用于任何数据对象的东西 这是我能拼凑出来的 aggPropByAssoc()或按关联聚合属性用于根据数据的
属性
名称,通过关联属性键/值
标识符,从对象中收集特定数据。当前,此函数假定关联的属性与被请求的属性位于同一对象级别
函数不假设在对象的哪个级别上可以找到请求的属性。因此,函数将在对象中递归,直到找到属性(以及相关属性)
语法 aggPropByAssoc(obj,ops[,cb])
- 要分析的对象
- 包含。。。
:关联属性的键名称assocKey
:assocKey值的字符串或数组assocVal
:要聚合的属性属性
- 回调函数[可选]
例子 以OP公司为例:
// I've removed the object definition for brevity.
var sales = { ... };
aggPropByAssoc( /* obj, ops, cb */
sales,
{
assocKey: 'Region',
assocVal: ['North', 'South'],
property: 'Value'
},
function (e) {
// As the function only returns the data found, and does not
// automatically manipulate it, we use the callback function
// to return the sum of each region, as requested.
return {
North: e.North.reduce(function (p, c) { return p + c}, 0),
South: e.South.reduce(function (p, c) { return p + c}, 0)
}
}
)
// var regionSums = aggPropByAssoc( ... );
// returns --> Object {North: 250, South: 120}
来源
这个问题可以通过聚合来解决。但是为了使用聚合,我们需要首先将它从一个对象转换为一个数组。这可以通过使用
Object.values(obj)
实现。因为源对象有两个嵌套级别,所以我们需要应用它两次并展平结果:
intermediate = Object.values(sales)
.map(x => Object.values(x))
.flat()
这给了我们
[
{
"Region": "North",
"Value": 200
},
{
"Region": "South",
"Value": 100
},
{
"Region": "North",
"Value": 50
},
{
"Region": "South",
"Value": 20
}
]
现在我们可以使用聚合
totals = intermediate.reduce((r,v) => {
r[v.Region] = (r[v.Region] || 0) + v.Value;
return r;
}, {});
我认为OP有有效的代码,我认为他们需要一个概括。@JuanMendes:这是一个概括。不,他不是。它会递归,直到找到一个具有
区域
和值
属性的对象。@JuanMendes:我正在对属性名称进行硬编码(我假设OP知道他们在寻找什么),但深度绝对不是硬编码的。将属性名用作参数并不难。@MattBurland您真的需要SalesArray
?找到叶对象时,您可以将其添加到totals
对象中。您是在汇总,但不是分组。@MattBurland谢谢,我已将其修改为添加分组。它与您的几乎相同,但允许您指定group by和sum prop名称,并且它返回SUMMETED对象,而不是在函数外修改变量。
function aggPropByAssoc(obj, ops, cb) {
if (typeof obj !== "object" || typeof ops !== "object") { return; }
if (!(ops.assocKey && ops.assocVal && ops.property)) { return; }
function recurseObj(robj) {
for (var k in robj) {
if (! (robj[k][ops.assocKey] && robj[k][ops.property])) { recurseObj(robj[k]); continue; }
if (robj[k][ops.assocKey] === ops.assocVal) { aggArr.push(robj[k][ops.property]); }
}
}
var assocVObj = ops.assocVal, aggArr = [], aggObj = {};
if (typeof ops.assocVal !== "object" ) {
recurseObj(obj), aggObj = aggArr;
} else {
for (var op in assocVObj) {
ops.assocVal = assocVObj[op];
recurseObj(obj);
aggObj[ops.assocVal] = aggArr, aggArr = [];
}
}
if (typeof cb === "function") { return cb(aggObj); }
return aggObj;
}
intermediate = Object.values(sales)
.map(x => Object.values(x))
.flat()
[
{
"Region": "North",
"Value": 200
},
{
"Region": "South",
"Value": 100
},
{
"Region": "North",
"Value": 50
},
{
"Region": "South",
"Value": 20
}
]
totals = intermediate.reduce((r,v) => {
r[v.Region] = (r[v.Region] || 0) + v.Value;
return r;
}, {});