925 Stimmen

Effizienteste Methode zur Gruppierung von Objekten in einem Array

Wie kann man Objekte in einem Array am effizientesten gruppieren?

Nehmen wir zum Beispiel dieses Array von Objekten:

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

Ich zeige diese Informationen in einer Tabelle an. Ich würde gerne nach verschiedenen Methoden gruppieren, aber ich möchte die Werte summieren.

Ich verwende Underscore.js für seine groupby-Funktion, die hilfreich ist, aber nicht den ganzen Trick, weil ich nicht will, dass sie "aufgeteilt", sondern "zusammengeführt", mehr wie die SQL group by método.

Was ich suche, ist die Möglichkeit, bestimmte Werte zu summieren (falls gewünscht).

Wenn ich also groupby Phase würde ich gerne erhalten:

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

Und wenn ich Groupy wäre Phase / Step würde ich erhalten:

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

Gibt es ein hilfreiches Skript für diese, oder sollte ich bleiben mit Underscore.js, und dann Schleife durch das resultierende Objekt zu tun, die Summen selbst?

13voto

kevinrodriguez-io Punkte 766

Hier ist eine hässliche, schwer zu lesende Lösung mit ES6:

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

Für diejenigen, die sich fragen, wie das geht sogar Arbeit, hier ist eine Erklärung:

  • In beiden => Sie haben eine kostenlose return

  • En Array.prototype.reduce Funktion nimmt bis zu 4 Parameter entgegen. Deshalb wird ein fünfter Parameter hinzugefügt, damit wir eine billige Variablendeklaration für die Gruppe (k) auf der Ebene der Parameterdeklaration haben können, indem wir einen Standardwert verwenden. (ja, das ist Zauberei)

  • Wenn unsere aktuelle Gruppe in der vorherigen Iteration nicht existiert, erstellen wir ein neues leeres Array ((r[k] || (r[k] = [])) Die Auswertung ergibt den Ausdruck ganz links, mit anderen Worten, ein vorhandenes Array oder ein leeres Array Deshalb gibt es eine sofortige push nach diesem Ausdruck, da Sie so oder so ein Array erhalten.

  • Wenn es eine return das Komma , Operator verwirft den Wert ganz links und gibt die geänderte vorherige Gruppe für dieses Szenario zurück.

Eine einfacher zu verstehende Version, die dasselbe tut, ist:

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

編集

TS-Version:

const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
  list.reduce((previous, currentItem) => {
    const group = getKey(currentItem);
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {} as Record<K, T[]>);

8voto

Anton Punkte 2458

Ich möchte meinen Ansatz vorschlagen. Erstens: Trennen Sie Gruppierung und Aggregation. Lassen Sie uns eine prototypische "Gruppieren nach" Funktion deklarieren. Es braucht eine andere Funktion, um "Hash"-String für jedes Array-Element zu gruppieren durch zu produzieren.

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

Wenn die Gruppierung erfolgt ist, können Sie die Daten nach Bedarf aggregieren, in Ihrem Fall

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

in gemeinsamen es funktioniert. Ich habe diesen Code in Chrome-Konsole getestet. und fühlen sich frei zu verbessern und Fehler zu finden ;)

8voto

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

Dieser gibt ein Array aus.

7voto

Bless Punkte 4962

Ohne Mutationen:

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

7voto

Diego Punkte 17339

Diese Lösung nimmt eine beliebige Funktion (keinen Schlüssel), ist also flexibler als die obigen Lösungen und erlaubt Pfeilfunktionen die ähnlich sind wie Lambda-Ausdrücke verwendet in LINQ :

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

HINWEIS: Ob Sie die Array Prototyps ist Ihnen überlassen.

Das Beispiel wird von den meisten Browsern unterstützt:

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

Beispiel mit Pfeilfunktionen (ES6):

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

Beide obigen Beispiele ergeben:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X