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

IT技术 javascript arrays object group-by underscore.js
2021-01-07 00:02:17

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

例如,给定这个对象数组:

[ 
    { 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 函数,这很有帮助,但并不能解决所有问题,因为我不希望它们“拆分”而是“合并”,更像是 SQLgroup by方法。

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

因此,如果我执行 groupby Phase,我希望收到:

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

如果我做了 groupy 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,然后循环遍历结果对象来自己计算总数?

6个回答

如果你想避免使用外部库,你可以简洁地实现一个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"]}

如果有人感兴趣,我为这个函数制作了一个更具可读性和注释的版本,并将其放在一个要点中:gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d我发现它有助于理解这里实际发生的事情。
2021-02-15 00:02:17
太好了,正是我需要的。如果其他人需要它,这里是 TypeScript 签名:var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
2021-02-22 00:02:17
我会这样修改:``` return xs.reduce(function(rv, x) { var v = key instanceof Function ? key(x) : x[key]; (rv[v] = rv[v] || []).push(x); return rv; }, {}); ``` 允许回调函数返回一个排序条件
2021-02-27 00:02:17
这是一个输出数组而不是对象的: 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; }, []); }
2021-03-07 00:02:17
我们不能有合理的变量名吗?
2021-03-08 00:02:17

使用 ES6 Map 对象:

/**
 * @description
 * Takes an Array<V>, and a grouping function,
 * and returns a Map of the array grouped by the grouping function.
 *
 * @param list An array of type V.
 * @param keyGetter A Function that takes the the Array type V as an input, and returns a value of type K.
 *                  K is generally intended to be a property key of V.
 *
 * @returns Map of the array grouped by the grouping function.
 */
//export function groupBy<K, V>(list: Array<V>, keyGetter: (input: V) => K): Map<K, Array<V>> {
//    const map = new Map<K, Array<V>>();
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"}]

const odd = Symbol();
const even = Symbol();
const numbers = [1,2,3,4,5,6,7];

const oddEven = groupBy(numbers, x => (x % 2 === 1 ? odd : even));
    
console.log(oddEven.get(odd)); // -> [1,3,5,7]
console.log(oddEven.get(even)); // -> [2,4,6]

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

@FaiZalDong:我不确定什么最适合您的情况?如果我console.log(grouped.entries());在 jsfiddle 示例中编写它,它会返回一个可迭代的,它的行为类似于键 + 值的数组。你能试试看是否有帮助吗?
2021-02-14 00:02:17
你也可以试试 console.log(Array.from(grouped));
2021-02-17 00:02:17
我已将 jsfiddle 转换为 stackoverflow 内联代码片段。原来的 jsFiddle 仍然在线:jsfiddle.net/buko8r5d
2021-02-18 00:02:17
查看组中元素的数量: Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
2021-02-28 00:02:17
@mortb,如何在不调用get()方法的情况下获取它这是我希望输出显示而不传递密钥
2021-03-03 00:02:17

使用 ES6:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);
优雅,但在较大的阵列上速度很慢!
2021-02-12 00:02:17
@ArthurTacca 你是对的,result是累加器,这意味着它是每个项目更新的“工作值”。它从空对象开始,每个项目都添加到分配给具有分组字段值名称的属性的数组中。
2021-02-19 00:02:17
我绞尽脑汁,仍然无法理解它是如何从...result. 现在我因此无法入睡。
2021-02-25 00:02:17
@user3307073 我认为它乍一看就像...result是起始值,这就是它如此令人困惑的原因(...result如果我们还没有开始构建result呢?)。但是起始值是 的第二个参数.reduce(),而不是第一个参数,它位于底部:{}所以你总是从一个 JS 对象开始。相反,...result是在{}传递给第一个参数那个中,所以它的意思是“从你已经拥有的所有字段开始(在添加新字段之前item[key])”。
2021-03-02 00:02:17
需要一点时间来习惯,但大多数 C++ 模板也是如此
2021-03-09 00:02:17

您可以Maparray.reduce().

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

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

  • 它不需要任何库(与 eg 不同_.groupBy()
  • 您得到的是 JavaScriptMap而不是对象(例如,由 返回_.groupBy())。这有很多好处,包括:
    • 它会记住第一次添加项目的顺序,
    • 键可以是任何类型而不仅仅是字符串。
  • AMap是比数组数组更有用的结果。但是如果你确实想要一个数组数组,那么你可以调用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()
);
我不知道为什么这没有更多选票。它简洁、易读(对我来说)并且看起来很高效。 它不会在 IE11 上运行,但改造不是太难(a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()),大约)
2021-02-16 00:02:17

我会检查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
这个答案是不是有问题?有多种方式使 lodash _.groupBy 结果不是 OP 请求的结果格式。(1) 结果不是数组。(2) “值”已成为 lodash 对象结果中的“键”。
2021-02-27 00:02:17
为简单起见,您可以将属性直接传递给 groupBy: const a = groupBy(arr, 'Phase')
2021-03-05 00:02:17