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

例如,给定此对象数组:

[ 
    { 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,然后遍历生成的对象,自己计算总数?


当前回答

基于以前的答案

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处的当前iteree值计算的,其值是该prop的所有值以及当前值的列表。

其他回答

您可以从array.reduce()构建ES6映射。

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

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

它不需要任何库(与例如_.groupBy()不同)您得到的是JavaScript Map而不是对象(例如,由_.groupBy()返回)。这有很多好处,包括:它会记住第一次添加项目的顺序,键可以是任何类型,而不仅仅是字符串。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"}]

要做到这一点,我通常首先按id分组,然后合并每个结果数组。相反,您可以直接在reduce()中执行合并:

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

稍后编辑:

对于大多数目的来说,上述方法可能足够有效。但最初的问题是“最有效”的,正如一些人所指出的,上述解决方案并非如此。问题主要是为每个条目实例化一个新数组。我本以为JS解释器会优化这一点,但似乎并非如此。

有人建议进行编辑来解决这个问题,但看起来确实更复杂。原始代码段已经有点提高了可读性。如果你真的想这样做,请使用for循环!这不是罪!它需要一到两行代码,但它比函数技术更简单,尽管它并不短:

groupedMap = new Map();
for (const e of initialArray) {
    if (groupedMap.has(e.id)) {
        groupedMap.get(e.id).push(e)
    }
    else {
        groupedMap.set(e.id, [e])
    }
}
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

发件人:http://underscorejs.org/#groupBy

GroupBy one liner,ES2021解决方案

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).push(b),a),{});

TypeScript(类型脚本)

const groupBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>
  array.reduce((acc, value, index, array) => {
    (acc[predicate(value, index, array)] ||= []).push(value);
    return acc;
  }, {} as { [key: string]: T[] });

示例

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).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 },
//   ],
//   "1-2": [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

按映射分组

const groupByToMap = (x, f) =>
  x.reduce((a, b, i, x) => {
    const k = f(b, i, x);
    a.get(k)?.push(b) ?? a.set(k, [b]);
    return a;
  }, new Map());

TypeScript(类型脚本)

const groupByToMap = <T, Q>(array: T[], predicate: (value: T, index: number, array: T[]) => Q) =>
  array.reduce((map, value, index, array) => {
    const key = predicate(value, index, array);
    map.get(key)?.push(value) ?? map.set(key, [value]);
    return map;
  }, new Map<Q, T[]>());

具有排序功能

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

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

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