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?

1314voto

Ceasar Bautista Punkte 20391

Wenn Sie externe Bibliotheken vermeiden wollen, können Sie eine einfache Version von groupBy() etwa so:

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

420voto

mortb Punkte 8624

Verwendung des ES6 Map-Objekts:

/**
 * @description
 * Takes an Array<V>, and a grouping function,
 * and returns a Map of the array grouped by the grouping function.
 *
 * @param list An array of type V.
 * @param keyGetter A Function that takes the the Array type V as an input, and returns a value of type K.
 *                  K is generally intended to be a property key of V.
 *
 * @returns Map of the array grouped by the grouping function.
 */
//export function groupBy<K, V>(list: Array<V>, keyGetter: (input: V) => K): Map<K, Array<V>> {
//    const map = new Map<K, Array<V>>();
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"}]

const odd = Symbol();
const even = Symbol();
const numbers = [1,2,3,4,5,6,7];

const oddEven = groupBy(numbers, x => (x % 2 === 1 ? odd : even));

console.log(oddEven.get(odd)); // -> [1,3,5,7]
console.log(oddEven.get(even)); // -> [2,4,6]

Über die Karte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

168voto

Joseph Nields Punkte 5203

Mit ES6:

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

129voto

Arthur Tacca Punkte 7601

Sie können eine ES6 Map von array.reduce() .

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

Dies hat einige Vorteile gegenüber den anderen Lösungen:

  • Es benötigt keine Bibliotheken (im Gegensatz zu z.B. _.groupBy() )
  • Sie erhalten ein JavaScript Map statt eines Objekts (z. B. wie es von _.groupBy() ). Dies hat viele Vorteile , einschließlich:
    • merkt es sich die Reihenfolge, in der die Elemente zuerst hinzugefügt wurden,
    • Schlüssel können von jedem Typ sein und nicht nur Strings.
  • A Map ist ein nützlicheres Ergebnis als ein Array von Arrays. Aber wenn Sie ein Array von Arrays haben wollen, können Sie dann Array.from(groupedMap.entries()) (für ein Array von [key, group array] Paare) oder Array.from(groupedMap.values()) (für ein einfaches Array von Arrays).
  • Sie ist recht flexibel; oft kann das, was Sie als Nächstes mit dieser Karte vorhatten, direkt als Teil der Verkleinerung durchgeführt werden.

Als ein Beispiel für den letzten Punkt, stellen Sie sich vor, ich habe ein Array von Objekten, die ich eine (oberflächliche) merge auf nach id, wie diese tun möchten:

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

Um dies zu tun, würde ich in der Regel durch Gruppierung von id beginnen, und dann verschmelzen jeder der resultierenden Arrays. Stattdessen können Sie die Zusammenführung direkt in der reduce() :

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

Spätere Bearbeitung:

Für die meisten Zwecke ist die obige Vorgehensweise wahrscheinlich effizient genug. Aber die ursprüngliche Frage war "am effizientesten" und, wie ein paar Leute darauf hingewiesen haben, das ist nicht wahr, der oben genannten Lösung. Das Problem ist hauptsächlich, dass für jeden Eintrag ein neues Array instanziiert wird. Ich hatte gedacht, dass dies durch den JS-Interpreter weg optimiert werden würde, aber es scheint vielleicht nicht.

Jemand schlug eine Bearbeitung vor, um dies zu beheben, aber es sah wirklich komplizierter aus. Schon das ursprüngliche Snippet drückt ein wenig auf die Lesbarkeit. Wenn Sie das wirklich tun wollen, verwenden Sie bitte einfach eine for-Schleife! Das ist keine Sünde! Es erfordert ein oder zwei Zeilen mehr Code, aber es ist Einfacher als funktionale Techniken, auch wenn sie nicht kürzer ist:

const groupedMap = new Map();
for (const e of initialArray) {
    if (!groupedMap.has(e.id)) {
        groupedMap.set(e.id, []);
    }
    groupedMap.get(e.id).push(e);
}

119voto

nkitku Punkte 3128

GroupBy Einzeiler an ES2021 Lösung

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).push(b),a),{});

タイプスクリプト

const groupBy = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => string) =>
  array.reduce((acc, value, index, array) => {
    (acc[predicate(value, index, array)] ||= []).push(value);
    return acc;
  }, {} as { [key: string]: T[] });

BEISPIELE

const groupBy = (x,f)=>x.reduce((a,b,i)=>((a[f(b,i,x)]||=[]).push(b),a),{});
// f -> should must return string/number because it will be use as key in object

// for demo

groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9], v => (v % 2 ? "odd" : "even"));
// { odd: [1, 3, 5, 7, 9], even: [2, 4, 6, 8] };
const colors = [
  "Apricot",
  "Brown",
  "Burgundy",
  "Cerulean",
  "Peach",
  "Pear",
  "Red",
];

groupBy(colors, v => v[0]); // group by colors name first letter
// {
//   A: ["Apricot"],
//   B: ["Brown", "Burgundy"],
//   C: ["Cerulean"],
//   P: ["Peach", "Pear"],
//   R: ["Red"],
// };
groupBy(colors, v => v.length); // group by length of color names
// {
//   3: ["Red"],
//   4: ["Pear"],
//   5: ["Brown", "Peach"],
//   7: ["Apricot"],
//   8: ["Burgundy", "Cerulean"],
// }

const data = [
  { comment: "abc", forItem: 1, inModule: 1 },
  { comment: "pqr", forItem: 1, inModule: 1 },
  { comment: "klm", forItem: 1, inModule: 2 },
  { comment: "xyz", forItem: 1, inModule: 2 },
];

groupBy(data, v => v.inModule); // group by module
// {
//   1: [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   2: [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupBy(data, x => x.forItem + "-" + x.inModule); // group by module with item
// {
//   "1-1": [
//     { comment: "abc", forItem: 1, inModule: 1 },
//     { comment: "pqr", forItem: 1, inModule: 1 },
//   ],
//   "1-2": [
//     { comment: "klm", forItem: 1, inModule: 2 },
//     { comment: "xyz", forItem: 1, inModule: 2 },
//   ],
// }

groupByToMap

const groupByToMap = (x, f) =>
  x.reduce((a, b, i, x) => {
    const k = f(b, i, x);
    a.get(k)?.push(b) ?? a.set(k, [b]);
    return a;
  }, new Map());

タイプスクリプト

const groupByToMap = <T, Q>(array: T[], predicate: (value: T, index: number, array: T[]) => Q) =>
  array.reduce((map, value, index, array) => {
    const key = predicate(value, index, array);
    map.get(key)?.push(value) ?? map.set(key, [value]);
    return map;
  }, new Map<Q, T[]>());

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