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?

80voto

jmarceli Punkte 17378

Ich würde prüfen lodash groupBy Es scheint genau das zu tun, wonach Sie suchen. Es ist auch ziemlich leicht und wirklich einfach.

Beispiel für eine Geige: https://jsfiddle.net/r7szvt5k/

Vorausgesetzt, Ihr Array-Name lautet arr das groupBy mit lodash ist gerecht:

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

63voto

Scott Sauyet Punkte 45175

Obwohl die linq Antwort ist interessant, aber auch ziemlich schwerfällig. Mein Ansatz ist etwas anders:

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

Sie können es sehen in Aktion auf JSBin .

Ich habe in Underscore nichts gesehen, was das tut has tut, obwohl ich es vielleicht übersehen habe. Es ist fast dasselbe wie _.contains , sondern verwendet _.isEqual statt === für Vergleiche. Abgesehen davon ist der Rest des Textes problemspezifisch, auch wenn versucht wurde, ihn allgemein zu halten.

Jetzt DataGrouper.sum(data, ["Phase"]) gibt zurück.

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

Und DataGrouper.sum(data, ["Phase", "Step"]) gibt zurück.

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

Aber sum ist hier nur eine mögliche Funktion. Sie können nach Belieben weitere registrieren:

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

und jetzt DataGrouper.max(data, ["Phase", "Step"]) wird zurückgegeben

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

oder wenn Sie dies registriert haben:

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

und ruft dann DataGrouper.tasks(data, ["Phase", "Step"]) erhalten Sie

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

DataGrouper ist selbst eine Funktion. Sie können sie mit Ihren Daten und einer Liste der Eigenschaften, nach denen Sie gruppieren möchten, aufrufen. Sie gibt ein Array zurück, dessen Elemente Objekte mit zwei Eigenschaften sind: key ist die Sammlung der gruppierten Eigenschaften, vals ist ein Array von Objekten, das die übrigen Eigenschaften enthält, die nicht im Schlüssel enthalten sind. Zum Beispiel, DataGrouper(data, ["Phase", "Step"]) ergeben wird:

[
    {
        "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.register nimmt eine Funktion an und erstellt eine neue Funktion, die die Ausgangsdaten und die Eigenschaften, nach denen gruppiert werden soll, annimmt. Diese neue Funktion nimmt dann das oben beschriebene Ausgabeformat und führt Ihre Funktion nacheinander für jede dieser Eigenschaften aus und gibt ein neues Array zurück. Die erzeugte Funktion wird als eine Eigenschaft von DataGrouper nach einem von Ihnen angegebenen Namen und auch zurückgegeben, wenn Sie nur eine lokale Referenz wünschen.

Nun, das ist eine Menge an Erklärungen. Ich hoffe, der Code ist einigermaßen einfach!

52voto

mellamokb Punkte 55046

Dies ist wahrscheinlich einfacher zu bewerkstelligen mit linq.js , die eine echte Implementierung von LINQ in JavaScript sein soll ( DEMO ):

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

Ergebnis:

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

Oder, noch einfacher, mit Hilfe der stringbasierten Selektoren ( DEMO ):

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

32voto

HoppyKamper Punkte 978

MDN hat dieses Beispiel in ihrem Array.reduce() Dokumentation.

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

25voto

Julio Marins Punkte 9097
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

Von: http://underscorejs.org/#groupBy

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