分组对象数组的最有效方法

问题描述 投票:341回答:34

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

例如,给定此对象数组:

[ 
    { 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" }
]

我在表格中显示这些信息。我想用不同的方法分组,但我想总结这些值。

我正在使用Underscore.js作为其groupby函数,这很有帮助,但并没有完成整个技巧,因为我不希望它们“拆分”但是“合并”,更像是SQL group by方法。

我正在寻找的是能够总计特定值(如果要求)。

所以,如果我通过Phase进行组合,我希望收到:

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

如果我做了很多Phase / Step,我会收到:

[
    { 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 }
]

是否有一个有用的脚本,或者我应该坚持使用Underscore.js,然后循环结果对象自己做总计?

javascript arrays object group-by underscore.js
34个回答
555
投票

如果你想避免使用外部库,你可以简洁地实现groupBy()的vanilla版本,如下所示:

var groupBy = function(xs, key) {
  return xs.reduce(function(rv, x) {
    (rv[x[key]] = rv[x[key]] || []).push(x);
    return rv;
  }, {});
};

console.log(groupBy(['one', 'two', 'three'], 'length'));

// => {3: ["one", "two"], 5: ["three"]}

14
投票

你可以用var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }]; var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \ FROM ? GROUP BY Phase, Step',[data]); JavaScript库来做到这一点:

at jsFiddle

试试这个例子at jsPref

顺便说一句:在大型阵列上(100000条记录以上)Alasql更快到了Linq。见测试this example

评论:

  • 这里我将Value放在方括号中,因为VALUE是SQL中的关键字
  • 我必须使用CAST()函数将字符串值转换为数字类型。

9
投票

MDN在他们的Array.reduce()文档中有// Grouping objects by a property // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property var people = [ { name: 'Alice', age: 21 }, { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ]; function groupBy(objectArray, property) { return objectArray.reduce(function (acc, obj) { var key = obj[property]; if (!acc[key]) { acc[key] = []; } acc[key].push(obj); return acc; }, {}); } var groupedPeople = groupBy(people, 'age'); // groupedPeople is: // { // 20: [ // { name: 'Max', age: 20 }, // { name: 'Jane', age: 20 } // ], // 21: [{ name: 'Alice', age: 21 }] // }

Map

8
投票

虽然问题有一些答案,答案看起来有点过于复杂,但我建议使用vanilla Javascript进行分组,如果需要的话,使用function groupBy(array, groups, valueKey) { var map = new Map; groups = [].concat(groups); return array.reduce((r, o) => { groups.reduce((m, k, i, { length }) => { var child; if (m.has(o[k])) return m.get(o[k]); if (i + 1 === length) { child = Object .assign(...groups.map(k => ({ [k]: o[k] })), { [valueKey]: 0 }); r.push(child); } else { child = new Map; } m.set(o[k], child); return child; }, map)[valueKey] += +o[valueKey]; return r; }, []) }; var data = [{ 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" }]; console.log(groupBy(data, 'Phase', 'Value')); console.log(groupBy(data, ['Phase', 'Step'], 'Value'));

.as-console-wrapper { max-height: 100% !important; top: 0; }
arrow functions

6
投票

这个解决方案采用任意函数(不是键),因此它比上面的解决方案更灵活,并且允许lambda expressions,它类似于LINQ中使用的Array.prototype.groupBy = function (funcProp) { return this.reduce(function (acc, val) { (acc[funcProp(val)] = acc[funcProp(val)] || []).push(val); return acc; }, {}); };

Array

注意:是否要扩展[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;}) 的原型取决于您。

大多数浏览器支持的示例:

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)

使用箭头功能的示例(ES6):

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}

以上两个例子都返回:

const groupBy = (xs, key) => xs.reduce((acc, x) => Object.assign({}, acc, {
  [x[key]]: (acc[x[key]] || []).concat(x)
}), {})

console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}

6
投票

没有突变:

Array.prototype.groupBy = function(hash){
  var _hash = hash ? hash : function(o){return o;};

  var _map = {};
  var put = function(map, key, value){
    if (!map[_hash(key)]) {
        map[_hash(key)] = {};
        map[_hash(key)].group = [];
        map[_hash(key)].key = key;

    }
    map[_hash(key)].group.push(value); 
  }

  this.map(function(obj){
    put(_map, obj, obj);
  });

  return Object.keys(_map).map(function(key){
    return {key: _map[key].key, group: _map[key].group};
  });
}

6
投票

我想建议我的方法。首先,单独分组和聚合。让我们宣布典型的“分组依据”功能。它需要另一个函数来为每个要分组的数组元素生成“哈希”字符串。

data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
    /* aggreagating */
    .map(function(el){ 
         var sum = el.group.reduce(
           function(l,c){
             return l + parseInt(c.Value);
           },
           0
         );
         el.key.Value = sum; 
         return el.key;
    });

分组完成后,您可以根据需要汇总数据

groupByArray(xs, key) {
    return xs.reduce(function (rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find((r) => r && r.key === v);
        if (el) {
            el.values.push(x);
        }
        else {
            rv.push({
                key: v,
                values: [x]
            });
        }
        return rv;
    }, []);
}

共同的是它有效。我已经在chrome控制台中测试了这段代码。随时改进并发现错误;)


3
投票
const groupBy = (prop) => (xs) =>
  xs.reduce((rv, x) =>
    Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});

这个输出数组。


3
投票

根据以前的答案

const groupBy = (prop) => (xs) =>
  xs.reduce((acc, x) => ({
    ...acc,
    [ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
  }), {});

如果您的环境支持,使用对象扩展语法来查看它会更好一些。

prop

这里,我们的reducer采用部分形成的返回值(从空对象开始),并返回一个由前一个返回值的展开成员组成的对象,以及一个新成员,其成员的密钥是根据当前迭代的值来计算的。 Array.prototype.groupBy(),其值是该道具的所有值的列表以及当前值。


2
投票

让我们生成一个通用的Array.prototype.groupBy()工具。只是为了变化,让我们使用ES6 fanciness扩展运算符来进行递归方法的一些Haskellesque模式匹配。另外,让我们的e接受一个回调,它将项目(i)索引(a)和应用数组(Array.prototype.groupBy = function(cb){ return function iterate([x,...xs], i = 0, r = [[],[]]){ cb(x,i,[x,...xs]) ? (r[0].push(x), r) : (r[1].push(x), r); return xs.length ? iterate(xs, ++i, r) : r; }(this); }; var arr = [0,1,2,3,4,5,6,7,8,9], res = arr.groupBy(e => e < 5); console.log(res);)作为参数。


2
投票

Ceasar的答案很好,但仅适用于数组内部元素的内部属性(长度为字符串)。

这个实现更像是:const groupBy = function (arr, f) { return arr.reduce((out, val) => { let by = typeof f === 'function' ? '' + f(val) : val[f]; (out[by] = out[by] || []).push(val); return out; }, {}); };

Array.prototype.groupBy = function (groupingKeyFn) {
    if (typeof groupingKeyFn !== 'function') {
        throw new Error("groupBy take a function as only parameter");
    }
    return this.reduce((result, item) => {
        let key = groupingKeyFn(item);
        if (!result[key])
            result[key] = [];
        result[key].push(item);
        return result;
    }, {});
}

var a = [
	{type: "video", name: "a"},
  {type: "image", name: "b"},
  {type: "video", name: "c"},
  {type: "blog", name: "d"},
  {type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));

希望这可以帮助...


159
投票

使用ES6 Map对象:

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
         const key = keyGetter(item);
         const collection = map.get(key);
         if (!collection) {
             map.set(key, [item]);
         } else {
             collection.push(item);
         }
    });
    return map;
}

// example usage

const pets = [
    {type:"Dog", name:"Spot"},
    {type:"Cat", name:"Tiger"},
    {type:"Dog", name:"Rover"}, 
    {type:"Cat", name:"Leo"}
];
    
const grouped = groupBy(pets, pet => pet.type);
    
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
    
    

关于地图:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map


2
投票

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
reduce

2
投票

基于ES6 iteratee的版本版本,支持功能iteratee

如果没有提供const data = [{id: 1, score: 2},{id: 1, score: 3},{id: 2, score: 2},{id: 2, score: 4}] const group = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {}); const groupBy = (arr, k, fn = () => true) => arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {}); console.log(group(data, 'id')) // grouping via `reduce` console.log(groupBy(data, 'id')) // same result if `fn` is omitted console.log(groupBy(data, 'score', x => x > 2 )) // group with the iteratee功能,则按预期工作:

const data = [ { 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" } ]

const groupBy = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupWith = (arr, k, fn = () => true) => 
  arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});

console.log(groupBy(data, 'Phase'))
console.log(groupWith(data, 'Value', x => x > 30 ))  // group by `Value` > 30

在OP问题的背景下:

values

另一个ES6版本反转分组并使用keys作为keysgrouped values作为const data = [{A: "1"}, {B: "10"}, {C: "10"}] const groupKeys = arr => arr.reduce((r,c) => (Object.keys(c).map(x => r[c[x]] = [...r[c[x]] || [], x]),r),{}); console.log(groupKeys(data))

let groupbyKeys = function(arr, ...keys) {
  let keysFieldName = keys.join();
  return arr.map(ele => {
    let keysField = {};
    keysField[keysFieldName] = keys.reduce((keyValue, key) => {
      return keyValue + ele[key]
    }, "");
    return Object.assign({}, ele, keysField);
  }).reduce((groups, ele) => {
    (groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
      .push([ele].map(e => {
        if (keys.length > 1) {
          delete e[keysFieldName];
        }
        return e;
    })[0]);
    return groups;
  }, {});
};

console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));

注意:为简洁起见,函数以简短形式(一行)发布,并仅涉及该想法。您可以展开它们并添加其他错误检查等。


1
投票
function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}

1
投票

这是一个ES6版本,不会在null成员上中断

answer

1
投票

只是为了添加到Scott Sauyet的DataGrouper.register("sum", function(item) { return _.extend({}, item.key, {VALUE1: _.reduce(item.vals, function(memo, node) { return memo + Number(node.VALUE1);}, 0)}, {VALUE2: _.reduce(item.vals, function(memo, node) { return memo + Number(node.VALUE2);}, 0)} ); }); ,有些人在评论中询问如何使用他的函数来组合value1,value2等,而不是仅仅分组一个值。

只需要编辑他的求和函数:

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

离开主要一个(DataGrouper)不变:

export const groupBy = function groupByArray(xs, key, sortKey) {
      return xs.reduce(function(rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find(r => r && r.key === v);

        if (el) {
          el.values.push(x);
          el.values.sort(function(a, b) {
            return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
          });
        } else {
          rv.push({ key: v, values: [x] });
        }

        return rv;
      }, []);
    };

1
投票

有排序功能

var state = [
    {
      name: "Arkansas",
      population: "2.978M",
      flag:
  "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },{
      name: "Crkansas",
      population: "2.978M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },
    {
      name: "Balifornia",
      population: "39.14M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
      category: "city"
    },
    {
      name: "Florida",
      population: "20.27M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
      category: "airport"
    },
    {
      name: "Texas",
      population: "27.47M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
      category: "landmark"
    }
  ];
console.log(JSON.stringify(groupBy(state,'category','name')));

样品:

this post

1
投票

来自@mortb,@ jmarceli的答案和JSON.stringify()

我利用function groupBy(list, keyGetter) { const map = new Map(); list.forEach((item) => { const key = keyGetter(item); if (!map.has(key)) { map.set(key, [item]); } else { map.get(key).push(item); } }); return map; } const pets = [ {type:"Dog", age: 3, name:"Spot"}, {type:"Cat", age: 3, name:"Tiger"}, {type:"Dog", age: 4, name:"Rover"}, {type:"Cat", age: 3, name:"Leo"} ]; const grouped = groupBy(pets, pet => JSON.stringify({ type: pet.type, age: pet.age })); console.log(grouped); 作为PRIMITIVE VALUE多列的标识。

没有第三方

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

let rslt = _.groupBy(pets, pet => JSON.stringify(
 { type: pet.type, age: pet.age }));

console.log(rslt);

与Lodash第三方合作

const inputArray = [ 
    { 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" }
];

var outObject = inputArray.reduce(function(a, e) {
  // GROUP BY estimated key (estKey), well, may be a just plain key
  // a -- Accumulator result object
  // e -- sequentally checked Element, the Element that is tested just at this itaration

  // new grouping name may be calculated, but must be based on real value of real field
  let estKey = (e['Phase']); 

  (a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
  return a;
}, {});

console.log(outObject);

1
投票

检查答案 - 不是分组解决,但是直接的答案。

对于具有计算的键名称的某个字段的对象数组的REAL GROUP BY。

estKey

Phase一起玩 - 你可以分组超过一个领域

您还可以递归地对数据进行分组。例如,最初由Step分组,然后由fiddler字段分组。

自己验证,运行它。这就是人们称之为团伙的事吗?

祝你成功。

为了全人类的繁荣,程序员的高技能水平万岁!万岁,同志们!


0
投票

我从underscore.js window.helpers=(function (){ var lookupIterator = function(value) { if (value == null){ return function(value) { return value; }; } if (typeof value === 'function'){ return value; } return function(obj) { return obj[value]; }; }, each = function(obj, iterator, context) { var breaker = {}; if (obj == null) return obj; if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, length = obj.length; i < length; i++) { if (iterator.call(context, obj[i], i, obj) === breaker) return; } } else { var keys = [] for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key) for (var i = 0, length = keys.length; i < length; i++) { if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; } } return obj; }, // An internal function used for aggregate "group by" operations. group = function(behavior) { return function(obj, iterator, context) { var result = {}; iterator = lookupIterator(iterator); each(obj, function(value, index) { var key = iterator.call(context, value, index, obj); behavior(result, key, value); }); return result; }; }; return { groupBy : group(function(result, key, value) { Object.prototype.hasOwnProperty.call(result, key) ? result[key].push(value) : result[key] = [value]; }) }; })(); var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}]; console.dir(helpers.groupBy(arr,"b")); console.dir(helpers.groupBy(arr,function (el){ return el.b>2; })); 借用了这个方法

var arr = [ 
        { 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" }
    ];
var groupBy = (arr, pahse, step='') => {

   var pahseArr = [];
   var resultArr = [];

   arr.map((item)=>{
     var pushed = false;
     pahseArr.map((ele)=>{
       if(ele===item.Phase){
         pushed = true;
       }
     })
     if(!pushed){
       pahseArr.push(item.Phase);
     }     
   })

   pahseArr.map((item)=>{
      var sum = 0;
      arr.map((ele)=>{
        if(ele.Phase===item){
          sum += parseFloat(ele.Value)
        }
      })
      resultArr.push({
        Phase: item,
        Value: sum
      })
   })

   if(step!=''){
     var resultArr = [];


     pahseArr.map((item)=>{
         var stepArr = [];

         arr.map((item2)=>{
           var pushed = false;
           stepArr.map((ele)=>{
             if(ele===item2.Step){
               pushed = true;
             }
           })
           if(!pushed){
             stepArr.push(item2.Step);
           } 
         })

         stepArr.map((item1)=>{
            var sum = 0;
            arr.map((ele)=>{
              if(ele.Step===item1 && ele.Phase===item){
                sum += parseFloat(ele.Value)
              }
            })
            resultArr.push({
              Phase: item,
              Step: item1,
              Value: sum
            })
         })

     })
     return resultArr;
   }   
   return resultArr;

}

console.log(groupBy(arr, 'Phase'));
console.log(groupBy(arr, 'Phase', 'Step'));

0
投票

你可以通过以下方式完成。我刚刚形成了新的数组并从groupBy函数返回它。通过.map函数循环计算的计数

https://stackblitz.com/edit/typescript-ezydzv

96
投票

与ES6:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);

0
投票

我已经扩展了接受的答案,包括按多个属性分组,添加thenby并使其纯粹功能,没有变异。在export interface Group { key: any; items: any[]; } export interface GroupBy { keys: string[]; thenby?: GroupBy; } export const groupBy = (array: any[], grouping: GroupBy): Group[] => { const keys = grouping.keys; const groups = array.reduce((groups, item) => { const group = groups.find(g => keys.every(key => item[key] === g.key[key])); const data = Object.getOwnPropertyNames(item) .filter(prop => !keys.find(key => key === prop)) .reduce((o, key) => ({ ...o, [key]: item[key] }), {}); return group ? groups.map(g => (g === group ? { ...g, items: [...g.items, data] } : g)) : [ ...groups, { key: keys.reduce((o, key) => ({ ...o, [key]: item[key] }), {}), items: [data] } ]; }, []); return grouping.thenby ? groups.map(g => ({ ...g, items: groupBy(g.items, grouping.thenby) })) : groups; }; 上观看演示

qazxswpoi

53
投票

虽然linq答案很有趣,但它也很重。我的方法有些不同:

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key, {Value: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.Value);
    }, 0)});
});

你可以看到它in action on JSBin

我没有在Underscore中看到任何与has有关的东西,尽管我可能会错过它。它与_.contains大致相同,但使用_.isEqual而不是===进行比较。除此之外,其余部分是特定于问题的,尽管试图是通用的。

现在,DataGrouper.sum(data, ["Phase"])回归

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

DataGrouper.sum(data, ["Phase", "Step"])回归

[
    {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}
]

但是sum在这里只是一个潜在的功能。您可以随意注册其他人:

DataGrouper.register("max", function(item) {
    return _.extend({}, item.key, {Max: _.reduce(item.vals, function(memo, node) {
        return Math.max(memo, Number(node.Value));
    }, Number.NEGATIVE_INFINITY)});
});

现在DataGrouper.max(data, ["Phase", "Step"])将返回

[
    {Phase: "Phase 1", Step: "Step 1", Max: 10},
    {Phase: "Phase 1", Step: "Step 2", Max: 20},
    {Phase: "Phase 2", Step: "Step 1", Max: 30},
    {Phase: "Phase 2", Step: "Step 2", Max: 40}
]

或者如果你注册了这个:

DataGrouper.register("tasks", function(item) {
    return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
      return item.Task + " (" + item.Value + ")";
    }).join(", ")});
});

然后打电话给DataGrouper.tasks(data, ["Phase", "Step"])会得到你

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

DataGrouper本身就是一个功能。您可以使用您的数据和要分组的属性列表来调用它。它返回一个数组,其元素是具有两个属性的对象:key是分组属性的集合,vals是一个对象数组,包含不在键中的其余属性。例如,DataGrouper(data, ["Phase", "Step"])将产生:

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

DataGrouper.register接受一个函数并创建一个新函数,它接受初始数据和要分组的属性。然后,这个新函数采用上面的输出格式,依次对每个函数运行函数,返回一个新数组。生成的函数根据您提供的名称存储为DataGrouper的属性,如果您只想要本地引用,也会返回该函数。

那是很多解释。我希望代码相当简单!


38
投票

使用linq.js可能更容易做到这一点,DEMO旨在成为JavaScript中LINQ的真正实现(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(); ):

lodash groupBy

37
投票

我会检查https://jsfiddle.net/r7szvt5k/它似乎完全符合你的要求。它也非常轻巧,非常简单。

小提琴示例:arr

如果你的数组名是import groupBy from 'lodash/groupBy'; // if you still use require: // const groupBy = require('lodash/groupBy'); const a = groupBy(arr, function(n) { return n.Phase; }); // a is your array grouped by Phase attribute ,那么带有lodash的groupBy就是:

Map

20
投票

你可以从array.reduce()建立一个ES6 const groupedMap = initialArray.reduce( (entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]), new Map() );

_.groupBy()

与其他解决方案相比,这有一些优势:

  • 它不需要任何库(不像Map
  • 你得到一个JavaScript _.groupBy()而不是一个对象(例如lots of benefits返回的)。这有Map,包括: 它会记住首次添加项目的顺序, 键可以是任何类型而不仅仅是字符串。
  • Array.from(groupedMap.entries())是一个数组数组更有用的结果。但是如果你想要一个数组数组,你可以调用[key, group array](对于Array.from(groupedMap.values())对的数组)或const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}]; // The following variable should be created automatically const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}] (对于一个简单的数组数组)。
  • 它非常灵活;通常,无论您计划在下一次使用此地图做什么,都可以直接作为缩减的一部分来完成。

作为最后一点的一个例子,假设我有一个对象数组,我想通过id进行(浅)合并,如下所示:

reduce()

为此,我通常首先按id分组,然后合并每个结果数组。相反,您可以直接在const mergedArray = Array.from( objsToMerge.reduce( (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}), new Map() ).values() ); 中进行合并:

_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

19
投票
http://underscorejs.org/#groupBy

来自:Array.prototype.groupBy = function(keyFunction) { var groups = {}; this.forEach(function(el) { var key = keyFunction(el); if (key in groups == false) { groups[key] = []; } groups[key].push(el); }); return Object.keys(groups).map(function(key) { return { key: key, values: groups[key] }; }); };


15
投票
© www.soinside.com 2019 - 2024. All rights reserved.