按数组中的多个属性对对象进行分组,然后将它们的值相加

Posted

技术标签:

【中文标题】按数组中的多个属性对对象进行分组,然后将它们的值相加【英文标题】:Group objects by multiple properties in array then sum up their values 【发布时间】:2018-03-29 09:16:31 【问题描述】:

Grouping elements in array by multiple properties 是与我的问题最接近的匹配项,因为它确实通过数组中的多个键对对象进行分组。问题是这个解决方案没有总结属性值然后删除重复项,而是将所有重复项嵌套在一个二维数组中。

预期行为

我有一组对象,必须按shapecolor 分组。

var arr = [
    shape: 'square', color: 'red', used: 1, instances: 1,
    shape: 'square', color: 'red', used: 2, instances: 1,
    shape: 'circle', color: 'blue', used: 0, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 4,
    shape: 'circle', color: 'red', used: 1, instances: 1,
    shape: 'circle', color: 'red', used: 1, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 5,
    shape: 'square', color: 'red', used: 2, instances: 1
];

仅当 shapecolor 相同时,此数组中的对象才被视为重复对象。如果是,我想分别总结它们的usedinstances 值,然后删除重复项。

所以在这个例子中,结果数组可能只包含四种组合:square redsquare bluecircle redcircle blue

问题

我在这里尝试了一种更简单的方法:

var arr = [
    shape: 'square', color: 'red', used: 1, instances: 1,
    shape: 'square', color: 'red', used: 2, instances: 1,
    shape: 'circle', color: 'blue', used: 0, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 4,
    shape: 'circle', color: 'red', used: 1, instances: 1,
    shape: 'circle', color: 'red', used: 1, instances: 0,
    shape: 'square', color: 'red', used: 4, instances: 4,
    shape: 'square', color: 'red', used: 2, instances: 2
];

result = [];

arr.forEach(function (a) 
    if ( !this[a.color] && !this[a.shape] ) 
        this[a.color] =  color: a.color, shape: a.shape, used: 0, instances: 0 ;
        result.push(this[a.color]);
     
    this[a.color].used += a.used;
    this[a.color].instances += a.instances;
, Object.create(null));

console.log(result);

但它输出

[shape: "square", color: "red", used: 11, instances: 9,
shape: "circle", color: "blue", used: 4, instances: 4]

而不是预期的结果:

[shape: "square", color: "red", used: 5, instances: 3,
shape: "circle", color: "red", used: 2, instances: 1,
shape: "square", color: "blue", used: 11, instances: 9,
shape: "circle", color: "blue", used: 0, instances: 0]

如何让我的函数按形状和颜色正确分组对象?即总结它们的值并删除重复项?

【问题讨论】:

您可以使用其他问题中的解决方案,然后在最后遍历数组并将子数组中的usedinstances 相加。 您可以遍历数组并使用字符串“shape|color”作为对象的属性。 优秀的工作完美......实际上我找到了很多方法终于从你的代码中得到了解决方案......非常感谢...... 【参考方案1】:

将Array#reduce 与辅助对象一起使用以对相似对象进行分组。对于每个对象,检查组合的 shapecolor 是否存在于帮助程序中。如果没有,请使用 Object#assign 添加到帮助程序以创建对象的副本,然后推送到数组。如果是,请将其值添加到 usedinstances

var arr = ["shape":"square","color":"red","used":1,"instances":1,"shape":"square","color":"red","used":2,"instances":1,"shape":"circle","color":"blue","used":0,"instances":0,"shape":"square","color":"blue","used":4,"instances":4,"shape":"circle","color":"red","used":1,"instances":1,"shape":"circle","color":"red","used":1,"instances":0,"shape":"square","color":"blue","used":4,"instances":5,"shape":"square","color":"red","used":2,"instances":1];

var helper = ;
var result = arr.reduce(function(r, o) 
  var key = o.shape + '-' + o.color;
  
  if(!helper[key]) 
    helper[key] = Object.assign(, o); // create a copy of o
    r.push(helper[key]);
   else 
    helper[key].used += o.used;
    helper[key].instances += o.instances;
  

  return r;
, []);

console.log(result);

如果可以使用 ES6,则使用 Map 收集值,然后通过 spreading Map#values 将其转换回数组:

const arr = ["shape":"square","color":"red","used":1,"instances":1,"shape":"square","color":"red","used":2,"instances":1,"shape":"circle","color":"blue","used":0,"instances":0,"shape":"square","color":"blue","used":4,"instances":4,"shape":"circle","color":"red","used":1,"instances":1,"shape":"circle","color":"red","used":1,"instances":0,"shape":"square","color":"blue","used":4,"instances":5,"shape":"square","color":"red","used":2,"instances":1];

const result = [...arr.reduce((r, o) => 
  const key = o.shape + '-' + o.color;
  
  const item = r.get(key) || Object.assign(, o, 
    used: 0,
    instances: 0
  );
  
  item.used += o.used;
  item.instances += o.instances;

  return r.set(key, item);
, new Map).values()];

console.log(result);

【讨论】:

为什么在下面的示例中使用地图?与使用普通 作为基础减少值相比,这提供了哪些好处?最后返回Object.values(theObject)。谢谢。 如果我没记错的话,这个答案是在Object.values() 不完全支持(或者我不知道这种支持)时创建的。此外,使用 Map 比对象具有优势 - Map 将始终保留条目的顺序。如果键是整数(此处不是这种情况),则 Map 将保留条目顺序,而对象将按升序排列键。 此外,Map 将支持所有类型的键,而对象仅支持字符串键(非字符串转换为字符串)。【参考方案2】:

使用此方法指定多个属性:

 public static groupBy(array, f) 
       let groups = ;
       array.forEach(function (o) 
         var group = JSON.stringify(f(o));
         groups[group] = groups[group] || [];
         groups[group].push(o);
       );
    return Object.keys(groups).map(function (group) 
      return groups[group];
    )
 

像这样调用这个方法:

var result = Utils.groupBy(arr, function (item) 
            return [item.shape, item.color];
          );

【讨论】:

这非常有效。我必须按 4 个不同的属性进行分组,并且经过一些修改以使其达到 ES6 标准,它可以完美运行! @AlfaBravo 你能否分享 ES6 版本作为单独的答案【参考方案3】:

您可以使用哈希表和键对相同的组进行分组。

var array = [ shape: 'square', color: 'red', used: 1, instances: 1 ,  shape: 'square', color: 'red', used: 2, instances: 1 ,  shape: 'circle', color: 'blue', used: 0, instances: 0 ,  shape: 'square', color: 'blue', used: 4, instances: 4 ,  shape: 'circle', color: 'red', used: 1, instances: 1 ,  shape: 'circle', color: 'red', used: 1, instances: 0 ,  shape: 'square', color: 'blue', used: 4, instances: 5 ,  shape: 'square', color: 'red', used: 2, instances: 1 ],
    hash = Object.create(null),
    grouped = [];
    
array.forEach(function (o) 
    var key = ['shape', 'color'].map(function (k)  return o[k]; ).join('|');
    
    if (!hash[key]) 
        hash[key] =  shape: o.shape, color: o.color, used: 0, instances: 0 ;
        grouped.push(hash[key]);
    
    ['used', 'instances'].forEach(function (k)  hash[key][k] += o[k]; );
);

console.log(grouped);
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

【参考方案4】:

您可以使用reduce() 创建一个具有唯一shape|color 属性的对象,并使用Object.values() 返回这些值的数组。

var arr =["shape":"square","color":"red","used":1,"instances":1,"shape":"square","color":"red","used":2,"instances":1,"shape":"circle","color":"blue","used":0,"instances":0,"shape":"square","color":"blue","used":4,"instances":4,"shape":"circle","color":"red","used":1,"instances":1,"shape":"circle","color":"red","used":1,"instances":0,"shape":"square","color":"blue","used":4,"instances":5,"shape":"square","color":"red","used":2,"instances":1]

var result = Object.values(arr.reduce(function(r, e) 
  var key = e.shape + '|' + e.color;
  if (!r[key]) r[key] = e;
  else 
    r[key].used += e.used;
    r[key].instances += e.instances
  
  return r;
, ))

console.log(result)

【讨论】:

【参考方案5】:

用户要求的 ES6 回答:

// To call this function:
// const result = this.toolBox.multipleGroupByArray(
//    dataArray, (property: IProperty) => [property.prop1, property.prop2, property.prop3]);

multipleGroupByArray(dataArray, groupPropertyArray) 
    const groups = ;
    dataArray.forEach(item => 
        const group = JSON.stringify(groupPropertyArray(item));
        groups[group] = groups[group] || [];
        groups[group].push(item);
    );
    return Object.keys(groups).map(function(group) 
        return groups[group];
    );

【讨论】:

【参考方案6】:

这是一个更通用的分组和求和函数,它接受一个对象数组、一个要分组的键数组和一个要求和的键数组。

function groupAndSum(arr, groupKeys, sumKeys)
  return Object.values(
    arr.reduce((acc,curr)=>
      const group = groupKeys.map(k => curr[k]).join('-');
      acc[group] = acc[group] || Object.fromEntries(
         groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
      sumKeys.forEach(k => acc[group][k] += curr[k]);
      return acc;
    , )
  );

演示:

var arr = [
    shape: 'square', color: 'red', used: 1, instances: 1,
    shape: 'square', color: 'red', used: 2, instances: 1,
    shape: 'circle', color: 'blue', used: 0, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 4,
    shape: 'circle', color: 'red', used: 1, instances: 1,
    shape: 'circle', color: 'red', used: 1, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 5,
    shape: 'square', color: 'red', used: 2, instances: 1
];
function groupAndSum(arr, groupKeys, sumKeys)
  return Object.values(
    arr.reduce((acc,curr)=>
      const group = groupKeys.map(k => curr[k]).join('-');
      acc[group] = acc[group] || Object.fromEntries(groupKeys.map(k => [k, curr[k]]).concat(sumKeys.map(k => [k, 0])));
      sumKeys.forEach(k => acc[group][k] += curr[k]);
      return acc;
    , )
  );

const res = groupAndSum(arr, ['shape', 'color'], ['used', 'instances']);
console.log(res);

【讨论】:

代码按预期工作正常,而且它也是动态的。如果我在数组中的任何值都是NAN(用于sumKeys),如何在代码中处理?【参考方案7】:

如果您希望根据条件字段使用 groupBy 键,那么这里是 @Abbes 答案的修改:

function groupBy(array, f) 
    let groups = ;
    array.forEach((o) => 
        var group = f(o).join('-');
        groups[group] = groups[group] || [];
        groups[group].push(o);
    );
    return groups;

并像这样使用它:

groupBy(connectedServers, (item) => 
            return [item.key1, item.key2];
);

【讨论】:

【参考方案8】:

如果您需要基于颜色或形状属性的“已使用”和“实例”数组;那么你可以使用这个代码。

(PS:我知道这不是您想要的,但将来它可能会对某人有所帮助。也为此目的重用Nenand's 代码。如果代码对您有用,请感谢他)

var array = [ shape: 'square', color: 'red', used: 1, instances: 1 ,  shape: 'square', color: 'red', used: 2, instances: 1 ,  shape: 'circle', color: 'blue', used: 0, instances: 0 ,  shape: 'square', color: 'blue', used: 4, instances: 4 ,  shape: 'circle', color: 'red', used: 1, instances: 1 ,  shape: 'circle', color: 'red', used: 1, instances: 0 ,  shape: 'square', color: 'blue', used: 4, instances: 5 ,  shape: 'square', color: 'red', used: 2, instances: 1 ],


hash = Object.create(null),
grouped = [];

array.forEach(function (o) 
var key = ['shape', 'color'].map(function (k)  return o[k]; ).join('|');

if (!hash[key]) 
hash[key] =  shape: o.shape, color: o.color, YourArrayName : [] ;
grouped.push(hash[key]);

['used'].forEach(function (k)  hash[key]['YourArrayName'].push( used : o['used'], instances : o['instances'] ) );
);

console.log(grouped);

输出会是这样的

【讨论】:

【参考方案9】:

我有一个建议给你。 如果你想让它更容易做,你可以试试 Underscore 库:http://underscorejs.org/

我很快尝试使用它并得到了正确的结果:

var arr = [
    shape: 'square', color: 'red', used: 1, instances: 1,
    shape: 'square', color: 'red', used: 2, instances: 1,
    shape: 'circle', color: 'blue', used: 0, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 4,
    shape: 'circle', color: 'red', used: 1, instances: 1,
    shape: 'circle', color: 'red', used: 1, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 5,
    shape: 'square', color: 'red', used: 2, instances: 1
];

var byshape = _.groupBy(arr, 'shape');


var bycolor = _.map(byshape, function(array) 
                                    return _.groupBy(array, 'color')
                                );


var output = [];
_.each(bycolor, function(arrayOfShape) 
    _.each(arrayOfShape, function(arrayOfColor) 
    var computedItem = shape: "", color: "", used: 0, instances: 0;
    _.each(arrayOfColor, function(item) 
        computedItem.shape = item.shape;
      computedItem.color = item.color;
        computedItem.used += item.used;
      computedItem.instances += item.instances;
    );
    output.push(computedItem);
  );
);
console.log(output);

http://jsfiddle.net/oLyzdoo7/

此解决方案将第一个数据分组,然后您可以做任何您想做的事情,例如,按照您的意愿计算数据。

也许你可以优化它,如果你需要更多帮助,请告诉我

【讨论】:

【参考方案10】:
/**
  * Groups an array of objects with multiple properties.
  *
  * @param  Array  array: the array of objects to group
  * @param  Array props: the properties to groupby
  * @return Array an array of arrays with the grouped results
  */   
const groupBy = ( Group: array, By: props ) => 
    getGroupedItems = (item) => 
        returnArray = [];
        let i;
        for (i = 0; i < props.length; i++) 
            returnArray.push(item[props[i]]);
        
        return returnArray;
    ;

    let groups = ;
    let i;

    for (i = 0; i < array.length; i++) 
        const arrayRecord = array[i];
        const group = JSON.stringify(getGroupedItems(arrayRecord));
        groups[group] = groups[group] || [];
        groups[group].push(arrayRecord);
    
    return Object.keys(groups).map((group) => 
        return groups[group];
    );
;

示例:

假设我们有一个对象数组。每个对象都包含有关个人和所拥有金钱的信息。我们想为所有具有相同国籍和相同性别的人汇总这笔钱。

const data = [
Name: 'George', Surname: 'Best', Country: 'Great Britain', Gender: 'Male', Money:8000, 
Name: 'Orion', Surname: 'Papathanasiou', Country: 'Greece', Gender: 'Male', Money: 2000, 
Name: 'Mairy', Surname: 'Wellbeck', Country: 'Great Britain', Gender: 'Female', Money:5000, 
Name: 'Thanasis', Surname: 'Papathanasiou', Country: 'Greece',Gender: 'Male', Money: 3200,
Name: 'George', Surname: 'Washington', Country: 'Great Britain', Gender: 'Male',Money:4200, 
Name: 'Orfeas', Surname: 'Kalaitzis', Country: 'Greece', Gender: 'Male', Money: 7643, 
Name: 'Nick', Surname: 'Wellington', Country: 'USA', Gender: 'Male', Money:1000, 
Name: 'Kostas', Surname: 'Antoniou', Country: 'Greece', Gender: 'Male', Money: 8712,
Name: 'John', Surname: 'Oneal', Country: 'USA', Gender: 'Male', Money:98234, 
Name: 'Paulos', Surname: 'Stamou', Country: 'Greece',  Gender: 'Male', Money: 3422, 
Name: 'Soula', Surname: 'Spuropoulou', Country: 'Greece', Gender: 'Female', Money:400, 
Name: 'Paul', Surname: 'Pierce', Country: 'USA',  Gender: 'Male',Money: 13000,
Name: 'Helen', Surname: 'Smith', Country: 'Great Britain', Gender: 'Female', Money:1000, 
Name: 'Cathrine', Surname: 'Bryant', Country: 'Great Britain', Gender: 'Female', Money: 8712,
Name: 'Jenny', Surname: 'Scalabrini', Country: 'USA', Gender: 'Female', Money:92214];

const groupByProperties = ['Country', 'Gender'];

调用函数:

const groupResult =  groupBy( Group: data, By: groupByProperties );

分组结果为:

  (6) [Array(2), Array(5), Array(3), Array(3), Array(1), Array(1)]
0: Array(2)
0: Name: "George", Surname: "Best", Country: "Great Britain", Gender: "Male", Money: 8000
1: Name: "George", Surname: "Washington", Country: "Great Britain", Gender: "Male", Money: 4200
length: 2
__proto__: Array(0)
1: Array(5)
0: Name: "Orion", Surname: "Papathanasiou", Country: "Greece", Gender: "Male", Money: 2000
1: Name: "Thanasis", Surname: "Papathanasiou", Country: "Greece", Gender: "Male", Money: 3200
2: Name: "Orfeas", Surname: "Kalaitzis", Country: "Greece", Gender: "Male", Money: 7643
3: Name: "Kostas", Surname: "Antoniou", Country: "Greece", Gender: "Male", Money: 8712
4: Name: "Paulos", Surname: "Stamou", Country: "Greece", Gender: "Male", Money: 3422
length: 5
__proto__: Array(0)
2: Array(3)
0: Name: "Mairy", Surname: "Wellbeck", Country: "Great Britain", Gender: "Female", Money: 5000
1: Name: "Helen", Surname: "Smith", Country: "Great Britain", Gender: "Female", Money: 1000
2: Name: "Cathrine", Surname: "Bryant", Country: "Great Britain", Gender: "Female", Money: 8712
length: 3
__proto__: Array(0)
3: Array(3)
0: Name: "Nick", Surname: "Wellington", Country: "USA", Gender: "Male", Money: 1000
1: Name: "John", Surname: "Oneal", Country: "USA", Gender: "Male", Money: 98234
2: Name: "Paul", Surname: "Pierce", Country: "USA", Gender: "Male", Money: 13000
length: 3
__proto__: Array(0)
4: Array(1)
0: Name: "Soula", Surname: "Spuropoulou", Country: "Greece", Gender: "Female", Money: 400
length: 1
__proto__: Array(0)
5: Array(1)
0: Name: "Jenny", Surname: "Scalabrini", Country: "USA", Gender: "Female", Money: 92214
length: 1
__proto__: Array(0)
length: 6
__proto__: Array(0)

所以,我们得到了 6 个数组。每个数组按CountryGender 分组

遍历每个数组,我们可以总结出钱!

const groupBy = ( Group: array, By: props ) => 
    getGroupedItems = (item) => 
        returnArray = [];
        let i;
        for (i = 0; i < props.length; i++) 
            returnArray.push(item[props[i]]);
        
        return returnArray;
    ;

    let groups = ;
    let i;

    for (i = 0; i < array.length; i++) 
        const arrayRecord = array[i];
        const group = JSON.stringify(getGroupedItems(arrayRecord));
        groups[group] = groups[group] || [];
        groups[group].push(arrayRecord);
    
    return Object.keys(groups).map((group) => 
        return groups[group];
    );
;
	
	
const data = [
	Name: 'George', Surname: 'Best', Country: 'Great Britain', Gender: 'Male', Money:8000, 
	Name: 'Orion', Surname: 'Papathanasiou', Country: 'Greece', Gender: 'Male', Money: 2000, 
    Name: 'Mairy', Surname: 'Wellbeck', Country: 'Great Britain', Gender: 'Female', Money:5000, 
	Name: 'Thanasis', Surname: 'Papathanasiou', Country: 'Greece',Gender: 'Male', Money: 3200,
	Name: 'George', Surname: 'Washington', Country: 'Great Britain', Gender: 'Male',Money:4200, 
	Name: 'Orfeas', Surname: 'Kalaitzis', Country: 'Greece', Gender: 'Male', Money: 7643, 
    Name: 'Nick', Surname: 'Wellington', Country: 'USA', Gender: 'Male', Money:1000, 
	Name: 'Kostas', Surname: 'Antoniou', Country: 'Greece', Gender: 'Male', Money: 8712,
	Name: 'John', Surname: 'Oneal', Country: 'USA', Gender: 'Male', Money:98234, 
	Name: 'Paulos', Surname: 'Stamou', Country: 'Greece',  Gender: 'Male', Money: 3422, 
    Name: 'Soula', Surname: 'Spuropoulou', Country: 'Greece', Gender: 'Female', Money:400, 
	Name: 'Paul', Surname: 'Pierce', Country: 'USA',  Gender: 'Male',Money: 13000,
	Name: 'Helen', Surname: 'Smith', Country: 'Great Britain', Gender: 'Female', Money:1000, 
	Name: 'Cathrine', Surname: 'Bryant', Country: 'Great Britain', Gender: 'Female', Money: 8712,
	Name: 'Jenny', Surname: 'Scalabrini', Country: 'USA', Gender: 'Female', Money:92214];
  
const groupByProperties = ['Country', 'Gender'];
const groupResult =  groupBy( Group: data, By: groupByProperties );

console.log(groupResult);

【讨论】:

最好能解释一下为什么这是解决方案,而不是仅仅发布一些代码【参考方案11】:
    var arr = [
        shape: 'square', color: 'red', used: 1, instances: 1,
        shape: 'square', color: 'red', used: 2, instances: 1,
        shape: 'circle', color: 'blue', used: 0, instances: 0,
        shape: 'square', color: 'blue', used: 4, instances: 4,
        shape: 'circle', color: 'red', used: 1, instances: 1,
        shape: 'circle', color: 'red', used: 1, instances: 0,
        shape: 'square', color: 'blue', used: 4, instances: 5,
        shape: 'square', color: 'red', used: 2, instances: 1
    ];


    result = [];

    arr.forEach(function (a) 
        if ( !this[a.color] && !this[a.shape] ) 
            this[a.color] =  color: a.color, shape: a.shape, used: 0, instances: 0 ;
            result.push(this[a.color]);
         
        this[a.color].used += a.used;
        this[a.color].instances += a.instances;
    , Object.create(null));

    console.log(result);


**Output:**
    [
  
    "color": "red",
    "shape": "square",
    "used": 11,
    "instances": 9
  ,
  
    "color": "blue",
    "shape": "circle",
    "used": 4,
    "instances": 4
  
]


    thats all perfetcly working.

     Enjoy your coding....

【讨论】:

【参考方案12】:

1.sumkeys 3.组键

  var arr = [
    shape: 'square', color: 'red', used: 1, instances: 1,
    shape: 'square', color: 'red', used: 2, instances: 1,
    shape: 'circle', color: 'blue', used: 0, instances: 0,
    shape: 'square', color: 'blue', used: 4, instances: 4,
    shape: 'circle', color: 'red', used: 1, instances: 1,
    shape: 'circle', color: 'red', used: 1, instances: 0,
    shape: 'square', color: 'red', used: 4, instances: 4,
    shape: 'square', color: 'red', used: 2, instances: 2
];
   function groupbykeys(arr, groupKeys, sumKeys)
    var   hash = Object.create(null),
       grouped = [];
      arr.forEach(function (o) 
       var key = groupKeys.map(function (k)  return o[k]; ).join('|');
         if (!hash[key]) 
           hash[key] = Object.keys(o).reduce((result, key)=> 
            result[key]=o[key]; 
            if(sumKeys.includes(key))
               result[key]=0;
            return result;
          ,  ); //map_(o) // shape: o.shape, color: o.color, used: 0, instances: 0 ;
           grouped.push(hash[key]);
       
       sumKeys.forEach(function (k)  hash[key][k] += o[k]; );
   );
     return grouped;
     
   
   
var result=groupbykeys(arr,['shape','color'],['used','instances']);
console.log(result)

【讨论】:

以上是关于按数组中的多个属性对对象进行分组,然后将它们的值相加的主要内容,如果未能解决你的问题,请参考以下文章

对象从数组而不是硬代码中赋值

将具有对象属性的数组分组的最佳方法以及如何呈现结果

如何按特定的子数组值对多维数组进行分组?

通过对数据框列中的值进行分组来绘制多个图?

如何按月对数组中的值进行分组?

如何按天、周、月对具有时间戳属性的对象进行分组?