Bir dizi nesneyi gruplamak için en etkili yöntem


507

Bir dizideki nesneleri gruplandırmanın en etkili yolu nedir?

Örneğin, bu nesne dizisi göz önüne alındığında:

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

Bu bilgiyi bir tabloda görüntülüyorum. Farklı yöntemlerle gruplandırmak istiyorum, ama değerleri toplamak istiyorum.

Underscore.js, yararlı, ancak tüm hile yapmaz groupby işlevi için kullanıyorum, çünkü daha "SQL group byyöntemi gibi" bölünmüş "ama" birleştirilmiş "istemiyorum .

Aradığım şey, belirli değerleri (istenirse) toplam edebilecektir.

Eğer groupby yapsaydım Phase, almak istiyorum:

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

Ve eğer gruplanmış Phase/ Stepolsaydım,

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

Bunun için yararlı bir komut dosyası var mı, yoksa Underscore.js'yi kullanmaya devam etmeli ve toplamları kendim yapmak için ortaya çıkan nesne arasında döngü yapmalıyım?

Yanıtlar:


755

Harici kütüphanelerden kaçınmak istiyorsanız, böyle bir vanilya sürümünü kısaca uygulayabilirsiniz 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"]}


18
bu şekilde değiştiririm: `` return xs.reduce (function (rv, x) {var v = key instanceof Function? key (x): x [key]; (rv [v] = rv [v] || []). itme (x); dönüş rv;}, {}); `` Geri arama işlevlerinin bir sıralama ölçütü döndürmesine izin verme
y_nk

110
Nesne değil, dizi çıktısı veren dizge: groupByArray (xs, key) {return xs.reduce (function (rv, x) {let v = key instanceof İşlev? Tuşu (x): x [key]; let el = rv .find ((r) => r && r.key === v); eğer (el) {el.values.push (x);} else {rv.push ({anahtar: v, değerler: [x] });} dönüş rv;}, []); }
tomitrescak

24
Harika, tam ihtiyacım olan şey. Başka birinin buna ihtiyacı varsa, işte TypeScript imzası:var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Michael Sandino

4
Değeri ne olursa olsun, tomitrescak'ın çözümü, uygun olsa da, find () muhtemelen O (n) olduğu için önemli ölçüde daha az verimlidir. Yanıttaki çözüm, indirgeme (itme gibi nesne ataması O (1) 'dir, O (n) * ise yorum O (n) * O (n) veya O (n ^ 2) veya en az O (nlgn)
narthur157

21
Herkes ilgileniyorsa, bu işlevin daha okunabilir ve açıklamalı bir versiyonunu yaptım ve bir özete koydum: gist.github.com/robmathers/1830ce09695f759bf2c4df15c29dd22d Burada gerçekten ne olduğunu anlamak için yararlı buldum.
robmathers

228

ES6 Harita nesnesini kullanma:

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

Harita Hakkında: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map


@mortb, get()yöntemi çağırmadan nasıl alınır ? ki çıktıyı anahtarı geçmeden göstermek istiyorum
Fai Zal Dong

@FaiZalDong: Davanız için en iyisinin ne olduğundan emin değilim? Eğer console.log(grouped.entries());jsfiddle örneğinde yazarsanız, anahtarlar + değerler dizisi gibi davranan bir yinelenebilir döndürür. Bunu deneyebilir ve yardımcı olup olmadığını görebilir misin?
Aralık'ta ipotek

7
Ayrıca deneyebilirsinizconsole.log(Array.from(grouped));
mortb

Ben bu cevabı çok esnek seviyorum
benshabatnoam

gruplardaki element sayısını görmek için:Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
Ahmet Şimşek

105

ES6 ile:

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

3
Alışmak biraz zaman alıyor, ancak C ++ şablonlarının çoğu da öyle
Levi Haskell

2
Beynimi sardım ve hala dünyada nasıl çalıştığını anlayamadım ...result. Şimdi bu yüzden uyuyamıyorum.

8
Zarif, ancak daha büyük dizilerde acı verici yavaş!
infinity1975

4
lol bu nedir
Nino Škopac

1
kolay çözüm. Teşekkürler
Ezequiel Tavares

59

Linq cevabı ilginç olmasına rağmen, aynı zamanda oldukça ağırdır. Benim yaklaşımım biraz farklı:

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

Görebiliyorsun JSBin üzerinde eylem .

Undercore'da bunu yapan hiçbir şey görmedim has, ancak onu kaçırıyor olabilirim. Oldukça aynı _.contains, ancak karşılaştırmalar _.isEqualyerine kullanır ===. Bunun dışında, genel olmaya çalışılsa da, bunun geri kalanı probleme özgüdür.

Şimdi DataGrouper.sum(data, ["Phase"])geri dönüyor

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

Ve DataGrouper.sum(data, ["Phase", "Step"])geri dönüyor

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

Ancak sumburada sadece bir potansiyel işlev var. Başkalarını istediğiniz şekilde kaydedebilirsiniz:

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

ve şimdi DataGrouper.max(data, ["Phase", "Step"])geri dönecek

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

veya bunu kaydettiyseniz:

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

o zaman aramak DataGrouper.tasks(data, ["Phase", "Step"])seni alacak

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

DataGrouperkendisi bir işlevdir. Verileriniz ve gruplandırmak istediğiniz özelliklerin bir listesi ile arayabilirsiniz. Öğeleri iki özelliğe sahip nesne olan bir dizi döndürür: keygruplandırılmış özelliklerin toplanması vals, anahtarda olmayan kalan özellikleri içeren bir nesne dizisidir. Örneğin, DataGrouper(data, ["Phase", "Step"])verim:

[
    {
        "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.registerbir işlevi kabul eder ve ilk verileri ve gruplanacak özellikleri kabul eden yeni bir işlev oluşturur. Bu yeni işlev daha sonra çıkış biçimini yukarıdaki gibi alır ve işlevinizi sırayla her birine karşı çalıştırır ve yeni bir dizi döndürür. Oluşturulan işlev, DataGroupersağladığınız bir ada göre bir özellik olarak depolanır ve yalnızca yerel bir başvuru istiyorsanız döndürülür.

Bu çok fazla açıklama. Kod oldukça basit, umarım!


Merhaba .. Sadece bir değer ile gruplamak ve toplamı görebiliyorum, ama değer1 ve değer2 ve değer3 toplamı istiyorum durumunda ... u bir çözüm var mı?
SAMUEL OSPINA

@SAMUELOSPINA bunu yapmanın bir yolunu buldunuz mu?
howMuchCheeseIsTooMuchCheese

50

Lodash grubunu kontrol ediyorum.Tam olarak aradığınızı yapıyor gibi görünüyor. Aynı zamanda oldukça hafif ve gerçekten basit.

Keman örneği: https://jsfiddle.net/r7szvt5k/

Dizi adınızın arrgroup olması şartıyla, lodash ile sadece:

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
Bu cevap sorunlu değil mi? Lodash _.groupBy sonucunun OP'nin istediği sonuç biçiminde olmaması için birden çok yol vardır. (1) Sonuç bir dizi değildir. (2) "değer", lodash nesnesi / nesnelerinin sonucundaki "anahtar" olmuştur.
mg1075

44

Bu muhtemelen daha kolay yapılır linq.js LINQ'nun JavaScript'te ( DEMO ) gerçek bir uygulaması olması amaçlanan :

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

sonuç:

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

Veya daha basit bir şekilde dize tabanlı seçicileri ( DEMO) kullanarak ) kullanarak:

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

burada gruplandırırken birden fazla özellik kullanabilir miyiz:GroupBy(function(x){ return x.Phase; })
Amit

38

Bir ES6 inşa edebilirsiniz Mapdan array.reduce().

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

Bunun diğer çözümlere göre birkaç avantajı vardır:

  • Herhangi bir kütüphane gerektirmez (ör. Aksine _.groupBy())
  • MapNesne yerine JavaScript alırsınız (örneğin, tarafından döndürülen şekilde _.groupBy()). Bunun birçok faydası vardır :
    • öğelerin ilk eklenme sırasını hatırlar,
    • anahtarlar sadece dizelerden ziyade herhangi bir tür olabilir.
  • A Map, bir dizi diziden daha kullanışlı bir sonuçtur. Ancak bir dizi dizi istiyorsanız, Array.from(groupedMap.entries())(bir [key, group array]çift dizi için ) veya Array.from(groupedMap.values())(basit bir dizi dizi için ) çağırabilirsiniz .
  • Oldukça esnektir; sık sık, bu harita ile ne yapmayı planlıyorsanız, azaltmanın bir parçası olarak doğrudan yapılabilir.

Son noktaya bir örnek olarak, ben id tarafından bir (sığ) birleştirme yapmak istiyorum nesnelerin bir dizi var, şöyle hayal:

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

Bunu yapmak için, genellikle kimliğe göre gruplandırarak ve sonra ortaya çıkan dizilerin her birini birleştirerek başlardım. Bunun yerine, birleştirmeyi doğrudan şu adreste yapabilirsiniz reduce():

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

1
Bunun neden daha fazla oyu olduğunu bilmiyorum. Özlü, okunabilir (bana göre) ve verimli görünüyor . IE11'de uçmuyor , ancak güçlendirme çok zor değil ( a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map()), yaklaşık olarak)
0'da açılsın


18

Alasql JavaScript kütüphanesi ile yapabilirsiniz :

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

Bu örneği jsFiddle'da deneyin .

BTW: Büyük dizilerde (100000 kayıt ve daha fazlası) Alasql daha hızlı tham Linq. JsPref'deki teste bakın .

Yorumlar:

  • Burada VALUE değeri SQL'de bir anahtar kelime olduğu için köşeli parantez içine Değer koydum
  • Dize değerlerini sayı türüne dönüştürmek için CAST () işlevini kullanmalıyım.

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 vardır Bu örneği kendi içinde Array.reduce()belgeler.

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

Her ne kadar sorunun bazı cevapları olsa da ve cevapları biraz fazla karmaşık görünse de, gruplanmış için vanilya Javascript'i kullanmanızı öneririz (gerekirse) 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

Mutasyonlar olmadan:

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

Bu çözelti, yukarıda çözümlere göre daha esnek, böylece herhangi bir keyfi fonksiyon (bir anahtar) alır ve sağlar fonksiyonları ok benzer, lambda ifadeleri kullanılır LINQ :

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

NOT: Arrayprototipini genişletmek isteyip istemediğiniz size bağlıdır.

Çoğu tarayıcıda desteklenen örnek:

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

Ok fonksiyonlarını kullanan örnek (ES6):

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

Yukarıdaki her iki örnek de:

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

ES6 çözümünü çok beğendim. Dizi prototipini genişletmeden sadece küçük bir semplifikasyon:let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
caneta

8

yaklaşımımı önermek istiyorum. İlk olarak, ayrı gruplama ve toplama. Prototipik "gruplama ölçütü" işlevini bildirelim. Gruplanacak her dizi öğesi için "hash" dizesi üretmek için başka bir işlev gerekir.

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

gruplama tamamlandığında, veriyi ihtiyacınız olan şekilde toplayabilirsiniz

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

ortak çalışır. Bu kodu krom konsolda test ettim. ve hataları geliştirmek ve bulmak için çekinmeyin;)


Teşekkürler ! Yaklaşımı seviyorum ve ihtiyaçlarıma mükemmel bir şekilde uyuyor (toplanmaya ihtiyacım yok).
aberaud

Sana koymak da çizgi değiştirmek istediğiniz düşünüyorum (): map[_hash(key)].key = key;için map[_hash(key)].key = _hash(key);.
Scotty.NET

6

Bunun gibi bir şeyiniz olduğunu düşünün:

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

Bunu yaparak: const categories = [...new Set(cars.map((car) => car.cat))]

Bunu alacaksınız: ['sedan','sport']

Açıklama: 1. İlk olarak, bir dizi geçirerek yeni bir Set oluşturuyoruz. Set yalnızca benzersiz değerlere izin verdiğinden, tüm kopyalar kaldırılır.

  1. Şimdi kopyalar kayboldu, forma operatörünü kullanarak onu bir diziye dönüştüreceğiz ...

Doc'u Ayarla: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Sread Spread OperatorDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Referans / Operatörler / Spread_syntax


Cevabınızı çok beğendim, en kısa olanı, ama yine de mantığı anlamıyorum, özellikle, burada gruplamayı kim yapıyor? yayılan operatör (...) mi? veya 'yeni Set ()'? lütfen bize açıklayın ... teşekkür ederim
Ivan

1
1. İlk olarak, bir dizi geçirerek yeni bir Set oluşturuyoruz. Set yalnızca benzersiz değerlere izin verdiğinden, tüm kopyalar kaldırılır. 2. Şimdi çiftleri, biz yayılmış operatörü ... Set Doc kullanarak bir diziye geri dönüştürmek için gidiyoruz gitmiş gibidir: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... Yayılmasını Operatör: developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/…
Yago Gehres

tamam, anladım ... açıklama için teşekkür ederim efendim :)
Ivan

rica ederim!
Yago Gehres

6

İşaretli cevap - sadece sığ gruplama. İndirgeyi anlamak güzel. Soru ayrıca ek toplam hesaplamalar sorununu da sağlar.

İşte 1) hesaplanan anahtar adı ve 2) istenen anahtarların listesini sağlayarak ve benzersiz değerlerini SQL GROUP gibi kök anahtarlara dönüştürerek gruplamanın basamaklandırılması için bazı alanlara göre Nesneler Dizisi için REAL GROUP BY BY yapar.

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

İle oyna estKey - birden fazla alana göre gruplandırabilir, ek toplamalar, hesaplamalar veya başka işlemler ekleyebilirsiniz.

Ayrıca verileri özyinelemeli olarak gruplayabilirsiniz. Örneğin başlangıçta gruplandır Phase, sonra Stepalana ve benzerlerine göre gruplandırın . Ek olarak yağ istirahat verilerini uçurun.

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

Bu bir dizi çıktı.


4

Önceki cevaplara dayanarak

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

ve ortamınız destekliyorsa, nesne yayılımı sözdizimine bakmak biraz daha hoştur.

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

Burada, redüktörümüz kısmen oluşturulmuş dönüş değerini alır (boş bir nesneden başlayarak) ve anahtarı önceki iteree'in değerinden hesaplanan yeni bir üyeyle birlikte önceki dönüş değerinin yayılan üyelerinden oluşan bir nesneyi döndürür propve değeri geçerli değerle birlikte o pervane için tüm değerlerin bir listesi.


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

İşte ES6 kullanarak kötü, okunması zor bir çözüm:

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

Bunu nasıl soran olanlar için bile çalışmayı aşağıda bir açıklama verilmiştir:

  • Her ikisinde de =>ücretsizreturn

  • Array.prototype.reduceFonksiyon 4 değişken almaktadır. Bu nedenle, varsayılan bir değer kullanarak parametre bildirim düzeyinde (k) grubu için ucuz bir değişken bildirimine sahip olabilmemiz için beşinci bir parametre ekleniyor. (evet, bu büyücülük)

  • Mevcut grubumuz önceki yinelemede yoksa, yeni bir boş dizi oluştururuz ((r[k] || (r[k] = []))Bu, en soldaki ifadeyi, başka bir deyişle, mevcut bir diziyi veya boş bir diziyi değerlendirir , bu yüzden pushbu ifadeden hemen sonra olur, çünkü her iki şekilde de bir dizi alırsınız.

  • Bir olduğunda return, virgül ,operatörü en soldaki değeri atar ve önceki senaryo için bu senaryo için ayarlanmış olan döndürür.

Aynı şeyi anlayan daha kolay bir sürüm:

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

2
biraz açıklamak ister misiniz, mükemmel çalışıyor
Nuwan Dammika

@NuwanDammika - Her ikisinde de => ücretsiz bir "dönüş" var - Azaltma fonksiyonu 4 parametreye kadar sürer. Bu nedenle (k) grubu için ucuz bir değişken bildirimi alabilmemiz için beşinci bir parametre ekleniyor. - Önceki değerin geçerli grubumuz yoksa, yeni bir boş grup oluştururuz ((r [k] || (r [k] = [])) Bu en soldaki ifadeyi, aksi takdirde bir dizi veya bir boş dizidir, bu yüzden bu ifadeden sonra hemen bir itme olur - Bir dönüş olduğunda, virgül operatörü en önceki değeri döndürerek en soldaki değeri atar.
darkndream

2

Genel bir Array.prototype.groupBy()araç oluşturalım . Sadece çeşitlilik için, tekrarlayan bir yaklaşımla bazı Haskellesque desen eşleşmesi için ES6 fantezisini kullanalım. Ayrıca Array.prototype.groupBy()item ( e) öğesini index ( i) ve uygulanan array ( a) değişkenini bağımsız değişken olarak alan bir geri çağrıyı kabul etmemizi sağlayalım .

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

Ceasar'ın cevabı iyidir, ancak sadece dizi içindeki öğelerin iç özellikleri için çalışır (dize durumunda uzunluk).

bu uygulama daha çok benzer: bu bağlantı

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

Bu yardımcı olur umarım...


2

@Mortb, @jmarceli cevaptan ve bu gönderiden ,

Ben grup tarafından PRIMITIVE VALUE birden çok sütun JSON.stringify()için kimlik olmak avantajından yararlanın .

Üçüncü taraf olmadan

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

İle Lodash üçüncü taraf

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 undefined döndürüyor
Asbar Ali

@AsbarAli Snippet'imi Chrome'un konsolu - Sürüm 66.0.3359.139 (Resmi Derleme) (64 bit) ile test ettim. Ve her şey yolunda gidiyor. Lütfen hata ayıklama kesme noktasını koyabilir ve keyGetter'ın neden tanımlanmadığını görebilirsiniz. Belki de tarayıcı sürümü nedeniyle.
Pranithan T.

2

Fonksiyon reduce desteği ile ES6 tabanlı versiyon iteratee.

iterateeİşlev sağlanmadığında beklendiği gibi çalışır :

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

OP sorusu bağlamında:

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

Başka ES6 gruplandırma tersine döner ve kullanan sürümü valuesolarak keysve keysşekilde 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))

Not: işlevler kısalık ve sadece fikri ilişkilendirmek için kısa formlarında (bir satır) yayınlanır. Bunları genişletebilir ve ek hata kontrolü vb. Ekleyebilirsiniz.


2

Ben tam olarak aradığınızı yapmak gibi görünüyor declarative-js kontrol ediyorum groupBy. Ayrıca:

  • çok performanslı (performans ölçütü )
  • daktiloda yazılır, böylece tüm tiplemeler dahil edilir.
  • 3. taraf dizi benzeri nesneleri kullanmak zorlamıyor.
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

İşte boş üyelere zarar vermeyecek bir ES6 sürümü

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

1

Sadece Scott Sauyet en eklenecek cevap , bazı insanlar nasıl yerine sadece bir değer gruplama vb GroupBy değer1, değer2, yaptığı işlevi kullanmak için yorumlarda soruyorlardı.

Tek yapmanız gereken toplam fonksiyonunu düzenlemek:

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

ana olanı (DataGrouper) değiştirmeden bırakmak:

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

İle sıralama özelliği

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

Örneklem:

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')));
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.