Metode yang paling efisien untuk dikelompokkan berdasarkan berbagai objek

507

Apa cara paling efisien untuk mengelompokkan objek dalam array?

Misalnya, diberikan array objek ini:

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

Saya menampilkan informasi ini dalam sebuah tabel. Saya ingin mengelompokkan berdasarkan metode yang berbeda, tetapi saya ingin menjumlahkan nilainya.

Saya menggunakan Underscore.js untuk fungsi groupby, yang membantu, tetapi tidak melakukan seluruh trik, karena saya tidak ingin mereka "berpisah" tetapi "digabung", lebih seperti group bymetode SQL .

Apa yang saya cari akan dapat total nilai spesifik (jika diminta).

Jadi jika saya melakukan groupby Phase, saya ingin menerima:

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

Dan jika saya melakukan groupy Phase/ Step, saya akan menerima:

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

Apakah ada skrip yang membantu untuk ini, atau haruskah saya tetap menggunakan Underscore.js, dan kemudian mengulangi objek yang dihasilkan untuk melakukan total sendiri?

Rail24
sumber

Jawaban:

755

Jika Anda ingin menghindari pustaka eksternal, Anda dapat mengimplementasikan versi vanilla groupBy()seperti ini:

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

Ceasar Bautista
sumber
18
saya akan memodifikasi dengan cara ini: `` `return xs.reduce (function (rv, x) {var v = key instanceof Function? key (x): x [key]; (rv [v] = rv [v] || []) .dorong (x); return rv;}, {}); `` `memungkinkan fungsi callback untuk mengembalikan kriteria penyortiran
y_nk
110
Berikut ini adalah yang menampilkan array dan bukan objek: 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 ({kunci: v, nilai: [x] });} return rv;}, []); }
tomitrescak
24
Hebat, hanya apa yang saya butuhkan. Jika ada orang lain yang membutuhkannya, inilah tanda tangan TypeScript:var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Michael Sandino
4
Untuk apa nilainya, solusi tomitrescak, sementara nyaman, secara signifikan kurang efisien, karena find () mungkin O (n). Solusi dalam jawabannya adalah O (n), dari pengurangan (penugasan objek adalah O (1), seperti halnya push), sedangkan komentarnya adalah O (n) * O (n) atau O (n ^ 2) atau pada setidaknya O (nlgn)
narthur157
21
Jika ada yang tertarik, saya membuat versi yang lebih mudah dibaca dan beranotasi dari fungsi ini dan memasukkannya ke dalam inti: gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d Saya merasa terbantu untuk memahami apa yang sebenarnya terjadi di sini.
Robmathers
228

Menggunakan objek Peta 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"}]
    
    

Tentang Peta: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

mortb
sumber
@ portb, bagaimana cara mendapatkannya tanpa memanggil get()metode? yang saya ingin output ditampilkan tanpa melewati tombol
Fai Zal Dong
@FaiZalDong: Saya tidak yakin apa yang terbaik untuk kasus Anda? Jika saya menulis console.log(grouped.entries());dalam contoh jsfiddle ia mengembalikan iterable yang berperilaku seperti array kunci + nilai. Bisakah Anda mencobanya dan melihat apakah itu membantu?
mortb
7
Anda juga dapat mencobaconsole.log(Array.from(grouped));
mortb
Saya suka jawaban ini, sangat fleksibel
benshabatnoam
untuk melihat jumlah elemen dalam grup:Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
Ahmet Şimşek
105

dengan ES6:

const groupBy = (items, key) => items.reduce(
  (result, item) => ({
    ...result,
    [item[key]]: [
      ...(result[item[key]] || []),
      item,
    ],
  }), 
  {},
);
Joseph Nields
sumber
3
Dibutuhkan sedikit untuk membiasakan diri, tetapi begitu juga sebagian besar templat C ++ juga
Levi Haskell
2
Saya menghancurkan otak saya dan masih gagal memahami bagaimana cara kerjanya mulai dari ...result. Sekarang saya tidak bisa tidur karena itu.
8
Elegan, tapi sangat lambat pada array yang lebih besar!
infinity1975
4
lol apa ini
Nino Škopac
1
solusi mudah. Terima kasih
Ezequiel Tavares
59

Meskipun jawaban LINQ menarik, itu juga cukup berat. Pendekatan saya agak berbeda:

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

Anda dapat melihatnya beraksi di JSBin .

Saya tidak melihat apa pun di Underscore yang melakukan apa has, meskipun saya mungkin melewatkannya. Ini hampir sama dengan _.contains, tetapi menggunakan _.isEqualdaripada ===untuk perbandingan. Selain itu, sisanya adalah masalah khusus, meskipun dengan upaya untuk menjadi generik.

Sekarang DataGrouper.sum(data, ["Phase"])kembali

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

Dan DataGrouper.sum(data, ["Phase", "Step"])kembali

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

Tetapi sumhanya satu fungsi potensial di sini. Anda dapat mendaftarkan orang lain sesuka Anda:

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

dan sekarang DataGrouper.max(data, ["Phase", "Step"])akan kembali

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

atau jika Anda mendaftarkan ini:

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

maka menelepon DataGrouper.tasks(data, ["Phase", "Step"])akan membuat Anda

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

DataGrouperitu sendiri adalah suatu fungsi. Anda dapat menyebutnya dengan data Anda dan daftar properti yang ingin Anda kelompokkan. Ini mengembalikan array yang elemennya adalah objek dengan dua properti: keyadalah kumpulan properti yang dikelompokkan, valsadalah array objek yang berisi properti yang tersisa yang tidak ada dalam kunci. Sebagai contoh, DataGrouper(data, ["Phase", "Step"])akan menghasilkan:

[
    {
        "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.registermenerima fungsi dan membuat fungsi baru yang menerima data awal dan properti untuk dikelompokkan berdasarkan. Fungsi baru ini kemudian mengambil format output seperti di atas dan menjalankan fungsi Anda terhadap masing-masing, mengembalikan array baru. Fungsi yang dihasilkan disimpan sebagai properti DataGroupersesuai dengan nama yang Anda berikan dan juga dikembalikan jika Anda hanya ingin referensi lokal.

Nah itu banyak penjelasan. Kode ini cukup mudah, saya harap!

Scott Sauyet
sumber
Hai .. Dapatkah Anda melihat Anda mengelompokkan berdasarkan dan menjumlahkan hanya dengan sebuah nilai, tetapi jika saya ingin menjumlahkan dengan value1 dan valu2 dan value3 ... Anda punya solusi?
SAMUEL OSPINA
@SAMUELOSPINA apakah Anda pernah menemukan cara untuk melakukan ini?
howMuchCheeseIsTooMuchCheese
50

Saya akan memeriksa grup lodash oleh . Sepertinya ini melakukan persis apa yang Anda cari. Ini juga sangat ringan dan sangat sederhana.

Contoh biola: https://jsfiddle.net/r7szvt5k/

Asalkan nama array Anda adalah arrgroupBy dengan lodash hanya:

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
Jmarceli
sumber
1
Bukankah jawaban ini bermasalah? Ada beberapa cara di mana hasil Lodash _.groupBy tidak dalam format hasil permintaan OP. (1) Hasilnya bukan array. (2) "Nilai" telah menjadi "kunci" dalam hasil objek lodash.
mg1075
44

Ini mungkin lebih mudah dilakukan dengan linq.js, yang dimaksudkan sebagai implementasi sebenarnya dari LINQ dalam 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();

hasil:

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

Atau, lebih sederhana menggunakan penyeleksi berbasis string ( DEMO ):

linq.GroupBy("$.Phase", "",
    "k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
mellamokb
sumber
dapatkah kita menggunakan beberapa properti saat mengelompokkan di sini:GroupBy(function(x){ return x.Phase; })
Amit
38

Anda dapat membuat ES6 Mapdari array.reduce().

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

Ini memiliki beberapa keunggulan dibandingkan solusi lain:

  • Itu tidak memerlukan perpustakaan apa pun (tidak seperti misalnya _.groupBy())
  • Anda mendapatkan JavaScript Mapdaripada objek (mis. Dikembalikan oleh _.groupBy()). Ini memiliki banyak manfaat , termasuk:
    • ia mengingat urutan item pertama kali ditambahkan,
    • kunci dapat berupa tipe apa saja dan bukan hanya string.
  • A Mapadalah hasil yang lebih berguna daripada array. Tetapi jika Anda menginginkan array array, Anda dapat memanggil Array.from(groupedMap.entries())(untuk array [key, group array]berpasangan) atau Array.from(groupedMap.values())(untuk array array sederhana).
  • Ini cukup fleksibel; sering kali, apa pun yang Anda rencanakan untuk dilakukan selanjutnya dengan peta ini dapat dilakukan secara langsung sebagai bagian dari pengurangan.

Sebagai contoh dari poin terakhir, bayangkan saya memiliki array objek yang ingin saya gabungkan (dangkal) oleh id, seperti ini:

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

Untuk melakukan ini, saya biasanya memulai dengan mengelompokkan berdasarkan id, dan kemudian menggabungkan setiap array yang dihasilkan. Sebagai gantinya, Anda bisa melakukan penggabungan langsung di reduce():

const mergedArray = Array.from(
    objsToMerge.reduce(
        (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
        new Map()
    ).values()
);
Arthur Tacca
sumber
1
Saya tidak tahu mengapa ini tidak memiliki lebih banyak suara. Ini ringkas, mudah dibaca (untuk saya) dan terlihat efisien. Itu tidak terbang di IE11 , tetapi retrofit tidak terlalu keras ( a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()), kurang-lebih)
unbob
24
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

Dari: http://underscorejs.org/#groupBy

Julio Marins
sumber
18

Anda dapat melakukannya dengan pustaka 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]);

Coba contoh ini di jsFiddle .

BTW: Pada array besar (100000 catatan dan lebih banyak lagi) Alasql lebih cepat daripada Linq. Lihat tes di jsPref .

Komentar:

  • Di sini saya meletakkan Nilai dalam kurung siku, karena VALUE adalah kata kunci dalam SQL
  • Saya harus menggunakan fungsi CAST () untuk mengkonversi Nilai string ke tipe angka.
agershun
sumber
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]
        };
    });
};
cezarypiatek
sumber
15

MDN memiliki contoh ini dalam Array.reduce()dokumentasi mereka .

// 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 }] 
// }
HoppyKamper
sumber
14

Meskipun pertanyaan memiliki beberapa jawaban dan jawaban terlihat sedikit lebih rumit, saya sarankan untuk menggunakan Javascript vanilla untuk grup-by dengan bersarang (jika perlu) 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; }

Nina Scholz
sumber
9

Tanpa mutasi:

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"]}
Memberkati
sumber
8

Solusi ini mengambil fungsi sembarang (bukan kunci) sehingga lebih fleksibel daripada solusi di atas, dan memungkinkan fungsi panah , yang mirip dengan ekspresi lambda yang digunakan dalam LINQ :

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

CATATAN: apakah Anda ingin memperpanjang Arrayprototipe terserah Anda.

Contoh yang didukung di sebagian besar browser:

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

Contoh menggunakan fungsi panah (ES6):

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

Kedua contoh di atas kembali:

{
  "1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
  "2": [{"a": 2, "d": "d"}]
}
Diego
sumber
Saya menyukai banyak solusi ES6. Hanya sedikit pelapukan tanpa memperluas prototipe Array:let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
caneta
8

Saya ingin menyarankan pendekatan saya. Pertama, pengelompokan dan agregasi terpisah. Mari kita mendeklarasikan fungsi prototipe "grup demi". Dibutuhkan fungsi lain untuk menghasilkan string "hash" untuk setiap elemen array untuk dikelompokkan.

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

ketika pengelompokan selesai Anda dapat mengumpulkan data berapa yang Anda butuhkan, dalam kasus Anda

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

secara umum itu bekerja. saya telah menguji kode ini di konsol chrome. dan merasa bebas untuk meningkatkan dan menemukan kesalahan;)

Anton
sumber
Terima kasih! Saya suka pendekatannya, dan cocok dengan kebutuhan saya dengan sempurna (saya tidak perlu mengagregasi).
aberaud
Saya pikir Anda ingin mengubah baris Anda di tempat (): map[_hash(key)].key = key;menjadi map[_hash(key)].key = _hash(key);.
Scotty.NET
6

Bayangkan Anda memiliki sesuatu seperti ini:

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

Dengan melakukan ini: const categories = [...new Set(cars.map((car) => car.cat))]

Anda akan mendapatkan ini: ['sedan','sport']

Penjelasan: 1. Pertama, kami membuat Set baru dengan melewatkan array. Karena Atur hanya memungkinkan nilai unik, semua duplikat akan dihapus.

  1. Sekarang duplikat hilang, kita akan mengubahnya kembali ke array dengan menggunakan operator spread ...

Setel Dokumen: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Referensi / Operator / Spread_syntax

Yago Gehres
sumber
Saya sangat menyukai jawaban Anda, ini yang terpendek, tetapi saya masih tidak mengerti logika, terutama, siapa yang melakukan pengelompokan di sini? apakah itu operator yang menyebar (...)? atau 'Set baru ()'? tolong jelaskan kepada kami ... terima kasih
Ivan
1
1. Pertama, kami membuat Set baru dengan melewatkan array. Karena Atur hanya memungkinkan nilai unik, semua duplikat akan dihapus. 2. Sekarang duplikatnya hilang, kita akan mengubahnya kembali menjadi array dengan menggunakan operator penyebaran ... Set Doc: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Spread Operator: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Yago Gehres
ok, mengerti ... terima kasih atas penjelasannya Pak :)
Ivan
sama-sama!
Yago Gehres
6

Jawaban yang diperiksa - hanya pengelompokan yang dangkal. Sangat bagus untuk memahami pengurangan. Pertanyaan juga memberikan masalah perhitungan agregat tambahan.

Berikut ini adalah REAL GROUP BY untuk Array of Objects oleh beberapa bidang dengan 1) nama kunci dihitung dan 2) solusi lengkap untuk cascading pengelompokan dengan memberikan daftar kunci yang diinginkan dan mengonversi nilai-nilai uniknya ke kunci root seperti SQL GROUP BY tidak.

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

Bermain dengan estKey- Anda dapat mengelompokkan lebih dari satu bidang, menambahkan agregasi tambahan, perhitungan, atau pemrosesan lainnya.

Anda juga dapat mengelompokkan data secara rekursif. Misalnya awalnya dikelompokkan berdasarkan Phase, lalu oleh Stepbidang dan sebagainya. Selain itu meniupkan data sisa lemak.

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

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

Output array yang satu ini.

tomitrescak
sumber
4

Berdasarkan jawaban sebelumnya

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

dan itu sedikit lebih baik untuk melihat dengan sintaks penyebaran objek, jika lingkungan Anda mendukung.

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

Di sini, peredam kami mengambil nilai pengembalian yang dibentuk sebagian (dimulai dengan objek kosong), dan mengembalikan objek yang terdiri dari anggota yang tersebar dari nilai pengembalian sebelumnya, bersama dengan anggota baru yang kuncinya dihitung dari nilai iteree saat ini di propdan yang nilainya adalah daftar semua nilai untuk properti itu bersama dengan nilai saat ini.

Benny Powers
sumber
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>

Jean-Philippe
sumber
3

Inilah solusi yang sulit dan sulit dibaca menggunakan ES6:

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

Bagi mereka bertanya bagaimana ini bahkan bekerja, inilah penjelasan:

  • Dalam keduanya =>Anda memiliki gratisreturn

  • The Array.prototype.reducefungsi memakan waktu sampai 4 parameter. Itu sebabnya parameter kelima ditambahkan sehingga kita dapat memiliki deklarasi variabel murah untuk grup (k) di tingkat deklarasi parameter dengan menggunakan nilai default. (ya, ini sihir)

  • Jika grup kami saat ini tidak ada pada iterasi sebelumnya, kami membuat array kosong baru ((r[k] || (r[k] = []))Ini akan mengevaluasi ekspresi paling kiri, dengan kata lain, array yang ada atau array kosong , ini sebabnya ada segera pushsetelah ekspresi itu, karena bagaimanapun Anda akan mendapatkan sebuah array.

  • Ketika ada return, ,operator koma akan membuang nilai paling kiri, mengembalikan grup sebelumnya yang telah diubah untuk skenario ini.

Versi yang lebih mudah dipahami yang melakukan hal yang sama adalah:

export default (array, key) => 
  array.reduce((previous, currentItem) => {
    const group = currentItem[key];
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {});
darkndream
sumber
2
maukah Anda menjelaskannya sedikit, ini bekerja dengan sempurna
Nuwan Dammika
@NuwanDammika - Di keduanya => Anda memiliki "pengembalian" gratis - Fungsi pengurangan memerlukan hingga 4 parameter. Itu sebabnya parameter kelima ditambahkan sehingga kita dapat memiliki deklarasi variabel murah untuk grup (k). - Jika nilai sebelumnya tidak memiliki grup kami saat ini, kami membuat grup kosong baru ((r [k] || (r [k] = [])) Ini akan mengevaluasi ke ekspresi paling kiri, jika tidak array atau array kosong, inilah sebabnya ada dorongan segera setelah ekspresi itu - Ketika ada pengembalian, operator koma akan membuang nilai paling kiri, mengembalikan grup sebelumnya yang diubah
darkndream
2

Mari kita menghasilkan Array.prototype.groupBy()alat generik . Hanya untuk variasi, mari gunakan ES6 fanciness operator spread untuk beberapa pola Haskellesque yang cocok dengan pendekatan rekursif. Juga mari kita buat kita Array.prototype.groupBy()untuk menerima panggilan balik yang mengambil item ( e) indeks ( i) dan array yang diterapkan ( a) sebagai argumen.

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

Redu
sumber
2

Jawaban Ceasar baik, tetapi hanya berfungsi untuk properti bagian dalam elemen di dalam array (panjang untuk string).

implementasi ini lebih berfungsi seperti: tautan ini

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

semoga ini membantu...

Roey
sumber
2

Dari @mortb, @jmarceli menjawab dan dari pos ini ,

Saya mengambil keuntungan dari JSON.stringify()menjadi identitas untuk NILAI PRIMITIF beberapa kolom grup oleh.

Tanpa pihak ketiga

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

Dengan Lodash pihak ketiga

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);
Pranithan T.
sumber
keyGetter mengembalikan tidak terdefinisi
Asbar Ali
@ AsbarAli Saya menguji cuplikan saya dengan konsol Chrome - Versi 66.0.3359.139 (Pembuatan Resmi) (64-bit). Dan semuanya berjalan dengan baik. Bisakah Anda meletakkan break point debugging dan melihat mengapa keyGetter tidak terdefinisi. Mungkin karena versi peramban.
Pranithan T.
2

reduceVersi versi berbasis ES6 dengan dukungan fungsi iteratee.

Bekerja seperti yang diharapkan jika iterateefungsi tidak disediakan:

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

Dalam konteks pertanyaan 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

Versi ES6 lain yang membalikkan pengelompokan dan menggunakan valuesas keysdan the keyssebagai 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))

Catatan: fungsi diposting dalam bentuk singkat (satu baris) untuk singkatnya dan hanya untuk menghubungkan ide. Anda dapat mengembangkannya dan menambahkan pemeriksaan kesalahan tambahan, dll.

Akrion
sumber
2

Saya akan memeriksa deklaratif-js groupBy tampaknya melakukan persis apa yang Anda cari. Itu juga:

  • sangat performant ( tolok ukur kinerja )
  • ditulis dalam naskah sehingga semua mengetik dimasukkan.
  • Itu tidak memaksakan untuk menggunakan objek seperti array pihak ke-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());
Pasa89
sumber
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'));
Tom Jiang
sumber
1

Ini adalah versi ES6 yang tidak akan merusak anggota null

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

Hanya untuk menambah jawaban Scott Sauyet , beberapa orang bertanya di komentar bagaimana menggunakan fungsinya untuk mengelompokkan nilai1, nilai2, dll., Alih-alih mengelompokkan hanya satu nilai.

Yang diperlukan hanyalah mengedit fungsi penjumlahannya:

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

membiarkan yang utama (DataGrouper) tidak berubah:

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

Dengan fitur sortir

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

Sampel:

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