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

例如,给定此对象数组:

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


当前回答

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

其他回答

解释相同的代码。喜欢它我喜欢这里

const groupBy = (array, key) => {
  return array.reduce((result, currentValue) => {
    (result[currentValue[key]] = result[currentValue[key]] || []).push(
      currentValue
    );
    console.log(result);
    return result;
  }, {});
};

USE

 let group =   groupBy(persons, 'color');

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

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

结果:

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

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

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

在我的特定用例中,我需要按属性分组,然后删除分组属性。

无论如何,该属性只是为了分组目的而添加到记录中的,对于向用户显示它没有意义。

    group (arr, key) {

        let prop;

        return arr.reduce(function(rv, x) {
            prop = x[key];
            delete x[key];
            (rv[prop] = (rv[prop] || [])).push(x);
            return rv;
        }, {});

    },

顶部答案中的起始函数归功于@caesar bautista。

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

这一个输出数组。

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

希望这有帮助。。。