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

Rai*_*l24 424 javascript arrays group-by object underscore.js

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

例如,给定此对象数组:

[ 
    { 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" }
]
Run Code Online (Sandbox Code Playgroud)

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

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

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

所以,如果我做了groupby Phase,我想要收到:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]
Run Code Online (Sandbox Code Playgroud)

如果我做了分组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 }
]
Run Code Online (Sandbox Code Playgroud)

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

Cea*_*sta 615

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

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"]}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个输出数组而不是对象: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;},[]); } (91认同)
  • 我们不能有合理的变量名吗? (28认同)
  • 我会修改这种方式:```return xs.reduce(function(rv,x){var v = key instanceof Function?key(x):x [key];(rv [v] = rv [v] || []).push(x); return rv;},{}); ```允许回调函数返回排序标准 (17认同)
  • 太棒了,正是我需要的.如果其他人需要它,这里是TypeScript签名:`var groupBy = function <TItem>(xs:TItem [],key:string):{[key:string]:TItem []} {...` (17认同)
  • 如果有人感兴趣,我对该函数进行了更具可读性和注释性的版本,并放入了要点:https://gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d我发现它有助于理解此处实际发生的情况。 (10认同)
  • 为了它的价值,tomitrescak的解决方案虽然方便,效率明显较低,因为find()可能是O(n).答案中的解决方案是O(n),来自reduce(对象赋值是O(1),就像push),而注释是O(n)*O(n)或O(n ^ 2)或at至少O(nlgn) (4认同)
  • @HamishJohnson,这是风格和经验的问题。但是`x`/`xs`在函数式编程中很常见,变量可以是任何类型,而`rv`在Python中很常见,作为“返回值”的简写。 (2认同)
  • 将“key”替换为“fn”,并将其设置为“rv[fn(x)]”而不是“rv[x[key]]”,您将拥有一个更有用的工具。然后,您的使用示例将变为 `console.log(groupBy(['one', 'two', ' Three'], a =&gt; a.length));` (2认同)
  • `xs.reduce((rv, x) =&gt; ((rv[x[key]] = rv[x[key]] || []).push(x), rv), {});` - 更简单带有[逗号运算符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator)和[箭头函数](https://developer.mozilla.org/ en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) (2认同)

mor*_*rtb 187

使用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"}]
    
    
Run Code Online (Sandbox Code Playgroud)

用法示例:

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"}]
    
    
Run Code Online (Sandbox Code Playgroud)

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

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

  • 您也可以尝试`console.log(Array.from(grouped));` (6认同)

Jos*_*lds 100

与ES6:

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

  • 优雅,但在大型阵列上却很慢! (5认同)
  • 这需要一点时间来习惯,但是大多数C ++模板也需要习惯 (3认同)
  • 我不知所措,仍然无法理解世界从...结果开始如何运作。因此,我无法入睡。 (2认同)
  • @user3307073 我认为乍一看就像“...result”是起始值,这就是为什么它如此令人困惑(如果我们还没有开始构建“result”,那么“...result”是什么?)。但起始值是“.reduce()”的第二个参数,而不是第一个,它位于底部:“{}”。所以你总是从 JS 对象开始。相反,“...result”位于传递给第一个参数的“{}”中,因此它的意思是“从已有的所有字段开始(在添加新的“item[key]”之前)”。 (2认同)

Sco*_*yet 56

虽然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)});
});
Run Code Online (Sandbox Code Playgroud)

你可以在JSBin上看到它.

我没有在Underscore中看到任何可以做到的事情has,尽管我可能会错过它.它与_.contains使用相似,但_.isEqual不是===用于比较.除此之外,其余部分是特定于问题的,尽管试图是通用的.

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

[
    {Phase: "Phase 1", Value: 50},
    {Phase: "Phase 2", Value: 130}
]
Run Code Online (Sandbox Code Playgroud)

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}
]
Run Code Online (Sandbox Code Playgroud)

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)});
});
Run Code Online (Sandbox Code Playgroud)

现在又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}
]
Run Code Online (Sandbox Code Playgroud)

或者如果你注册了这个:

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

然后打电话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)"}
]
Run Code Online (Sandbox Code Playgroud)

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"}
        ]
    }
]
Run Code Online (Sandbox Code Playgroud)

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

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


jma*_*eli 41

我会检查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
Run Code Online (Sandbox Code Playgroud)

  • 这个答案是不是有问题?有多种方式使 lodash _.groupBy 结果不是 OP 请求的结果格式。(1) 结果不是数组。(2) “值”已成为 lodash 对象结果中的“键”。 (2认同)

mel*_*okb 40

这可能更容易完成linq.js,这是一个真正的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();
Run Code Online (Sandbox Code Playgroud)

结果:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]
Run Code Online (Sandbox Code Playgroud)

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

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


Art*_*cca 28

你可以建立一个ES6 Maparray.reduce().

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

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

  • 它不需要任何库(不像例如_.groupBy())
  • 你得到一个JavaScript Map而不是一个对象(例如,返回_.groupBy()).这有很多好处,包括:
    • 它会记住首次添加项目的顺序,
    • 键可以是任何类型而不仅仅是字符串.
  • A Map是一个数组数组更有用的结果.但是如果你想要一个数组数组,那么你可以调用Array.from(groupedMap.entries())(对于一[key, group array]对数组)或Array.from(groupedMap.values())(对于一个简单的数组数组).
  • 它非常灵活; 通常,无论您计划在下一次使用此地图做什么,都可以直接作为缩减的一部分来完成.

作为最后一点的一个例子,假设我有一个对象数组,我想通过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"}]
Run Code Online (Sandbox Code Playgroud)

为此,我通常首先按id分组,然后合并每个结果数组.相反,您可以直接在以下位置执行合并reduce():

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

  • 我不知道为什么这没有更多选票。它简洁、易读(对我而言)并且_看起来_高效。[它不会在 IE11 上运行](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Browser_compatibility),但改造并不难(`a .reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map() )`,大约) (4认同)
  • 因为它实际上是低效的解决方案,因为它在每次reduce回调调用时实例化一个新数组。 (3认同)

小智 24

有点晚了,但也许有人喜欢这个。

ES6:

const users = [{
    name: "Jim",
    color: "blue"
  },
  {
    name: "Sam",
    color: "blue"
  },
  {
    name: "Eddie",
    color: "green"
  },
  {
    name: "Robert",
    color: "green"
  },
];
const groupBy = (arr, key) => {
  const initialValue = {};
  return arr.reduce((acc, cval) => {
    const myAttribute = cval[key];
    acc[myAttribute] = [...(acc[myAttribute] || []), cval]
    return acc;
  }, initialValue);
};

const res = groupBy(users, "color");
console.log("group by:", res);
Run Code Online (Sandbox Code Playgroud)


Jul*_*ins 22

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

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


cez*_*tek 17

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]
        };
    });
};
Run Code Online (Sandbox Code Playgroud)


nki*_*tku 17

GroupBy one-liner,一种ES2021解决方案

const groupBy = (x,f)=>x.reduce((a,b)=>((a[f(b)]||=[]).push(b),a),{});
Run Code Online (Sandbox Code Playgroud)

例子

const groupBy = (x, f) => x.reduce((a, b) => ((a[f(b)] ||= []).push(b), a), {});
// f -> should must return string/number because it will be use as key in object

// for demo

groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };
const colors = [
  "Apricot",
  "Brown",
  "Burgundy",
  "Cerulean",
  "Peach",
  "Pear",
  "Red",
];

groupBy(colors, v => v[0]); // group by colors name first letter
// {
//   A: ["Apricot"],
//   B: ["Brown", "Burgundy"],
//   C: ["Cerulean"],
//   P: ["Peach", "Pear"],
//   R: ["Red"],
// };
groupBy(colors, v => v.length); // group by length of color names
// {
//   3: ["Red"],
//   4: ["Pear"],
//   5: ["Brown", "Peach"],
//   7: ["Apricot"],
//   8: ["Burgundy", "Cerulean"],
// }

const data = [
  { comment: "abc", forItem: 1, inModule: 1 },
  { comment: "pqr", forItem: 1, inModule: 1 },
  { comment: "klm", forItem: 1, inModule: 2 },
  { comment: "xyz", forItem: 1, inModule: 2 },
];

groupBy(data, v => v.inModule); // group by module
// {
//   1: [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   2: [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupBy(data, x => x.forItem + "-" + x.inModule); // group by module with item
// {
//   "1-1": [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   "2-1": [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }
Run Code Online (Sandbox Code Playgroud)

  • 我爱我的简洁的需要更多时间才能弄清楚的神奇台词!迄今为止最(主观上)优雅的解决方案。 (2认同)

age*_*hun 16

您可以使用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]);
Run Code Online (Sandbox Code Playgroud)

在jsFiddle上试试这个例子.

顺便说一句:在大型阵列上(100000条记录以上)Alasql更快到了Linq.请参阅 jsPref上的测试.

评论:

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


Hop*_*per 13

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 }] 
// }
Run Code Online (Sandbox Code Playgroud)


Nin*_*olz 12

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

此解决方案具有一个函数,该函数接受带有数据的数组Map和返回的Map一个属性名称以及用于计算值的属性名称Map.

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

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'));
Run Code Online (Sandbox Code Playgroud)


Syn*_*Cap 12

检查答案 - 只是浅分组。理解减少是非常好的。问题还提供了额外聚合计算的问题。

这里是一个 REAL GROUP BY for Array of Objects by some field(s) with 1) 计算的键名和 2) 通过提供所需键的列表并将其唯一值转换为根键(如 SQL 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);
Run Code Online (Sandbox Code Playgroud)

estKey- 您可以按多个字段分组,添加额外的聚合、计算或其他处理。

您也可以递归地对数据进行分组。例如最初 group by Phase,然后 by Stepfield 等等。另外吹掉脂肪休息数据。

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

/**
 * Small helper to get SHALLOW copy of obj WITHOUT prop
 */
const rmProp = (obj, prop) => ( (({[prop]:_, ...rest})=>rest)(obj) )

/**
 * Group Array by key. Root keys of a resulting array is value
 * of specified key.
 *
 * @param      {Array}   src     The source array
 * @param      {String}  key     The by key to group by
 * @return     {Object}          Object with grouped objects as values
 */
const grpBy = (src, key) => src.reduce((a, e) => (
  (a[e[key]] = a[e[key]] || []).push(rmProp(e, key)),  a
), {});

/**
 * Collapse array of object if it consists of only object with single value.
 * Replace it by the rest value.
 */
const blowObj = obj => Array.isArray(obj) && obj.length === 1 && Object.values(obj[0]).length === 1 ? Object.values(obj[0])[0] : obj;

/**
 * Recursive grouping with list of keys. `keyList` may be an array
 * of key names or comma separated list of key names whom UNIQUE values will
 * becomes the keys of the resulting object.
 */
const grpByReal = function (src, keyList) {
  const [key, ...rest] = Array.isArray(keyList) ? keyList : String(keyList).trim().split(/\s*,\s*/);
  const res = key ? grpBy(src, key) : [...src];
  if (rest.length) {
for (const k in res) {
  res[k] = grpByReal(res[k], rest)
}
  } else {
for (const k in res) {
  res[k] = blowObj(res[k])
}
  }
  return res;
}

console.log( JSON.stringify( grpByReal(inputArray, 'Phase, Step, Task'), null, 2 ) );
Run Code Online (Sandbox Code Playgroud)


dar*_*eam 11

这是一个使用 ES6 的讨厌的、难以阅读的解决方案:

export default (arr, key) => 
  arr.reduce(
    (r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).push(v), r),
    {}
  );
Run Code Online (Sandbox Code Playgroud)

对于那些问如何做到这一点,甚至工作,这里有一个解释:

  • 在两者中=>你都有一个免费的return

  • Array.prototype.reduce函数最多接受 4 个参数。这就是为什么要添加第五个参数,以便我们可以使用默认值在参数声明级别为组 (k) 进行廉价的变量声明。(是的,这是魔法)

  • 如果我们当前的组在前一次迭代中不存在,我们创建一个新的空数组((r[k] || (r[k] = []))这将计算最左边的表达式,换句话说,一个现有的数组或一个空数组,这就是为什么push在该表达式之后有一个立即数,因为无论哪种方式,你都会得到一个数组。

  • 当有 时return,逗号,运算符将丢弃最左边的值,返回针对此场景调整后的前一组。

一个更容易理解的版本是:

export default (array, key) => 
  array.reduce((previous, currentItem) => {
    const group = currentItem[key];
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {});
Run Code Online (Sandbox Code Playgroud)

编辑:

TS版本:

const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
  list.reduce((previous, currentItem) => {
    const group = getKey(currentItem);
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {} as Record<K, T[]>);
Run Code Online (Sandbox Code Playgroud)

  • TS 的最佳语法。最佳答案,当与复杂对象一起使用时。`const groups = groupBy(items, (x) =&gt; x.groupKey);` (3认同)
  • 你愿意解释一下吗,效果很完美 (2认同)

Ant*_*ton 8

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

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};
  });
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    });
Run Code Online (Sandbox Code Playgroud)

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


Ble*_*ess 7

没有突变:

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"]}
Run Code Online (Sandbox Code Playgroud)


Die*_*ego 7

这个解决方案采用任意函数(不是键),因此它比上面的解决方案更灵活,并且允许箭头函数,它类似于LINQ中使用的lambda表达式:

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

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

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

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

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

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
Run Code Online (Sandbox Code Playgroud)

以上两个例子都返回:

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}
Run Code Online (Sandbox Code Playgroud)


Yag*_*res 7

想象一下,你有这样的事情:

[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]

通过做这个: const categories = [...new Set(cars.map((car) => car.cat))]

你会得到这个: ['sedan','sport']

说明: 1. 首先,我们通过传递一个数组来创建一个新的 Set。因为 Set 只允许唯一值,所以将删除所有重复项。

  1. 现在重复项消失了,我们将使用扩展运算符将其转换回数组...

设置文档:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc:https : //developer.mozilla.org/en-US/docs/Web/JavaScript /Reference/Operators/Spread_syntax


Ben*_*ers 6

根据之前的回答

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

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

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

在这里,我们的 reducer 获取部分形成的返回值(从一个空对象开始),并返回一个由前一个返回值的展开成员组成的对象,以及一个新成员,其键是根据当前 iteree 的值计算出来的prop并且其值是该道具的所有值以及当前值的列表。


mar*_*424 6

您可以使用原生 JavaScriptgroup数组方法(目前处于第 2 阶段)。

我认为与减少或使用第三方库(例如 lodash 等)相比,解决方案要优雅得多。

const products = [{
    name: "milk",
    type: "dairy"
  },
  {
    name: "cheese",
    type: "dairy"
  },
  {
    name: "beef",
    type: "meat"
  },
  {
    name: "chicken",
    type: "meat"
  }
];

const productsByType = products.group((product) => product.type);

console.log("Grouped products by type: ", productsByType);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/npm/core-js-bundle@3.23.2/minified.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

  • 还有“Array.prototype.groupToMap”。 (2认同)

tom*_*cak 5

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;
    }, []);
}
Run Code Online (Sandbox Code Playgroud)

这个输出数组。