Phương pháp hiệu quả nhất để nhóm trên một mảng các đối tượng


507

Cách hiệu quả nhất để nhóm các đối tượng trong một mảng là gì?

Ví dụ, đưa ra mảng đối tượng này:

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

Tôi đang hiển thị thông tin này trong một bảng. Tôi muốn nhóm các phương thức khác nhau, nhưng tôi muốn tổng hợp các giá trị.

Tôi đang sử dụng Underscore.js cho chức năng nhóm của mình, điều này rất hữu ích, nhưng không thực hiện được toàn bộ mẹo này, vì tôi không muốn họ chia tách ra nhưng nhưng đã hợp nhất với nhau, giống như group byphương thức SQL .

Những gì tôi đang tìm kiếm sẽ có thể tổng các giá trị cụ thể (nếu được yêu cầu).

Vì vậy, nếu tôi đã làm theo nhóm Phase, tôi muốn nhận:

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

Và nếu tôi đã làm theo nhóm Phase/ Step, tôi sẽ nhận được:

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

Có một kịch bản hữu ích cho việc này hay tôi nên sử dụng Underscore.js và sau đó lặp qua đối tượng kết quả để tự thực hiện tổng số?

Câu trả lời:


755

Nếu bạn muốn tránh các thư viện bên ngoài, bạn có thể triển khai chính xác phiên bản vanilla groupBy()như vậy:

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


18
tôi sẽ sửa đổi theo cách này: `` `return xs.reduce (function (rv, x) {var v = key instanceof Hàm? key (x): x [key]; (rv [v] = rv [v] || []). đẩy (x); return rv;}, {}); `` `cho phép các hàm gọi lại trả về một tiêu chí sắp xếp
y_nk

110
Đây là một kết quả đầu ra mảng và không phải đối tượng: 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);} other {rv.push ({key: v, value: [x] });} trả về rv;}, []); }
tomitrescak

24
Tuyệt, chỉ là những gì tôi cần. Trong trường hợp bất cứ ai khác cần nó, đây là chữ ký TypeScript:var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Michael Sandino

4
Đối với những gì nó có giá trị, giải pháp của tomitrescak, trong khi thuận tiện, lại kém hiệu quả hơn đáng kể, vì find () có lẽ là O (n). Giải pháp trong câu trả lời là O (n), từ giảm (gán đối tượng là O (1), như là đẩy), trong khi nhận xét là O (n) * O (n) hoặc O (n ^ 2) hoặc tại ít nhất O (nlgn)
narthur157

21
Nếu bất cứ ai quan tâm, tôi đã tạo một phiên bản dễ đọc và chú thích hơn của chức năng này và đặt nó trong một ý chính: gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d Tôi thấy nó hữu ích để hiểu những gì thực sự xảy ra ở đây.
kẻ cướp

228

Sử dụng đối tượng Bản đồ ES6:

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

Giới thiệu về Bản đồ: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map


@mortb, làm thế nào để có được nó mà không gọi get()phương thức? Tôi muốn đầu ra được hiển thị mà không cần nhập khóa
Fai Zal Dong

@FaiZalDong: Tôi không chắc điều gì sẽ tốt nhất cho trường hợp của bạn? Nếu tôi viết console.log(grouped.entries());trong ví dụ jsfiddle, nó sẽ trả về một iterable hoạt động giống như một mảng các khóa + giá trị. Bạn có thể thử điều đó và xem nếu nó giúp?
cối

7
Bạn cũng có thể thửconsole.log(Array.from(grouped));
mortb 28/12/17

Tôi thích câu trả lời này, rất linh hoạt
benshabatnoam

để xem số lượng phần tử trong các nhóm:Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
Ahmet imşek

105

với ES6:

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

3
Phải mất một chút để làm quen, nhưng hầu hết các mẫu C ++ cũng vậy
Levi Haskell

2
Tôi vắt óc và vẫn không hiểu nó bắt đầu như thế nào trên thế giới ...result. Bây giờ tôi không thể ngủ được vì điều đó.

8
Thanh lịch, nhưng đau đớn chậm trên mảng lớn hơn!
vô cực1975

4
lol cái gì đây
Nino kopac

1
giải pháp dễ dàng. Cảm ơn
Ezequiel Tavares

59

Mặc dù câu trả lời linq rất thú vị, nhưng nó cũng khá nặng. Cách tiếp cận của tôi có hơi khác:

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

Bạn có thể thấy nó hoạt động trên JSBin .

Tôi đã không thấy bất cứ điều gì trong Underscore làm những gì has, mặc dù tôi có thể thiếu nó. Nó giống như _.contains, nhưng sử dụng _.isEqualchứ không phải ===để so sánh. Ngoài ra, phần còn lại của vấn đề này là dành riêng cho vấn đề, mặc dù với một nỗ lực chung chung.

Bây giờ DataGrouper.sum(data, ["Phase"])trở lại

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

DataGrouper.sum(data, ["Phase", "Step"])trả lại

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

Nhưng sumchỉ có một chức năng tiềm năng ở đây. Bạn có thể đăng ký người khác như bạn muốn:

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

và bây giờ DataGrouper.max(data, ["Phase", "Step"])sẽ trở lại

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

hoặc nếu bạn đã đăng ký này:

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

sau đó gọi DataGrouper.tasks(data, ["Phase", "Step"])sẽ giúp bạn

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

DataGrouperchính nó là một chức năng. Bạn có thể gọi nó với dữ liệu của bạn và một danh sách các thuộc tính bạn muốn nhóm theo. Nó trả về một mảng có các phần tử là đối tượng có hai thuộc tính: keylà tập hợp các thuộc tính được nhóm, valslà một mảng các đối tượng chứa các thuộc tính còn lại không có trong khóa. Ví dụ: DataGrouper(data, ["Phase", "Step"])sẽ mang lại:

[
    {
        "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.registerchấp nhận một chức năng và tạo ra một chức năng mới chấp nhận dữ liệu ban đầu và các thuộc tính để nhóm theo. Hàm mới này sau đó lấy định dạng đầu ra như trên và lần lượt chạy hàm của bạn theo từng hàm, trả về một mảng mới. Hàm được tạo được lưu trữ dưới dạng một thuộc tính DataGroupertheo tên bạn cung cấp và cũng được trả về nếu bạn chỉ muốn tham chiếu cục bộ.

Vâng, đó là rất nhiều lời giải thích. Mã này là hợp lý đơn giản, tôi hy vọng!


Xin chào .. Có thể thấy bạn nhóm theo và tổng hợp chỉ bằng một giá trị, nhưng trong trường hợp tôi muốn tổng theo value1 và valu2 và value3 ... bạn có giải pháp nào không?
SAMUEL OSPINA

@SAMUELOSPINA bạn đã bao giờ tìm ra cách để làm điều này chưa?
howMuchCheeseIsTooMuchCheese

50

Tôi sẽ kiểm tra nhóm lodashBạn dường như làm chính xác những gì bạn đang tìm kiếm. Nó cũng khá nhẹ và thực sự đơn giản.

Ví dụ về Fiddle: https://jsfiddle.net/r7szvt5k/

Với điều kiện tên mảng của bạn là arrnhómBy với lodash chỉ là:

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

1
Không phải câu trả lời này có vấn đề sao? Có nhiều cách trong đó kết quả lodash _.groupBy không theo định dạng của kết quả mà OP đang yêu cầu. (1) Kết quả không phải là một mảng. (2) "Giá trị" đã trở thành "chìa khóa" trong kết quả (các) đối tượng lodash.
mg1075

44

Điều này có lẽ dễ thực hiện hơn linq.js, được dự định là một triển khai thực sự của LINQ trong 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();

kết quả:

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

Hoặc, đơn giản hơn là sử dụng các bộ chọn dựa trên chuỗi ( DEMO ):

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

chúng ta có thể sử dụng nhiều thuộc tính trong khi nhóm ở đây:GroupBy(function(x){ return x.Phase; })
Amit

38

Bạn có thể xây dựng ES6 Maptừ array.reduce().

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

Điều này có một vài lợi thế so với các giải pháp khác:

  • Nó không yêu cầu bất kỳ thư viện (không giống như _.groupBy())
  • Bạn nhận được một JavaScript Mapchứ không phải là một đối tượng (ví dụ như được trả về bởi _.groupBy()). Điều này có rất nhiều lợi ích , bao gồm:
    • nó nhớ thứ tự các mục đầu tiên được thêm vào,
    • Các khóa có thể là bất kỳ loại nào thay vì chỉ là chuỗi.
  • A Maplà một kết quả hữu ích hơn mà một mảng các mảng. Nhưng nếu bạn muốn có một mảng các mảng, thì bạn có thể gọi Array.from(groupedMap.entries())(cho một mảng các [key, group array]cặp) hoặc Array.from(groupedMap.values())(cho một mảng đơn giản của mảng).
  • Nó khá linh hoạt; thông thường, bất cứ điều gì bạn định làm tiếp theo với bản đồ này đều có thể được thực hiện trực tiếp như là một phần của việc giảm.

Như một ví dụ về điểm cuối cùng, hãy tưởng tượng tôi có một mảng các đối tượng mà tôi muốn thực hiện hợp nhất (nông) trên id, như thế này:

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

Để làm điều này, tôi thường bắt đầu bằng cách nhóm theo id, và sau đó hợp nhất từng mảng kết quả. Thay vào đó, bạn có thể thực hiện hợp nhất trực tiếp trong reduce():

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

1
Tôi không biết tại sao điều này không có nhiều phiếu bầu hơn. Nó ngắn gọn, dễ đọc (với tôi) và có vẻ hiệu quả. Nó không bay trên IE11 , nhưng trang bị thêm không quá khó ( a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()), xấp xỉ)
unbob


18

Bạn có thể làm điều đó với thư viện JavaScript Alasql :

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

Hãy thử ví dụ này tại jsFiddle .

BTW: Trên các mảng lớn (100000 bản ghi và hơn thế nữa) Alasql nhanh hơn tham Linq. Xem thử nghiệm tại jsPref .

Bình luận:

  • Ở đây tôi đặt Giá trị trong ngoặc vuông, vì VALUE là một từ khóa trong SQL
  • Tôi phải sử dụng hàm CAST () để chuyển đổi Giá trị chuỗi thành loại số.

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

15

MDN có ví dụ này trong Array.reduce()tài liệu của họ .

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

14

Mặc dù câu hỏi có một số câu trả lời và câu trả lời có vẻ hơi phức tạp, tôi đề nghị sử dụng vanilla Javascript cho nhóm theo nhóm với nhau (nếu cần) 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'));
.as-console-wrapper { max-height: 100% !important; top: 0; }


9

Không có đột biến:

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

8

Giải pháp này có bất kỳ chức năng tùy ý nào (không phải là khóa) vì vậy nó linh hoạt hơn các giải pháp ở trên và cho phép các hàm mũi tên , tương tự như các biểu thức lambda được sử dụng trong LINQ :

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

LƯU Ý: bạn có muốn gia hạn Arraynguyên mẫu hay không là tùy thuộc vào bạn.

Ví dụ được hỗ trợ trong hầu hết các trình duyệt:

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

Ví dụ sử dụng các hàm mũi tên (ES6):

[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)

Cả hai ví dụ trên đều trả về:

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}

Tôi thích rất nhiều giải pháp ES6. Chỉ cần một chút bán kết mà không mở rộng nguyên mẫu Array:let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
caneta

8

Tôi muốn đề xuất cách tiếp cận của tôi. Đầu tiên, phân nhóm và tổng hợp riêng biệt. Cho phép khai báo nguyên mẫu "nhóm theo" chức năng. Nó nhận một hàm khác để tạo chuỗi "băm" cho mỗi phần tử mảng để nhóm theo.

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

khi nhóm xong, bạn có thể tổng hợp dữ liệu theo cách bạn cần, trong trường hợp của bạn

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

nói chung nó hoạt động. tôi đã kiểm tra mã này trong giao diện điều khiển chrome. và cảm thấy tự do để cải thiện và tìm ra sai lầm;)


Cảm ơn ! Yêu cách tiếp cận và hoàn toàn phù hợp với nhu cầu của tôi (tôi không cần tổng hợp).
aberaud

Tôi nghĩ rằng bạn muốn thay đổi dòng của bạn trong put (): map[_hash(key)].key = key;to map[_hash(key)].key = _hash(key);.
Scotty.NET 3/03/2015

6

Hãy tưởng tượng rằng bạn có một cái gì đó như thế này:

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

Bằng cách này: const categories = [...new Set(cars.map((car) => car.cat))]

Bạn sẽ nhận được điều này: ['sedan','sport']

Giải thích: 1. Đầu tiên, chúng tôi đang tạo một Set mới bằng cách chuyển một mảng. Bởi vì Set chỉ cho phép các giá trị duy nhất, tất cả các bản sao sẽ bị xóa.

  1. Bây giờ các bản sao đã biến mất, chúng tôi sẽ chuyển đổi nó trở lại một mảng bằng cách sử dụng toán tử trải ...

Đặt tài liệu: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/set Toán tử lan truyềnDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Tham khảo / Toán tử / Spread_syntax


Tôi rất thích câu trả lời của bạn, đó là câu trả lời ngắn nhất, nhưng tôi vẫn không hiểu logic, đặc biệt, ai là người nhóm ở đây? nó có phải là toán tử lây lan (...) không? hoặc 'Bộ mới ()'? vui lòng giải thích cho chúng tôi ... cảm ơn bạn
Ivan

1
1. Đầu tiên, chúng tôi đang tạo một Set mới bằng cách truyền một mảng. Bởi vì Set chỉ cho phép các giá trị duy nhất, tất cả các bản sao sẽ bị xóa. 2. Bây giờ các bản sao đã mất hết, chúng ta sẽ chuyển đổi nó trở lại một mảng bằng cách sử dụng các nhà điều hành lây lan ... Set Đốc: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... Spread Toán tử: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
mẹo

ok, hiểu rồi ... cảm ơn bạn đã giải thích thưa ông :)
Ivan

không có gì!
Yago Gehres

6

Kiểm tra câu trả lời - chỉ cần nhóm nông. Nó khá hay để hiểu giảm. Câu hỏi cũng cung cấp các vấn đề tính toán tổng hợp bổ sung.

Dưới đây là một NHÓM THỰC SỰ BỞI cho Mảng đối tượng theo một số trường với 1) tên khóa được tính toán và 2) giải pháp hoàn chỉnh cho việc phân nhóm bằng cách cung cấp danh sách các khóa mong muốn và chuyển đổi các giá trị duy nhất của nó thành khóa gốc như SQL GROUP BỞI

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

Chơi với estKey- bạn có thể nhóm theo nhiều hơn một trường, thêm tập hợp bổ sung, tính toán hoặc xử lý khác.

Ngoài ra, bạn có thể nhóm dữ liệu đệ quy. Ví dụ ban đầu nhóm theo Phase, sau đó theo Steplĩnh vực và như vậy. Ngoài ra, thổi bay dữ liệu nghỉ ngơi chất béo.

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 groupped 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;

/**
 * Recoursive groupping 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 ) );


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

Điều này đầu ra mảng.


4

Dựa trên các câu trả lời trước

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

và sẽ tốt hơn một chút khi xem xét với cú pháp trải rộng đối tượng, nếu môi trường của bạn hỗ trợ.

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

Ở đây, bộ giảm tốc của chúng tôi lấy giá trị trả về được hình thành một phần (bắt đầu bằng một đối tượng trống) và trả về một đối tượng bao gồm các thành viên dàn trải của giá trị trả về trước đó, cùng với một thành viên mới có khóa được tính từ giá trị của iteree hiện tại propvà có giá trị là danh sách tất cả các giá trị cho prop đó cùng với giá trị hiện tại.


3

Array.prototype.groupBy = function (groupingKeyFn) {
    if (typeof groupingKeyFn !== 'function') {
        throw new Error("groupBy take a function as only parameter");
    }
    return this.reduce((result, item) => {
        let key = groupingKeyFn(item);
        if (!result[key])
            result[key] = [];
        result[key].push(item);
        return result;
    }, {});
}

var a = [
	{type: "video", name: "a"},
  {type: "image", name: "b"},
  {type: "video", name: "c"},
  {type: "blog", name: "d"},
  {type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


3

Đây là một giải pháp khó đọc, khó đọc bằng ES6:

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

Đối với những người hỏi làm thế nào điều này thậm chí hoạt động, đây là một lời giải thích:

  • Trong cả hai =>bạn có một miễn phíreturn

  • Các Array.prototype.reducechức năng phải mất đến 4 thông số. Đó là lý do tại sao một tham số thứ năm đang được thêm vào để chúng ta có thể có một khai báo biến giá rẻ cho nhóm (k) ở mức khai báo tham số bằng cách sử dụng giá trị mặc định. (vâng, đây là phép thuật)

  • Nếu nhóm hiện tại của chúng tôi không tồn tại ở lần lặp trước, chúng tôi sẽ tạo một mảng trống mới ((r[k] || (r[k] = []))Điều này sẽ đánh giá biểu thức ngoài cùng bên trái, nói cách khác, một mảng hiện có hoặc một mảng trống , đây là lý do tại sao có ngay pushsau biểu thức đó, bởi vì một trong hai cách bạn sẽ nhận được một mảng.

  • Khi có một return, ,toán tử dấu phẩy sẽ loại bỏ giá trị ngoài cùng bên trái, trả về nhóm đã điều chỉnh trước đó cho kịch bản này.

Một phiên bản dễ hiểu hơn làm tương tự là:

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

2
bạn có quan tâm để giải thích điều này một chút không, nó hoạt động hoàn hảo
Nuwan Dammika

@NuwanDammika - Trong cả hai => bạn có "trả lại" miễn phí - Hàm giảm có tối đa 4 tham số. Đó là lý do tại sao một tham số thứ năm đang được thêm vào để chúng ta có thể có một khai báo biến giá rẻ cho nhóm (k). - Nếu giá trị trước đó không có nhóm hiện tại của chúng tôi, chúng tôi sẽ tạo một nhóm trống mới ((r [k] || (r [k] = [])) Điều này sẽ đánh giá biểu thức ngoài cùng bên trái, nếu không thì một mảng hoặc một mảng trống, đây là lý do tại sao có sự thúc đẩy ngay lập tức sau biểu thức đó. - Khi có sự trở lại, toán tử dấu phẩy sẽ loại bỏ giá trị ngoài cùng bên trái, trả về nhóm đã điều chỉnh trước đó
darkndream

2

Cho phép tạo ra một Array.prototype.groupBy()công cụ chung . Chỉ dành cho sự đa dạng, hãy sử dụng ES6 fanciness toán tử trải rộng cho một số mẫu Haskellesque phù hợp với phương pháp đệ quy. Ngoài ra, hãy làm cho chúng ta Array.prototype.groupBy()chấp nhận một cuộc gọi lại lấy mục ( e) chỉ mục ( i) và mảng được áp dụng ( a) làm đối số.

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);
                                     return xs.length ? iterate(xs, ++i, r) : r;
                                   }(this);
                          };

var arr = [0,1,2,3,4,5,6,7,8,9],
    res = arr.groupBy(e => e < 5);
console.log(res);


2

Câu trả lời của Ceasar là tốt, nhưng chỉ hoạt động cho các thuộc tính bên trong của các phần tử bên trong mảng (độ dài trong trường hợp chuỗi).

việc thực hiện này hoạt động giống như: liên kết này

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

hi vọng điêu nay co ich...


2

Từ @mortb, @jmarceli trả lời và từ bài đăng này ,

Tôi tận dụng lợi thế JSON.stringify()để trở thành danh tính cho nhiều cột GIÁ TRỊ CHÍNH XÁC .

Không có bên thứ ba

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
        const key = keyGetter(item);
        if (!map.has(key)) {
            map.set(key, [item]);
        } else {
            map.get(key).push(item);
        }
    });
    return map;
}

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));

console.log(grouped);

Với Lodash của bên thứ ba

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

let rslt = _.groupBy(pets, pet => JSON.stringify(
 { type: pet.type, age: pet.age }));

console.log(rslt);

keyGetter trả về không xác định
Asbar Ali

@AsbarAli Tôi đã thử đoạn trích của mình với bảng điều khiển của Chrome - Phiên bản 66.0.3359.139 (Bản dựng chính thức) (64-bit). Và mọi thứ chạy tốt. Bạn có thể vui lòng đặt điểm dừng gỡ lỗi và xem tại sao keyGetter không được xác định. Có lẽ, vì phiên bản trình duyệt.
Pranithan T.

2

reducePhiên bản dựa trên ES6 với sự hỗ trợ cho chức năng iteratee.

Hoạt động như mong đợi nếu iterateechức năng không được cung cấp:

const data = [{id: 1, score: 2},{id: 1, score: 3},{id: 2, score: 2},{id: 2, score: 4}]

const group = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});

const groupBy = (arr, k, fn = () => true) => 
  arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});

console.log(group(data, 'id'))     // grouping via `reduce`
console.log(groupBy(data, 'id'))   // same result if `fn` is omitted
console.log(groupBy(data, 'score', x => x > 2 )) // group with the iteratee

Trong bối cảnh của câu hỏi OP:

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

const groupBy = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupWith = (arr, k, fn = () => true) => 
  arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});

console.log(groupBy(data, 'Phase'))
console.log(groupWith(data, 'Value', x => x > 30 ))  // group by `Value` > 30

Một phiên bản ES6 khác đảo ngược việc phân nhóm và sử dụng valuesnhư keyskeysnhư grouped values:

const data = [{A: "1"}, {B: "10"}, {C: "10"}]

const groupKeys = arr => 
  arr.reduce((r,c) => (Object.keys(c).map(x => r[c[x]] = [...r[c[x]] || [], x]),r),{});

console.log(groupKeys(data))

Lưu ý: các chức năng được đăng ở dạng ngắn (một dòng) để đơn giản và chỉ liên quan đến ý tưởng. Bạn có thể mở rộng chúng và thêm kiểm tra lỗi, v.v.


2

Tôi sẽ kiểm tra khai báo-js groupBy nó dường như làm chính xác những gì bạn đang tìm kiếm. Nó cũng là:

  • rất hiệu suất ( điểm chuẩn hiệu suất )
  • được viết trong bản đánh máy để tất cả các kiểu chữ được bao gồm.
  • Nó không bắt buộc sử dụng các đối tượng giống như mảng của bên thứ 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());

1
let groupbyKeys = function(arr, ...keys) {
  let keysFieldName = keys.join();
  return arr.map(ele => {
    let keysField = {};
    keysField[keysFieldName] = keys.reduce((keyValue, key) => {
      return keyValue + ele[key]
    }, "");
    return Object.assign({}, ele, keysField);
  }).reduce((groups, ele) => {
    (groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
      .push([ele].map(e => {
        if (keys.length > 1) {
          delete e[keysFieldName];
        }
        return e;
    })[0]);
    return groups;
  }, {});
};

console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));

1

Đây là phiên bản ES6 sẽ không phá vỡ thành viên null

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}

1

Chỉ để thêm vào câu trả lời của Scott Sauyet , một số người đã hỏi trong các ý kiến ​​về cách sử dụng chức năng của anh ấy để nhóm giá trị1, value2, v.v., thay vì chỉ nhóm một giá trị.

Tất cả chỉ cần chỉnh sửa hàm tổng của anh ta:

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

giữ nguyên cái chính (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;
}());

1

Với tính năng sắp xếp

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

Mẫu vật:

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')));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.