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
      :关联属性的键名称
    • assocVal
      :assocKey值的字符串或数组
    • 属性
      :要聚合的属性
  • 回调函数[可选]

例子 以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;
}, {});