首页 文章

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

提问于
浏览
260

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

例如,给定此对象数组:

[ 
    { 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,然后循环结果对象自己做总计?

30 回答

  • 391
    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));
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    
  • 39

    我会检查lodash groupBy它似乎完全符合您的要求 . 它也非常轻巧,非常简单 .

    小提琴示例:https://jsfiddle.net/r7szvt5k/

    如果你的数组名是 arr ,那么带有lodash的groupBy就是:

    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
    
  • 36

    此解决方案采用任意函数(不是键),因此它比上述解决方案更灵活,并且允许arrow functions,类似于 LINQ 中使用的lambda expressions

    Array.prototype.groupBy = function (funcProp) {
        return this.reduce(function (acc, val) {
            (acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
            return acc;
        }, {});
    };
    

    注意:是否要扩展 Array 的原型取决于您 .

    Example supported in most browsers:

    [{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
    

    Example using arrow functions (ES6):

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

    以上两个例子都返回:

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

    根据以前的答案

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

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

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

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

  • 6
    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]
            };
        });
    };
    
  • 6

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

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

    如果你想避免使用外部库,你可以简洁地实现 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"]}
    
  • 0
    data = [{id:1, name:'BMW'}, {id:2, name:'AN'}, {id:3, name:'BMW'}, {id:1, name:'NNN'}]
    key = 'id'//try by id or name
    data.reduce((previous, current)=>{
        previous[current[key]] && previous[current[key]].length != 0 ? previous[current[key]].push(current) : previous[current[key]] = new Array(current)
        return previous;
    }, {})
    
  • 6
    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

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

    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};
      });
    }
    

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

    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;
        });
    

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

  • 28

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

    这个实现更像是:this link

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

    希望这可以帮助...

  • 17

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

    尽管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 的属性,如果您只想要本地引用,也会返回该函数 .

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

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

    来自:http://underscorejs.org/#groupBy

  • 3

    与ES6:

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

    没有突变:

    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"]}
    
  • 1
    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;
        }, []);
    }
    

    这个输出数组 .

  • 1

    虽然问题有一些答案,答案看起来有点复杂,但我建议使用vanilla Javascript进行分组 .

    此解决方案具有一个函数,该函数使用数据 array 和一个返回 col 的属性名称以及用于计算值 value 的属性名称 .

    该函数依赖于一个对象,该对象充当了一个哈希表结果 .

    function groupBy(array, col, value) {
        var r = [], o = {};
        array.forEach(function (a) {
            if (!o[a[col]]) {
                o[a[col]] = {};
                o[a[col]][col] = a[col];
                o[a[col]][value] = 0;
                r.push(o[a[col]]);
            }
            o[a[col]][value] += +a[value];
        });
        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" }];
    
    document.write('<pre>' + JSON.stringify(groupBy(data, 'Phase', 'Value'), 0, 4) + '</pre>');
    
  • 14

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

    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'));
    
  • 2

    MDN在其 Array.reduce() 文档中有this example .

    // 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 }] 
    // }
    
  • 113

    您可以使用Alasql JavaScript库来执行此操作:

    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]);
    

    试试这个例子at jsFiddle .

    BTW: 在大型阵列上(100000条记录及更多)Alasql更快到了Linq . 见test at jsPref .

    评论:

    • 这里我将Value放在方括号中,因为VALUE是SQL中的关键字

    • 我必须使用CAST()函数将字符串值转换为数字类型 .

  • 2

    让我们生成一个通用的 Array.prototype.groupBy() 工具 . 只是为了变化,让's use ES6 fanciness the spread operator for some Haskellesque pattern matching on a recursive approach. Also let'使我们的 Array.prototype.groupBy() 接受一个回调,它将项目( 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);
    
  • 53

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

    具有计算的键名称的某些字段的REAL GROUP BY用于对象数组 .

    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);
    

    使用 estKey 进行游戏 - 您可以按多个字段进行分组

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

    自己验证,运行它 . Этотосамоеоно,чтолюдиназываютгруппировкой?

    祝你成功 .

    Даздравствуютвысокиепоказателимастерствапрограммистоввоимяпроцветаниявсегочеловечества! Ура,товарищи!

  • 1

    我从underscore.js fiddler借用了这个方法

    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;
     }));
    
  • 6

    只是为了添加到Scott Sauyet的answer,有些人在评论中询问如何使用他的函数来组合value1,value2等,而不是仅仅分组一个值 .

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

    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)}
        );
    });
    

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

    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;
    }());
    
  • 0

    使用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;
    }
    

    用法示例:

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

    jsfiddle:https://jsfiddle.net/buko8r5d/

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

  • 8

    我已经扩展了接受的答案,包括多个属性的分组,添加thenby并使其纯粹功能,没有变异 . 观看https://stackblitz.com/edit/typescript-ezydzv的演示

    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;
    };
    
  • 1

    从@mortb,@ jmarceli回答和this post

    我利用 JSON.stringify() 作为 PRIMITIVE VALUE group by的多个列的标识 .

    没有第三方

    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);
    

    与Lodash第三方合作

    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);
    
  • 14

    随着 sort feature

    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;
          }, []);
        };
    

    样品:

    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')));
    
  • 0

    您可以从 array.reduce() 构建ES6 Map .

    const groupedMap = initialArray.reduce(
        (entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
        new Map()
    );
    

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

    • 它不需要任何库(不像例如 _.groupBy()

    • 你得到一个JavaScript Map 而不是一个对象(例如由 _.groupBy() 返回) . 这有lots of benefits,包括:

    • 它会记住首次添加项目的顺序,

    • 键可以是任何类型而不仅仅是字符串 .

    • Map 是一个数组数组更有用的结果 . 但是如果你想要一个数组数组,那么你可以调用 Array.from(groupedMap.entries()) (对于 [key, group array] 对的数组)或 Array.from(groupedMap.values()) (对于一个简单的数组数组) .

    • 这很灵活;通常,无论您计划在下一次使用此 Map 做什么,都可以直接作为缩减的一部分来完成 .

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

    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() 中进行合并:

    const mergedArray = Array.from(
        objsToMerge.reduce(
            (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
            new Map()
        ).values()
    );
    

相关问题