ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

[ 
    { 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 方法。

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

因此,如果我进行 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,然后循环遍历生成的对象以自己进行总计?

虽然 _.groupBy 本身不能完成这项工作,但它可以与其他 Underscore 函数结合来完成所要求的工作。无需手动循环。请参阅此答案:stackoverflow.com/a/66112210/1166087
已接受答案的更具可读性的版本:­ function groupBy(data, key){ return data.reduce( (acc, cur) => { acc[cur[key]] = acc[cur[key]] || []; // if the key is new, initiate its value to an array, otherwise keep its own array value acc[cur[key]].push(cur); return acc; } , []) }

C
Ceasar Bautista

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


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

使用 ES6 Map 对象:

/** * @description * 接受一个 Array 和一个分组函数 * 并返回由分组函数分组的数组的 Map。 * * @param list V 类型的数组。 * @param keyGetter 以数组类型 V 作为输入的函数,并返回 K 类型的值。 * K 通常用作 V 的属性键。 * * @returns 按分组函数分组的数组的映射。 */ //导出函数 groupBy(list: Array, keyGetter: (input: V) => K): Map> { // const map = new Map< K, 数组>();函数 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.推(项目);}});返回地图; } // 示例用法 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();常量偶数 = Symbol();常量数 = [1,2,3,4,5,6,7];常量奇数 = groupBy(数字, x => (x % 2 === 1 ? 奇数: 偶数)); 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


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

使用 ES6:

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

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

您可以从 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() 返回的)。这有很多好处,包括:它记住第一次添加项目的顺序,键可以是任何类型而不仅仅是字符串。

它会记住第一次添加项目的顺序,

键可以是任何类型,而不仅仅是字符串。

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

我不知道为什么这没有更多的选票。它简洁、易读(对我而言)并且看起来高效。 It doesn't fly on IE11,但改造并不难(a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()),大约)
j
jmarceli

我会检查 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 对象结果中的“键”。
为了简单起见,您可以直接将属性传递给 groupBy:const a = groupBy(arr, 'Phase')
S
Sebastian Simon

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

好吧,这是很多解释。代码相当简单,我希望!


嗨..可以看到你只按一个值分组和求和,但如果我想按 value1 和 valu2 和 value3 求和...你有解决方案吗?
@SAMUELOSPINA 你有没有找到办法做到这一点?
n
nkitku

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

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

打字稿

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

例子

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 },
//   ],
//   "1-2": [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupByToMap

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

打字稿

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

||= 被我的 Babel 拒绝了?
那是最近才标准化的。 blog.saeloun.com/2021/06/17/…
我爱我,我的简洁需要更多时间来弄清楚魔术单线!到目前为止,最(主观)优雅的解决方案。
非常优雅,尤其是能够以这种方式调整谓词。华丽的。
M
Mark Milford

使用 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();

结果:

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

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

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

我们可以在此处分组时使用多个属性吗:GroupBy(function(x){ return x.Phase; })
linq.js 的性能如何?
H
HoppyKamper

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

显然,我错过了一些东西。为什么我们不能用 MDN 的这个解决方案生成一个数组?如果你尝试用 ,[] 初始化 reducer,你会得到一个空数组。
J
Julio Marins
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

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


c
cezarypiatek
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]
        };
    });
};

K
Kmylo darkstar

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

ES6:

const users = [{ name: "Jim", color: "blue" }, { name: "Sam", color: "blue" }, { name: "Eddie", color: "green" }, { name: "罗伯特”,颜色:“绿色”},]; 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("分组方式:", res);


谢谢你的方法有效,我对这个概念有点新,你能解释一下 initialValue 部分它做了什么吗
@PraveenVishnu initialValue 是减少回调的一部分,我只是想明确添加它developer.mozilla.org/es/docs/Web/JavaScript/Reference/…
a
agershun

您可以使用 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

顺便说一句: 在大型数组(100000 条记录及更多)上,Alasql 比 Linq 更快。请参阅测试 at jsPref

注释:

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

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


N
Nina Scholz

一种较新的方法,其中包含一个用于分组的对象和另外两个函数来创建一个键并获取一个具有所需分组项的对象和另一个用于增加值的键。

const groupBy = (array, groups, valueKey) => { const getKey = o => groups.map(k => o[k]).join('|'), getObject = o => Object.fromEntries([. ..groups.map(k => [k, o[k]]), [valueKey, 0]]);组 = [].concat(组); return Object.values(array.reduce((r, o) => { (r[getKey(o)] ??= getObject(o))[valueKey] += +o[valueKey]; return r; }, { })); }, data = [{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task :“任务 2”,值:“10”},{阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 1”,值:“15”},{阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 2”,值:“20”},{阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 1”,值:“25”}, { 阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 2”,值:“30”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 1” ,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]; console.log(groupBy(data, 'Phase', 'Value')); console.log(groupBy(data, ['Phase', 'Step'], 'Value')); .as-console-wrapper { max-height: 100% !important;顶部:0; }

旧方法:

虽然这个问题有一些答案,而且答案看起来有点复杂,但我建议使用带有嵌套(如果需要)Map 的 vanilla Javascript 进行分组。

函数 groupBy(array, groups, valueKey) { var map = new Map;组 = [].concat(组); 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]; 返回 r; }, []) }; var data = [{ 阶段:“阶段 1”,步骤:“步骤 1”,任务:“任务 1”,值:“5”},{ 阶段:“阶段 1”,步骤:“步骤 1”,任务: “任务 2”,值:“10”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 1”,值:“15”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 2”,值:“20”},{ 阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 1”,值:“25”},{阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 2”,值:“30”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]; console.log(groupBy(data, 'Phase', 'Value')); console.log(groupBy(data, ['Phase', 'Step'], 'Value')); .as-console-wrapper { max-height: 100% !important;顶部:0; }


k
kevinrodriguez-io

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

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

对于那些询问这甚至如何工作的人,这里有一个解释:

在这两个 => 你有一个免费的回报

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

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

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

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

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

编辑:

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

你愿意解释一下吗,它很完美
@NuwanDammika - 在这两个 => 中你都有一个免费的“return” - reduce 函数最多需要 4 个参数。这就是为什么要添加第五个参数,以便我们可以为组 (k) 提供一个廉价的变量声明。 - 如果之前的值没有我们当前的组,我们创建一个新的空组 ((r[k] || (r[k] = [])) 这将评估最左边的表达式,否则为数组或空数组,这就是为什么在该表达式之后立即推送。 - 当有返回时,逗号运算符将丢弃最左边的值,返回调整后的前一组。
TS 的最佳语法。与复杂对象一起使用时的最佳答案。 const groups = groupBy(items, (x) => x.groupKey);
这很棒。我是一个 scala 人,感觉就像在家里一样。好吧..除了默认值是什么?
@javadba export default 只是用于 JS 模块的语法,类似于仅导出,默认关键字将允许您像这样导入:import Group from '../path/to/module';
m
marc_s

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

这是一个 REAL GROUP BY 对象数组,由一些字段组成,1)计算的键名和 2)通过提供所需键的列表并将其唯一值转换为根键(如 SQL GROUP)来实现分组级联的完整解决方案BY 确实如此。

const inputArray = [ { 阶段:“阶段 1”,步骤:“步骤 1”,任务:“任务 1”,值:“5”},{ 阶段:“阶段 1”,步骤:“步骤 1”,任务: “任务 2”,值:“10”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 1”,值:“15”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 2”,值:“20”},{ 阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 1”,值:“25”},{阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 2”,值:“30”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]; var outObject = inputArray.reduce(function(a, e) { // GROUP BY 估计的键 (estKey),嗯,可能只是普通键 // a -- 累加器结果对象 // e -- 顺序检查的元素,仅在本次迭代中测试的元素 // 可以计算新的分组名称,但必须基于真实字段的真实值 let estKey = (e['Phase']); (a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e); return a; }, {});控制台.log(outObject);

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

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

const inputArray = [ { 阶段:“阶段 1”,步骤:“步骤 1”,任务:“任务 1”,值:“5”},{ 阶段:“阶段 1”,步骤:“步骤 1”,任务: “任务 2”,值:“10”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 1”,值:“15”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 2”,值:“20”},{ 阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 1”,值:“25”},{阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 2”,值:“30”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}]; /** * 获取 obj 的 SHALLOW 副本而不使用 prop */ const rmProp = (obj, prop) => ( (({[prop]:_, ...rest})=>rest)(obj) 的小帮手/** * 按键分组数组。结果数组的根键是指定键的值 *。 * * @param {Array} src 源数组 * @param {String} key 分组依据 * @return {Object} 以分组对象为值的对象 */ const grpBy = (src, key) => src. reduce((a, e) => ( (a[e[key]] = a[e[key]] || []).push(rmProp(e, key)), a ), {}); /** * 如果对象数组只包含单个值的对象,则折叠它。 * 将其替换为剩余值。 */ const blowObj = obj => Array.isArray(obj) && obj.length === 1 && Object.values(obj[0]).length === 1 ? Object.values(obj[0])[0] : obj; /** * 带有键列表的递归分组。 `keyList` 可以是键名数组 * 或逗号分隔的键名列表,唯一值将 * 成为结果对象的键。 */ const grpByReal = function (src, keyList) { const [key, ...rest] = Array.isArray(keyList) ? keyList : String(keyList).trim().split(/\s*,\s*/);常量 res = 键? 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]) } } 返回资源; } console.log( JSON.stringify( grpByReal(inputArray, 'Phase, Step, Task'), null, 2 ) );


D
Diego

此解决方案采用任意函数(不是键),因此比上述解决方案更灵活,并允许 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 的原型由您决定。

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

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

使用箭头函数 (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"}]
}

我非常喜欢 ES6 解决方案。在不扩展 Array 原型的情况下稍作简化:let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
B
Bless

没有突变:

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

A
Anton

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

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 控制台中测试了这段代码。并随时改进和发现错误;)


谢谢 !喜欢这种方法,并且非常适合我的需求(我不需要聚合)。
我认为您想将 put(): map[_hash(key)].key = key; 中的行更改为 map[_hash(key)].key = _hash(key);
请注意,如果数组包含名称类似于对象原型中任何函数的字符串(例如:["toString"].groupBy()),这将失败
t
tomitrescak
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;
    }, []);
}

这个输出数组。


Y
Yago Gehres

想象一下,你有这样的东西:

[{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 只允许唯一值,所以所有重复项都将被删除。

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

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


很喜欢你的回答,是最短的,但我还是不明白其中的逻辑,尤其是这里分组的是谁?它是传播运算符(...)吗?还是“新 Set()”?请向我们解释...谢谢
1. 首先,我们通过传递一个数组来创建一个新的 Set。因为 Set 只允许唯一值,所以所有重复项都将被删除。 2. 现在重复项已经消失,我们将使用扩展运算符将其转换回数组... Set Doc:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Spread Operator:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
B
Benny Powers

根据以前的答案

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 采用部分形成的返回值(从一个空对象开始),并返回一个由前一个返回值的展开成员组成的对象,以及一个新成员,其键是根据当前 iteree 的值计算得出的prop,其值是该道具的所有值以及当前值的列表。


e
edin0x

groupBy 可以按特定键或给定分组函数对数组进行分组的函数。打字。

groupBy = <T, K extends keyof T>(array: T[], groupOn: K | ((i: T) => string)): Record<string, T[]> => {
  const groupFn = typeof groupOn === 'function' ? groupOn : (o: T) => o[groupOn];

  return Object.fromEntries(
    array.reduce((acc, obj) => {
      const groupKey = groupFn(obj);
      return acc.set(groupKey, [...(acc.get(groupKey) || []), obj]);
    }, new Map())
  ) as Record<string, T[]>;
};

我会对这个版本的性能基准(在每一轮使用新数组和解构以创建要设置的值)与另一个仅在需要时创建一个空数组的基准感兴趣。根据您的代码:gist.github.com/masonlouchart/da141b3af477ff04ccc626f188110f28
需要明确的是,对于偶然发现这一点的新手来说,这是 Typescript 代码,并且最初的问题被标记为 javascript,所以这是相当离题的,对吧?
A
Aznhar

我不认为给出的答案是对问题的回应,我认为以下内容应该回答第一部分:

const arr = [ { 阶段:“阶段 1”,步骤:“步骤 1”,任务:“任务 1”,值:“5”},{ 阶段:“阶段 1”,步骤:“步骤 1”,任务: “任务 2”,值:“10”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 1”,值:“15”},{ 阶段:“阶段 1”,步骤:“步骤 2”,任务:“任务 2”,值:“20”},{ 阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 1”,值:“25”},{阶段:“阶段 2”,步骤:“步骤 1”,任务:“任务 2”,值:“30”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 1”,值:“35”},{ 阶段:“阶段 2”,步骤:“步骤 2”,任务:“任务 2”,值:“40”} ] const groupBy = (key) => arr.sort((a , b) => a[key].localeCompare(b[key])).reduce((total, currentValue) => { const newTotal = total; if ( total.length && total[total.length - 1][key ] === currentValue[key] ) newTotal[total.length - 1] = { ...total[total.length - 1], ...currentValue, Value: parseInt(total[total.length - 1].Value ) + parseInt(currentValue.Value), }; 否则 newTotal[total.length] = currentValue; return newTotal; }, []); console.log(groupBy('Phase')); // => [{ Phase: "Phase 1", Value: 50 },{ Phase: "Phase 2", Value: 130 }] console.log(groupBy('Step')); // => [{ Step: "Step 1", Value: 70 },{ Step: "Step 2", Value: 110 }]


关键步骤 Ex 的错误输出:groupBy('Step')
是的,我认为您必须先对其进行排序: arr.sort((a, b) => a[key] - b[key]).reduce ... 我更新了我的答案
我的坏:sort((a, b) => a[key].localeCompare(b[key]))
J
Julian

让我们在重用已经编写的代码(即下划线)的同时充分回答原始问题。如果您将其超过 100 个功能结合起来,您可以使用 Underscore 做更多的事情。以下解决方案证明了这一点。

第 1 步:通过属性的任意组合对数组中的对象进行分组。这使用了 _.groupBy 接受返回对象组的函数这一事实。它还使用 _.chain_.pick_.values_.join_.value。请注意,此处并非严格需要 _.value,因为链式值在用作属性名称时会自动展开。我将它包括在内是为了防止混淆,以防有人试图在不发生自动展开的上下文中编写类似的代码。

// Given an object, return a string naming the group it belongs to.
function category(obj) {
    return _.chain(obj).pick(propertyNames).values().join(' ').value();
}

// Perform the grouping.
const intermediate = _.groupBy(arrayOfObjects, category);

给定原始问题中的 arrayOfObjects 并将 propertyNames 设置为 ['Phase', 'Step']intermediate 将获得以下值:

{
    "Phase 1 Step 1": [
        { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
        { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }
    ],
    "Phase 1 Step 2": [
        { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
        { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }
    ],
    "Phase 2 Step 1": [
        { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
        { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }
    ],
    "Phase 2 Step 2": [
        { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
        { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
    ]
}

第 2 步:将每个组缩减为单个平面对象,并将结果以数组的形式返回。除了我们之前看到的函数之外,以下代码还使用了 _.pluck_.first_.pick_.extend_.reduce_.map。在这种情况下,_.first 保证返回一个对象,因为 _.groupBy 不会产生空组。在这种情况下,_.value 是必需的。

// Sum two numbers, even if they are contained in strings.
const addNumeric = (a, b) => +a + +b;

// Given a `group` of objects, return a flat object with their common
// properties and the sum of the property with name `aggregateProperty`.
function summarize(group) {
    const valuesToSum = _.pluck(group, aggregateProperty);
    return _.chain(group).first().pick(propertyNames).extend({
        [aggregateProperty]: _.reduce(valuesToSum, addNumeric)
    }).value();
}

// Get an array with all the computed aggregates.
const result = _.map(intermediate, summarize);

鉴于我们之前获得的 intermediate 并将 aggregateProperty 设置为 Value,我们得到了询问者所需的 result

[
    { 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 }
]

我们可以将所有这些放在一个以 arrayOfObjectspropertyNamesaggregateProperty 作为参数的函数中。请注意,arrayOfObjects 实际上也可以是带有字符串键的普通对象,因为 _.groupBy 两者都接受。为此,我将 arrayOfObjects 重命名为 collection

function aggregate(collection, propertyNames, aggregateProperty) {
    function category(obj) {
        return _.chain(obj).pick(propertyNames).values().join(' ');
    }
    const addNumeric = (a, b) => +a + +b;
    function summarize(group) {
        const valuesToSum = _.pluck(group, aggregateProperty);
        return _.chain(group).first().pick(propertyNames).extend({
            [aggregateProperty]: _.reduce(valuesToSum, addNumeric)
        }).value();
    }
    return _.chain(collection).groupBy(category).map(summarize).value();
}

aggregate(arrayOfObjects, ['Phase', 'Step'], 'Value') 现在将再次给我们相同的 result

我们可以更进一步,使调用者能够计算每个组中值的任何统计信息。我们可以这样做并且还允许调用者将任意属性添加到每个组的摘要中。我们可以在使我们的代码更短的同时做到这一切。我们将 aggregateProperty 参数替换为 iteratee 参数并将其直接传递给 _.reduce

function aggregate(collection, propertyNames, iteratee) {
    function category(obj) {
        return _.chain(obj).pick(propertyNames).values().join(' ');
    }
    function summarize(group) {
        return _.chain(group).first().pick(propertyNames)
            .extend(_.reduce(group, iteratee)).value();
    }
    return _.chain(collection).groupBy(category).map(summarize).value();
}

实际上,我们将一些责任转移给了调用者;她必须提供一个可以传递给 _.reduceiteratee,以便对 _.reduce 的调用将生成一个具有她想要添加的聚合属性的对象。例如,我们使用以下表达式获得与之前相同的 result

aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
    Value: +memo.Value + +value.Value
}));

对于稍微复杂的 iteratee 的示例,假设我们想要计算每个组的 最大值 Value 而不是总和,并且我们想要添加一个 Tasks 属性列出组中出现的 Task 的所有值。这是我们可以做到这一点的一种方法,使用上面的 aggregate(和 _.union)的最后一个版本:

aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
    Value: Math.max(memo.Value, value.Value),
    Tasks: _.union(memo.Tasks || [memo.Task], [value.Task])
}));

我们得到以下结果:

[
    { Phase: "Phase 1", Step: "Step 1", Value: 10, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 1", Step: "Step 2", Value: 20, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 2", Step: "Step 1", Value: 30, Tasks: [ "Task 1", "Task 2" ] },
    { Phase: "Phase 2", Step: "Step 2", Value: 40, Tasks: [ "Task 1", "Task 2" ] }
]

感谢 @much2learn,他还发布了一个可以处理任意归约函数的 answer。我又写了几个 SO 答案,展示了如何通过组合多个下划线函数来实现复杂的事情:

https://stackoverflow.com/a/64938636/1166087

https://stackoverflow.com/a/64094738/1166087

https://stackoverflow.com/a/63625129/1166087

https://stackoverflow.com/a/63088916/1166087


J
Jean-Philippe

Array.prototype.groupBy = function (groupingKeyFn) { if (typeof groupingKeyFn !== 'function') { throw new Error("groupBy 将函数作为唯一参数"); } return this.reduce((result, item) => { let key = groupingKeyFn(item); if (!result[key]) result[key] = []; result[key].push(item); 返回结果; }, {}); } var a = [ {类型:“视频”,名称:“a”},{类型:“图像”,名称:“b”},{类型:“视频”,名称:“c”},{类型: "blog", name: "d"}, {type: "video", name: "e"}, ] console.log(a.groupBy((item) => item.type));


E
Error404

我会检查 declarative-js groupBy 它似乎完全符合您的要求。也是:

非常高性能(性能基准)

用打字稿写的,所以所有的打字都包括在内。

不强制使用第 3 方类似数组的对象。

import { Reducers } from 'declarative-js';
import groupBy = Reducers.groupBy;
import Map = Reducers.Map;

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

data.reduce(groupBy(element=> element.Step), Map());
data.reduce(groupBy('Step'), Map());

R
Redu

让我们生成一个通用的 Array.prototype.groupBy() 工具。只是为了多样化,让我们使用 ES6 奇特的扩展运算符在递归方法上进行一些 Haskellesque 模式匹配。另外,让我们的 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);返回 xs.length ?迭代(xs,++i,r):r; }(这个); }; var arr = [0,1,2,3,4,5,6,7,8,9], res = arr.groupBy(e => e < 5);控制台.log(res);


T
Telho

只是为了补充 Scott Sauyet 的 answer,有些人在评论中询问如何使用他的函数对 value1、value2 等进行分组,而不是只对一个值进行分组。

只需编辑他的 sum 函数:

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

H
Hasan Fathi

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

希望这可以帮助...