如果我有这样的对象(或类似的):
sales = {
obs1:{
Sales1:{
Region:"North", Value: 200},
Sales2:{
Region:"South", Value:100}},
obs2:{
Sales1:{
Region:"North", Value: 50},
Sales2:{
Region:"South", Value:20}
}
}
如何通过Value
汇总属性Region
的总和?答案可以是纯JavaScript或库。
最终结果应与此类似:
totals = {North: 250, South:120}
答案 0 :(得分:6)
正如其他人指出的那样,没有内置的JavaScript函数可以做到这一点(有一些高阶函数,如map
,但不足以完成任务)。但是,某些库(如Underscore.js)提供了许多实用程序来简化此类任务。
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
此答案假设您的数据的结构已知 - 与其他答案相反,后者侧重于概括结构。虽然上面的代码可以自己推广,但是通过删除硬编码的Region
和Value
并将嵌套级别改为除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);
另一个例子(按地区查找最小值):
function min(a,b) { return a < b ? a : b; }
var mins = aggregate(sales, "Region", "Value", min, 999999);
答案 1 :(得分:4)
这里是一个函数,它将对象中的所有属性求和并进行分组(递归)http://jsfiddle.net/tjX8p/2/这与@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}
我远离错误检查以保持代码清晰。
答案 2 :(得分:3)
您是否在寻找类似的内容(根据@ Barmar的建议更新):
var totals = {};
function ExtractSales(obj) {
if (obj.Region && obj.Value) {
if (totals[obj.Region]) {
totals[obj.Region] += obj.Value;
} else {
totals[obj.Region] = obj.Value;
}
} else {
for (var p in obj) {
ExtractSales(obj[p]);
}
}
}
ExtractSales(sales);
console.log(totals);
对于给定的根对象,这将做的是向下走它的属性并尝试找到具有Region
和Value
属性的内容。如果找到它们,它会用总数填充一个对象。
使用这种方法,您无需了解有关对象嵌套的任何信息。您唯一需要知道的是,您要查找的对象具有Region
和Value
属性。
这可以进一步优化,包括一些错误检查(hasOwnProperty
,undefined
,循环引用等),但应该给你一个基本的想法。
答案 3 :(得分:1)
在JS中搜索查询选项并查看@mgibsonbr答案,似乎对此类问题的另一个好解决方案是使用jFunk之类的东西进行查询(即使jFunk仍然是原型)和{{ 3}}分组和减少。
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();
答案 4 :(得分:0)
有很多方法可以解决这个问题 - 其中大部分都已经解决了。所以我决定在这里加倍努力,创造一些既可以解决OP问题的可行解决方案,也可以在其定义中模糊地用于任何数据对象。
所以,这是我能够一起扔的东西......
aggPropByAssoc()或按关联聚合属性用于根据数据的property
名称从对象收集某些数据,通过关联的属性key/value
标识符。目前,此函数假定关联的属性与所请求的属性位于相同的对象级别。
该函数不会假设对象中的哪个级别,可以找到所请求的属性。因此,该函数将通过对象递归,直到找到属性(和相关属性)。
aggPropByAssoc(obj,ops [,cb] )
使用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}
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;
}
答案 5 :(得分:0)
可以通过聚合解决此问题。但是为了使用聚合,我们需要首先将其从对象转换为数组。这可以通过使用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;
}, {});