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?

24voto

Kmylo darkstar Punkte 359

Es ist ein bisschen spät, aber vielleicht gefällt es jemandem.

ES6:

const users = [{
    name: "Jim",
    color: "blue"
  },
  {
    name: "Sam",
    color: "blue"
  },
  {
    name: "Eddie",
    color: "green"
  },
  {
    name: "Robert",
    color: "green"
  },
];
const groupBy = (arr, key) => {
  const initialValue = {};
  return arr.reduce((acc, cval) => {
    const myAttribute = cval[key];
    acc[myAttribute] = [...(acc[myAttribute] || []), cval]
    return acc;
  }, initialValue);
};

const res = groupBy(users, "color");
console.log("group by:", res);

21voto

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

20voto

agershun Punkte 3923

Sie können dies tun mit Alasql JavaScript-Bibliothek:

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

Versuchen Sie dieses Beispiel bei jsFiddle .

ÜBRIGENS: Bei großen Arrays (100000 Datensätze und mehr) ist Alasql schneller als Linq. Siehe Test bei jsPref .

Kommentare:

  • Hier habe ich Value in eckige Klammern gesetzt, weil VALUE ein Schlüsselwort in SQL ist
  • Ich muss die Funktion CAST() verwenden, um String-Werte in den Zahlentyp zu konvertieren.

19voto

Nina Scholz Punkte 348155

Ein neuerer Ansatz mit einem Objekt für die Gruppierung und zwei weiteren Funktionen, um einen Schlüssel zu erstellen und ein Objekt mit den gewünschten Elementen der Gruppierung und einen weiteren Schlüssel für den zusätzlichen Wert zu erhalten.

const
    groupBy = (array, groups, valueKey) => {
        const
            getKey = o => groups.map(k => o[k]).join('|'),
            getObject = o => Object.fromEntries([...groups.map(k => [k, o[k]]), [valueKey, 0]]);

        groups = [].concat(groups);

        return Object.values(array.reduce((r, o) => {
            (r[getKey(o)] ??= getObject(o))[valueKey] += +o[valueKey];
            return r;
        }, {}));
    },
    data = [{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task: "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; }

Alter Ansatz:

Obwohl die Frage einige Antworten haben und die Antworten ein bisschen zu kompliziert aussehen, schlage ich vor, Vanilla Javascript für Group-by mit einem verschachtelten (falls erforderlich) zu verwenden 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; }

15voto

SynCap Punkte 5938

Kontrollierte Antwort - nur oberflächliche Gruppierung. Es ist ziemlich gut zu verstehen, zu reduzieren. Frage bieten auch das Problem der zusätzlichen aggregierten Berechnungen.

Hier ist ein REAL GROUP BY für ein Array von Objekten nach einem oder mehreren Feldern mit 1) berechneten Schlüsselnamen und 2) einer vollständigen Lösung für die Kaskadierung der Gruppierung durch Bereitstellung der Liste der gewünschten Schlüssel und Konvertierung der eindeutigen Werte in Wurzelschlüssel wie bei SQL GROUP BY tut.

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

Spielen mit estKey -- Sie können nach mehr als einem Feld gruppieren, zusätzliche Aggregationen, Berechnungen oder andere Verarbeitungen hinzufügen.

Sie können Daten auch rekursiv gruppieren. Zum Beispiel zunächst gruppieren nach Phase , dann durch Step Feld und so weiter. Zusätzlich abblasen die fetten Restdaten.

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

/**
 * Recursive grouping 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 ) );

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