按数组中的对象分组最有效的方法是什么?

例如,给定此对象数组:

[ 
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]

我正在表格中显示这些信息。我想通过不同的方法进行分组,但我想对值求和。

我将Undercore.js用于其groupby函数,这很有用,但并不能完成全部任务,因为我不希望它们“拆分”,而是“合并”,更像SQL groupby方法。

我要找的是能够合计特定值(如果需要)。

因此,如果我按阶段分组,我希望收到:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

如果我组了阶段/步骤,我会收到:

[
    { Phase: "Phase 1", Step: "Step 1", Value: 15 },
    { Phase: "Phase 1", Step: "Step 2", Value: 35 },
    { Phase: "Phase 2", Step: "Step 1", Value: 55 },
    { Phase: "Phase 2", Step: "Step 2", Value: 75 }
]

是否有一个有用的脚本,或者我应该坚持使用Undercore.js,然后遍历生成的对象,自己计算总数?


当前回答

let x  = [
  {
    "id": "6",
    "name": "SMD L13",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "7",
    "name": "SMD L15",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "2",
    "name": "SMD L1",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  }
];

function groupBy(array, property) {
  return array.reduce((accumulator, current) => {
    const object_property = current[property];
    delete current[property]

    let classified_element = accumulator.find(x => x.id === object_property.id);
    let other_elements = accumulator.filter(x => x.id !== object_property.id);

   if (classified_element) {
     classified_element.children.push(current)
   } else {
     classified_element = {
       ...object_property, 
       'children': [current]
     }
   }
   return [classified_element, ...other_elements];
 }, [])
}

console.log( groupBy(x, 'equipmentType') )

/* output 

[
  {
    "id": "1",
    "name": "SMD",
    "children": [
      {
        "id": "6",
        "name": "SMD L13"
      },
      {
        "id": "7",
        "name": "SMD L15"
      },
      {
        "id": "2",
        "name": "SMD L1"
      }
    ]
  }
]

*/

其他回答

基于@Ceasar Bautista的原始想法,我修改了代码并使用typescript创建了一个groupBy函数。

static groupBy(data: any[], comparator: (v1: any, v2: any) => boolean, onDublicate: (uniqueRow: any, dublicateRow: any) => void) {
    return data.reduce(function (reducedRows, currentlyReducedRow) {
      let processedRow = reducedRows.find(searchedRow => comparator(searchedRow, currentlyReducedRow));

      if (processedRow) {
        // currentlyReducedRow is a dublicateRow when processedRow is not null.
        onDublicate(processedRow, currentlyReducedRow)
      } else {
        // currentlyReducedRow is unique and must be pushed in the reducedRows collection.
        reducedRows.push(currentlyReducedRow);
      }

      return reducedRows;
    }, []);
  };

此函数接受一个回调(比较器)和一个第二个回调(onDuplicate),该回调比较行并查找副本。

用法示例:

data = [
    { name: 'a', value: 10 },
    { name: 'a', value: 11 },
    { name: 'a', value: 12 },
    { name: 'b', value: 20 },
    { name: 'b', value: 1 }
  ]

  private static demoComparator = (v1: any, v2: any) => {
    return v1['name'] === v2['name'];
  }

  private static demoOnDublicate = (uniqueRow, dublicateRow) => {
    uniqueRow['value'] += dublicateRow['value'];    
  };

使命感

groupBy(data, demoComparator, demoOnDublicate) 

将执行计算值和的分组。

{name: "a", value: 33}
{name: "b", value: 21}

我们可以根据项目的需要创建任意多个回调函数,并根据需要聚合这些值。在一个例子中,我需要合并两个数组,而不是求和数据。

使用linq.js可能更容易做到这一点,它是linq在JavaScript(DEMO)中的真正实现:

var linq = Enumerable.From(data);
var result =
    linq.GroupBy(function(x){ return x.Phase; })
        .Select(function(x){
          return {
            Phase: x.Key(),
            Value: x.Sum(function(y){ return y.Value|0; })
          };
        }).ToArray();

结果:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

或者,更简单地使用基于字符串的选择器(DEMO):

linq.GroupBy("$.Phase", "",
    "k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();

我不认为给出的答案是对问题的回应,我认为以下内容应回答第一部分:

常量arr=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]const groupBy=(key)=>arr.sort((a,b)=>a[key].localeCompare(b[key])).reduce((total,currentValue)=>{const newTotal=总计;如果(总长度&&总计[total.length-1][key]==当前值[key])新总计[total.length-1]={…总计[总长度-1],…当前值,值:parseInt(total[total.length-1].Value)+parseInt(currentValue.Value,};else newTotal[total.length]=当前值;return newTotal;}, []);console.log(groupBy(“阶段”));//=>[{阶段:“阶段1”,值:50},{阶段“阶段2”,值130}]console.log(groupBy(“步骤”));//=>[{步骤:“步骤1”,值:70},{步骤“步骤2”,值为110}]

常量动物=[{type:'狗',品种:“水坑”},{type:'狗',品种:“拉布拉多”},{类型:'cat',品种:“暹罗”},{type:'狗',品种:“法国斗牛犬”},{类型:'cat',品种:'泥'}];var groupBy=(arr,prop)=>{return arr.reduce((objs,obj)=>{const key=obj[prop];if(键){让fi=objs.findIndex(x=>x.key==key);如果(fi>=0){objs[fi].values=[…objs[fi].values,obj];}其他{对象.推送({key:键,值:[obj]})}}返回对象;}, []);}console.log(groupBy(动物,“类型”))

我已经改进了答案。此函数获取组字段数组并返回分组对象,该对象的键也是组字段的对象。

function(xs, groupFields) {
        groupFields = [].concat(groupFields);
        return xs.reduce(function(rv, x) {
            let groupKey = groupFields.reduce((keyObject, field) => {
                keyObject[field] = x[field];
                return keyObject;
            }, {});
            (rv[JSON.stringify(groupKey)] = rv[JSON.stringify(groupKey)] || []).push(x);
            return rv;
        }, {});
    }



let x = [
{
    "id":1,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":2,
    "multimedia":false,
    "language":["fr"]
},
{
    "id":3,
    "multimedia":true,
    "language":["tr"]
},
{
    "id":4,
    "multimedia":false,
    "language":[]
},
{
    "id":5,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":6,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":7,
    "multimedia":false,
    "language":["tr","fr"]
}
]

groupBy(x, ['multimedia','language'])

//{
//{"multimedia":false,"language":["tr"]}: Array(3), 
//{"multimedia":false,"language":["fr"]}: Array(1), 
//{"multimedia":true,"language":["tr"]}: Array(1), 
//{"multimedia":false,"language":[]}: Array(1), 
//{"multimedia":false,"language":["tr","fr"]}: Array(1)
//}