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?

2voto

Gürcan Kavakçı Punkte 522

Ich habe die Antworten verbessert. Diese Funktion nimmt ein Array von Gruppenfeldern und gibt ein gruppiertes Objekt zurück, dessen Schlüssel auch ein Objekt von Gruppenfeldern ist.

function(xs, groupFields) {
        groupFields = [].concat(groupFields);
        return xs.reduce(function(rv, x) {
            let groupKey = groupFields.reduce((keyObject, field) => {
                keyObject[field] = x[field];
                return keyObject;
            }, {});
            (rv[JSON.stringify(groupKey)] = rv[JSON.stringify(groupKey)] || []).push(x);
            return rv;
        }, {});
    }

let x = [
{
    "id":1,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":2,
    "multimedia":false,
    "language":["fr"]
},
{
    "id":3,
    "multimedia":true,
    "language":["tr"]
},
{
    "id":4,
    "multimedia":false,
    "language":[]
},
{
    "id":5,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":6,
    "multimedia":false,
    "language":["tr"]
},
{
    "id":7,
    "multimedia":false,
    "language":["tr","fr"]
}
]

groupBy(x, ['multimedia','language'])

//{
//{"multimedia":false,"language":["tr"]}: Array(3), 
//{"multimedia":false,"language":["fr"]}: Array(1), 
//{"multimedia":true,"language":["tr"]}: Array(1), 
//{"multimedia":false,"language":[]}: Array(1), 
//{"multimedia":false,"language":["tr","fr"]}: Array(1)
//}

2voto

much2learn Punkte 151

Ich schreibe, weil ich, obwohl diese Frage schon 7 Jahre alt ist, noch keine Antwort gefunden habe, die den ursprünglichen Kriterien entspricht:

Ich möchte nicht, dass sie "getrennt", sondern "zusammengeführt" werden, mehr li Methode.

Ursprünglich bin ich auf diesen Beitrag gestoßen, weil ich eine Methode zum Reduzieren eines Arrays von Objekten (d. h. der Datenstruktur, die beim Lesen aus einer CSV-Datei erstellt wird) und zum Aggregieren nach bestimmten Indizes finden wollte, um Folgendes zu erzeugen die gleiche Datenstruktur . Der Rückgabewert, den ich suchte, war ein weiteres Array von Objekten, nicht ein verschachteltes Objekt oder eine Karte, wie ich hier vorgeschlagen gesehen habe.

Die folgende Funktion nimmt einen Datensatz (Array von Objekten), eine Liste von Indizes (Array) und eine Reduzierfunktion und gibt das Ergebnis der Anwendung der Reduzierfunktion auf die Indizes als Array von Objekten zurück.

function agg(data, indices, reducer) {

  // helper to create unique index as an array
  function getUniqueIndexHash(row, indices) {
    return indices.reduce((acc, curr) => acc + row[curr], "");
  }

  // reduce data to single object, whose values will be each of the new rows
  // structure is an object whose values are arrays
  // [{}] -> {{}}
  // no operation performed, simply grouping
  let groupedObj = data.reduce((acc, curr) => {
    let currIndex = getUniqueIndexHash(curr, indices);

    // if key does not exist, create array with current row
    if (!Object.keys(acc).includes(currIndex)) {
      acc = {...acc, [currIndex]: [curr]}
    // otherwise, extend the array at currIndex
    } else {
      acc = {...acc, [currIndex]: acc[currIndex].concat(curr)};
    }

    return acc;
  }, {})

  // reduce the array into a single object by applying the reducer
  let reduced = Object.values(groupedObj).map(arr => {
    // for each sub-array, reduce into single object using the reducer function
    let reduceValues = arr.reduce(reducer, {});

    // reducer returns simply the aggregates - add in the indices here
    // each of the objects in "arr" has the same indices, so we take the first
    let indexObj = indices.reduce((acc, curr) => {
      acc = {...acc, [curr]: arr[0][curr]};
      return acc;
    }, {});

    reduceValues = {...indexObj, ...reduceValues};

    return reduceValues;
  });

  return reduced;
}

Ich werde einen Reduzierer erstellen, der count(*) und sum(Value) zurückgibt:

reducer = (acc, curr) => {
  acc.count = 1 + (acc.count || 0);
  acc.value = +curr.Value + (acc.value|| 0);
  return acc;
}

schließlich ergibt die Anwendung der agg-Funktion mit unserem Reducer auf den ursprünglichen Datensatz ein Array von Objekten, auf die die entsprechenden Aggregationen angewendet werden:

agg(tasks, ["Phase"], reducer);
// yields:
Array(2) [
  0: Object {Phase: "Phase 1", count: 4, value: 50}
  1: Object {Phase: "Phase 2", count: 4, value: 130}
]

agg(tasks, ["Phase", "Step"], reducer);
// yields:
Array(4) [
  0: Object {Phase: "Phase 1", Step: "Step 1", count: 2, value: 15}
  1: Object {Phase: "Phase 1", Step: "Step 2", count: 2, value: 35}
  2: Object {Phase: "Phase 2", Step: "Step 1", count: 2, value: 55}
  3: Object {Phase: "Phase 2", Step: "Step 2", count: 2, value: 75}
]

2voto

Ping Woo Punkte 1185

Normalerweise verwende ich ローダッシュ JavaScript-Hilfsbibliothek mit einer vorgefertigten groupBy() Methode. Sie ist ziemlich einfach zu benutzen, siehe mehr Details aquí .

2voto

Roey Punkte 1579

Die Antwort von Ceasar ist gut, funktioniert aber nur für die inneren Eigenschaften der Elemente innerhalb des Arrays (Länge im Fall von String).

diese Implementierung funktioniert eher wie: dieser Link

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

Ich hoffe, das hilft...

2voto

Akrion Punkte 16862

ES6 reduce basierte Version mit der Unterstützung für die Funktion iteratee .

Funktioniert genau wie erwartet, wenn die iteratee Funktion ist nicht vorgesehen:

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

Im Zusammenhang mit der OP-Frage:

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

Eine weitere ES6 Version, die die Gruppierung umkehrt und die values als keys und die keys als die 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))

Anmerkung: Die Funktionen werden in ihrer Kurzform (eine Zeile) veröffentlicht, um sich kurz zu fassen und nur die Idee zu vermitteln. Sie können sie erweitern und zusätzliche Fehlerprüfungen usw. hinzufügen.

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