javascript 如何聚合对象属性?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23521894/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to aggregate objects properties?
提问by Carlos Cinelli
If I have an object like this (or similar):
如果我有一个这样(或类似)的对象:
sales = {
obs1:{
Sales1:{
Region:"North", Value: 200},
Sales2:{
Region:"South", Value:100}},
obs2:{
Sales1:{
Region:"North", Value: 50},
Sales2:{
Region:"South", Value:20}
}
}
How could I aggregate the sum of the property Value
by Region
? Answers could be in pure JavaScript or a library.
我怎么可能聚集的财产总和Value
的Region
?答案可以是纯 JavaScript 或库。
The end result should be something similar to this:
最终结果应该类似于:
totals = {North: 250, South:120}
回答by mgibsonbr
As others pointed out, there's no built-in JavaScript functions to do that (there are a few high-order functions like map
, but not enough for the task). However, some libraries such as Underscore.jsprovide many utilities to simplify this kind of task.
正如其他人指出的那样,没有内置的 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
Source: this answer at SOpt
资料来源:SOpt 的这个答案
This answer assumes the structureof your data is known - contrary to the other answers, which focus on generalizing the structure. Though the code above can be generalized itself, by removing the hardcoded Region
and Value
and varying the nesting level to something other than two and the aggregation function to something other than sum - as long as the leaves contain both a property you want to group by, and a value you want to aggregate.
这个答案假设您的数据结构是已知的 - 与其他答案相反,后者侧重于概括结构。尽管上面的代码本身可以泛化,通过删除硬编码Region
和Value
并将嵌套级别更改为两个以外的级别,将聚合函数更改为 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();
}
It's called like this:
它是这样叫的:
function add(a,b) { return a + b; }
var totals = aggregate(sales, "Region", "Value", add);
Another example (finds minimum value by region):
另一个示例(按区域查找最小值):
function min(a,b) { return a < b ? a : b; }
var mins = aggregate(sales, "Region", "Value", min, 999999);
回答by Juan Mendes
Here's a function that will sum and group all all the properties in an object (recursively) http://jsfiddle.net/tjX8p/2/This is almost the same as @MattBurland, except that it's fully generalized, that is, you can use any property as what to group-by or sum.
这是一个函数,它将对一个对象中的所有属性进行求和和分组(递归)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;
}
This function will work with the data you posted, or with something like
此功能将与您发布的数据或类似的数据一起使用
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}
I've stayed away from error checking to keep to code clear.
我一直远离错误检查以保持代码清晰。
回答by Matt Burland
Are you looking for something like this (updated based on @Barmar's suggestion):
您是否正在寻找这样的东西(根据@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);
What this will do, for a given root object, is walk down it's properties and try and find something with a Region
and a Value
property. If it finds them, it populates an object with your totals.
对于给定的根对象,这将做的是遍历它的属性并尝试找到具有 aRegion
和 aValue
属性的东西。如果找到它们,它会用您的总数填充一个对象。
With this approach, you don't need to know anything about the nesting of objects. The only thing you need to know is that the objects you are looking for have Region
and Value
properties.
使用这种方法,您无需了解有关对象嵌套的任何信息。您唯一需要知道的是,您要查找的对象具有Region
和Value
属性。
This can be optimized further and include some error checking (hasOwnProperty
, undefined
, circular references, etc), but should give you a basic idea.
这可以进一步优化,包括一些错误检查(hasOwnProperty
、、undefined
循环引用等),但应该给你一个基本的想法。
回答by Carlos Cinelli
Searching for querying options in JS and looking at @mgibsonbr answer, it seems that another good solution for problems like this would be using something like jFunkto query (even though jFunk is still a prototype) and Underscoreto group and reduce.
搜索在JS查询选项,看着@mgibsonbr答案,似乎对于这样的问题,另一个很好的解决办法是使用类似jFunk到查询(即使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();
回答by bartlb
There are a lot of ways to approach this scenario -- most of which have been addressed already. So I decided to go the extra mile here and create something that would both be a viable solution for the OP's question, and vague enough in its definition to be uses with any data object.
有很多方法可以处理这种情况——其中大部分已经解决了。所以我决定在这里加倍努力并创建一些既可以作为 OP 问题的可行解决方案,又在其定义中足够模糊以用于任何数据对象的东西。
So here's what I was able to throw together...
所以这就是我能够拼凑起来的东西......
aggPropByAssoc()or Aggregate Property By Associationis used to gather certain data from an object, based of the data's property
name, by an associated property key/value
identifier. Currently, this function assumes that the associated property resides in the same object level as the property being requested.
aggPropByAssoc()或Aggregate Property By Association用于根据数据的property
名称,通过关联的属性key/value
标识符从对象中收集某些数据。目前,此函数假定关联的属性与所请求的属性位于同一对象级别。
The function does not make assumptions about on which level in the object, that the requested property can be found. As such, the function will recurse through the object until the property (and the associated property) have been found.
该函数不会假设可以在对象中的哪个级别找到所请求的属性。因此,该函数将递归遍历对象,直到找到该属性(和关联的属性)。
Syntax
句法
aggPropByAssoc (obj, ops [, cb])
aggPropByAssoc (obj, ops [, cb])
- objthe object to parse
- opsan object containing...
assocKey
: the key name of the associated propertyassocVal
: astring orarray of assocKey valueproperty
: the property to aggregate
- cba callback function[optional]
Examples
例子
Using the OP's example:
使用 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}
Source
来源
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;
}
回答by J?rn Kalz
This problem can be solved by an aggregation. But in order to use aggregation we need to convert it from an object to an array first. This can be achieved by using Object.values(obj)
. Because the source object has two levels of nesting, we need to apply it twice and flatten the result:
这个问题可以通过聚合来解决。但是为了使用聚合,我们需要先将它从对象转换为数组。这可以通过使用来实现Object.values(obj)
。因为源对象有两层嵌套,所以我们需要应用它两次并将结果展平:
intermediate = Object.values(sales)
.map(x => Object.values(x))
.flat()
This gives us
这给了我们
[
{
"Region": "North",
"Value": 200
},
{
"Region": "South",
"Value": 100
},
{
"Region": "North",
"Value": 50
},
{
"Region": "South",
"Value": 20
}
]
And now we can use aggregation
现在我们可以使用聚合
totals = intermediate.reduce((r,v) => {
r[v.Region] = (r[v.Region] || 0) + v.Value;
return r;
}, {});