web-dev-qa-db-de.com

Die effizienteste Methode zum Gruppieren auf einem Array von Objekten

Was ist der effizienteste Weg, um Objekte in einem Array zu gruppieren?

Angenommen, 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 möchte nach verschiedenen Methoden gruppieren, aber ich möchte die Werte zusammenfassen.

Ich benutze Underscore.js für seine Groupby-Funktion, was zwar hilfreich ist, aber nicht den ganzen Trick, weil ich nicht will, dass sie getrennt werden, sondern wie die SQL-Methode group by.

Nach was ich suche, könnte ich bestimmte Werte addieren (falls gewünscht).

Wenn ich gruppiert habe mit Phase, möchte ich Folgendes erhalten:

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

Wenn ich Phase/Step gruppiert habe, würde ich Folgendes 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 dafür ein hilfreiches Skript, oder sollte ich mich an die Verwendung von Underscore.js halten und dann das resultierende Objekt durchlaufen, um die Summen selbst zu erstellen?

285
Rail24

Wenn Sie externe Bibliotheken vermeiden möchten, können Sie eine Vanilla-Version von groupBy() wie folgt einfach implementieren:

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

444
Ceasar Bautista

Verwenden des ES6-Kartenobjekts:

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

Verwendungsbeispiel:

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

jsfiddle: https://jsfiddle.net/buko8r5d/

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

126
mortb

mit ES6:

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

Obwohl die linq - Antwort interessant ist, ist sie auch ziemlich schwer. 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 in Aktion auf JSBin sehen.

Ich habe in Underscore nichts gesehen, was has tut, obwohl ich es vermisse. Es ist fast dasselbe wie _.contains, verwendet jedoch _.isEqual anstelle von === für Vergleiche. Davon abgesehen ist der Rest problemspezifisch, obwohl versucht wird, generisch zu sein.

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

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

Und DataGrouper.sum(data, ["Phase", "Step"]) kehrt 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}
]

sum ist hier jedoch nur eine mögliche Funktion. Sie können andere nach Belieben 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 kehrt DataGrouper.max(data, ["Phase", "Step"]) zurück

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

dann rufen Sie DataGrouper.tasks(data, ["Phase", "Step"]) an

[
    {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 selbst ist eine Funktion. Sie können es mit Ihren Daten und einer Liste der Eigenschaften aufrufen, nach denen Sie gruppieren möchten. Es gibt ein Array zurück, dessen Elemente Objekte mit zwei Eigenschaften sind: key ist eine Sammlung von gruppierten Eigenschaften. vals ist ein Array von Objekten, die die übrigen Eigenschaften enthalten, die nicht im Schlüssel enthalten sind. Zum Beispiel ergibt DataGrouper(data, ["Phase", "Step"]):

[
    {
        "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 akzeptiert eine Funktion und erstellt eine neue Funktion, die die ursprünglichen Daten und die Eigenschaften zum Gruppieren akzeptiert. Diese neue Funktion übernimmt dann das Ausgabeformat wie oben und führt Ihre Funktion gegen jedes von ihnen aus, wodurch ein neues Array zurückgegeben wird. Die generierte Funktion wird als Eigenschaft von DataGrouper gemäß einem von Ihnen angegebenen Namen gespeichert und auch zurückgegeben, wenn Sie nur eine lokale Referenz wünschen.

Nun, das ist viel Erklärung. Der Code ist ziemlich einfach, hoffe ich!

53
Scott Sauyet

Dies ist wahrscheinlich einfacher mit linq.js , was 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 verwenden Sie einfach die stringbasierten Selektoren ( DEMO ):

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

Ich würde nach lodash groupBy suchen es scheint genau das zu tun, was Sie suchen. Es ist auch ziemlich leicht und sehr einfach.

Geigenbeispiel: https://jsfiddle.net/r7szvt5k/

Vorausgesetzt, Ihr Array-Name ist arr, lautet die groupBy mit lodash nur:

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
30
jmarceli
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

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

17
Julio Marins
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]
        };
    });
};
15
cezarypiatek

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

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 at jsFiddle .

BTW: Bei großen Arrays (100000 Datensätze und mehr) ist Alasql schneller als Linq. Siehe test at jsPref

Bemerkungen:

  • Hier habe ich Value in eckige Klammern gesetzt, da VALUE ein Schlüsselwort in SQL ist
  • Ich muss die Funktion CAST () verwenden, um Zeichenfolgenwerte in den Zahlentyp umzuwandeln.
14
agershun

Sie können ein ES6 Map aus array.reduce() erstellen. 

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

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

  • Es sind keine Bibliotheken erforderlich (im Gegensatz zu _.groupBy()).
  • Sie erhalten ein JavaScript Map anstelle eines Objekts (z. B. wie von _.groupBy() zurückgegeben). Dies hat viele Vorteile , einschließlich:
    • es merkt sich die Reihenfolge, in der die Artikel zuerst hinzugefügt wurden,
    • schlüssel können jeden Typ und nicht nur Strings sein. 
  • Ein Map ist ein nützlicheres Ergebnis als ein Array von Arrays. Wenn Sie jedoch ein Array von Arrays benötigen, können Sie Array.from(groupedMap.entries()) (für ein Array von [key, group array]-Paaren) oder Array.from(groupedMap.values()) (für ein einfaches Array von Arrays) aufrufen.
  • Es ist ziemlich flexibel. Was auch immer Sie als nächstes mit dieser Karte geplant haben, kann direkt als Teil der Reduzierung durchgeführt werden. 

Stellen Sie sich als ein Beispiel für den letzten Punkt vor, ich habe eine Reihe von Objekten, mit denen ich eine (flache) Zusammenführung mit einer ID machen möchte, wie folgt:

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 normalerweise damit beginnen, nach id zu gruppieren und dann jedes der resultierenden Arrays zusammenzuführen. Stattdessen können Sie die Zusammenführung direkt in der reduce() durchführen:

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

Obwohl die Frage einige Antworten hat und die Antworten etwas kompliziert erscheinen, schlage ich vor, Vanilla Javascript für Group-By zu verwenden.

Diese Lösung bietet eine Funktion, die ein Array mit Daten array und einen Eigenschaftsnamen für return col und einen Eigenschaftsnamen zum Zählen eines Werts value verwendet.

Die Funktion basiert auf einem Objekt, das als Hashtabelle für das Ergebnis fungiert.

function groupBy(array, col, value) {
    var r = [], o = {};
    array.forEach(function (a) {
        if (!o[a[col]]) {
            o[a[col]] = {};
            o[a[col]][col] = a[col];
            o[a[col]][value] = 0;
            r.Push(o[a[col]]);
        }
        o[a[col]][value] += +a[value];
    });
    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" }];

document.write('<pre>' + JSON.stringify(groupBy(data, 'Phase', 'Value'), 0, 4) + '</pre>');

7
Nina Scholz

MDN hat dieses Beispiel in seiner 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 }] 
// }
7
HoppyKamper

Diese Lösung akzeptiert eine beliebige Funktion (keine Taste), ist also flexibler als die oben genannten Lösungen und erlaubt die folgenden Pfeilfunktionen ähnlich den Lambda-Ausdrücken 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: Es liegt an Ihnen, ob Sie den Prototyp von Array erweitern möchten.

In den meisten Browsern unterstütztes Beispiel:

[{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 geben Folgendes zurück:

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

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

ich möchte meinen Ansatz vorschlagen. Zuerst getrennte Gruppierung und Aggregation. Lassen Sie uns die prototypische Funktion "Gruppieren nach" deklarieren. Es ist eine andere Funktion erforderlich, um für jedes Array-Element, nach dem gruppiert werden soll, eine "Hash" -String zu erzeugen. 

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 abgeschlossen ist, können Sie in Ihrem Fall die Daten nach Bedarf zusammenstellen

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

gemeinsam funktioniert es. Ich habe diesen Code in Chrome Console getestet. und sich frei zu verbessern und Fehler zu finden;)

6
Anton

Basierend auf früheren Antworten

const groupBy = (prop) => (xs) =>
  xs.reduce((rv, x) =>
    Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});

es ist etwas schöner, sich die Objektverbreitungssyntax anzusehen, wenn Ihre Umgebung dies unterstützt.

const groupBy = (prop) => (xs) =>
  xs.reduce((acc, x) => ({
    ...acc,
    [ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
  }), {});

Hier nimmt unser Reduzierer den teilweise gebildeten Rückgabewert (beginnend mit einem leeren Objekt) und gibt ein Objekt zurück, das aus den gespreizten Elementen des vorherigen Rückgabewerts zusammen mit einem neuen Element besteht, dessen Schlüssel aus dem aktuellen Wert des iteree-Werts bei berechnet wird prop und deren Wert ist eine Liste aller Werte für diese Eigenschaft zusammen mit dem aktuellen Wert.

3
Benny Powers

Ceasars Antwort ist gut, funktioniert aber nur für die inneren Eigenschaften der Elemente innerhalb des Arrays (Länge bei String).

diese Implementierung funktioniert mehr wie folgt: 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;
    }, {});
};

hoffe das hilft...

2
Roey
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;
    }, []);
}

Dieses gibt ein Array aus.

2
tomitrescak

Array.prototype.groupBy = function (groupingKeyFn) {
    if (typeof groupingKeyFn !== 'function') {
        throw new Error("groupBy take a function as only parameter");
    }
    return this.reduce((result, item) => {
        let key = groupingKeyFn(item);
        if (!result[key])
            result[key] = [];
        result[key].Push(item);
        return result;
    }, {});
}

var a = [
	{type: "video", name: "a"},
  {type: "image", name: "b"},
  {type: "video", name: "c"},
  {type: "blog", name: "d"},
  {type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Lets generieren ein generisches Array.prototype.groupBy() Werkzeug. Zur Abwechslung verwenden wir ES6-Fancy, den Spread-Operator für einen Haskellesque-Pattern-Matching bei einem rekursiven Ansatz. Nehmen wir auch unsere Array.prototype.groupBy() an, um einen Rückruf zu akzeptieren, der das Element (e), den Index (i) und das verwendete Array (a) als Argumente verwendet.

Array.prototype.groupBy = function(cb){
                            return function iterate([x,...xs], i = 0, r = [[],[]]){
                                     cb(x,i,[x,...xs]) ? (r[0].Push(x), r)
                                                       : (r[1].Push(x), r);
                                     return xs.length ? iterate(xs, ++i, r) : r;
                                   }(this);
                          };

var arr = [0,1,2,3,4,5,6,7,8,9],
    res = arr.groupBy(e => e < 5);
console.log(res);

2
Redu

Geprüfte Antwort - löst keine Gruppierung, ist jedoch eine direkte Antwort.

REAL GROUP BY für ein Array von Objekten durch ein Feld mit berechnetem Schlüsselnamen.

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 Sie mit estKey - Sie können nach mehr als einem Feld gruppieren

Sie können Daten auch rekursiv gruppieren. Zum Beispiel gruppieren Sie zunächst nach Phase, dann nach Step.

Überprüfen Sie es selbst, führen Sie es einfach aus. То то самое оно, что люди называют группировкой?

Ich wünsche Ihnen ein erfolgreicher.

Да здравствуют высокие показатели мастерства программистов во имя процветания всего человечества! Ура, товарищи!

1
SynCap
let groupbyKeys = function(arr, ...keys) {
  let keysFieldName = keys.join();
  return arr.map(ele => {
    let keysField = {};
    keysField[keysFieldName] = keys.reduce((keyValue, key) => {
      return keyValue + ele[key]
    }, "");
    return Object.assign({}, ele, keysField);
  }).reduce((groups, ele) => {
    (groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
      .Push([ele].map(e => {
        if (keys.length > 1) {
          delete e[keysFieldName];
        }
        return e;
    })[0]);
    return groups;
  }, {});
};

console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));
1
Tom Jiang

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

Funktioniert genau wie erwartet, wenn die iteratee-Funktion nicht bereitgestellt wird:

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 andere ES6 -Version, die die Gruppierung aufhebt und die values als keys und die keys als grouped values verwendet:

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

Hinweis: Funktionen werden in ihrer Kurzform (eine Zeile) der Kürze halber zur Verfügung gestellt und beziehen sich nur auf die Idee. Sie können sie erweitern und zusätzliche Fehlerprüfungen hinzufügen usw.

1
Akrion

Mit Sortierfunktion

export const groupBy = function groupByArray(xs, key, sortKey) {
      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);
          el.values.sort(function(a, b) {
            return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
          });
        } else {
          rv.Push({ key: v, values: [x] });
        }

        return rv;
      }, []);
    };

Probe:

var state = [
    {
      name: "Arkansas",
      population: "2.978M",
      flag:
  "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },{
      name: "Crkansas",
      population: "2.978M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
      category: "city"
    },
    {
      name: "Balifornia",
      population: "39.14M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
      category: "city"
    },
    {
      name: "Florida",
      population: "20.27M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
      category: "airport"
    },
    {
      name: "Texas",
      population: "27.47M",
      flag:
        "https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
      category: "landmark"
    }
  ];
console.log(JSON.stringify(groupBy(state,'category','name')));
1
amorenew

Hier ist eine ES6-Version, die bei null Mitgliedern nicht kaputt geht

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}
1
bigkahunaburger

Von @mortb, @jmarceli antworten und von diesem Beitrag ,

Ich nutze JSON.stringify(), um die Identität für die PRIMITIVE VALUE mehrerer Spalten von group by zu sein.

Ohne Drittanbieter

function groupBy(list, keyGetter) {
    const map = new Map();
    list.forEach((item) => {
        const key = keyGetter(item);
        if (!map.has(key)) {
            map.set(key, [item]);
        } else {
            map.get(key).Push(item);
        }
    });
    return map;
}

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));

console.log(grouped);

Mit Lodash Drittanbieter

const pets = [
    {type:"Dog", age: 3, name:"Spot"},
    {type:"Cat", age: 3, name:"Tiger"},
    {type:"Dog", age: 4, name:"Rover"}, 
    {type:"Cat", age: 3, name:"Leo"}
];

let rslt = _.groupBy(pets, pet => JSON.stringify(
 { type: pet.type, age: pet.age }));

console.log(rslt);
1
Pranithan T.

Nur um Scott Sauyets answer hinzuzufügen, haben einige Leute in den Kommentaren gefragt, wie sie ihre Funktion verwenden sollen, um Wert1, Wert2 usw. zu gruppieren, anstatt nur einen Wert zu gruppieren.

Sie müssen lediglich seine Summenfunktion bearbeiten:

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key,
        {VALUE1: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE1);}, 0)},
        {VALUE2: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE2);}, 0)}
    );
});

den Hauptteil (DataGrouper) unverändert lassen:

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;
}());
0
Telho

Basierend auf der ursprünglichen Idee von @Ceasar Bautista habe ich den Code geändert und eine GroupBy-Funktion mit TypeScript erstellt.

static groupBy(data: any[], comparator: (v1: any, v2: any) => boolean, onDublicate: (uniqueRow: any, dublicateRow: any) => void) {
    return data.reduce(function (reducedRows, currentlyReducedRow) {
      let processedRow = reducedRows.find(searchedRow => comparator(searchedRow, currentlyReducedRow));

      if (processedRow) {
        // currentlyReducedRow is a dublicateRow when processedRow is not null.
        onDublicate(processedRow, currentlyReducedRow)
      } else {
        // currentlyReducedRow is unique and must be pushed in the reducedRows collection.
        reducedRows.Push(currentlyReducedRow);
      }

      return reducedRows;
    }, []);
  };

Diese Funktion akzeptiert einen Rückruf (Komparator), der die Zeilen vergleicht und die Dubletten findet, sowie einen zweiten Rückruf (onDublicate), der die Dubletten aggregiert.

anwendungsbeispiel:

data = [
    { name: 'a', value: 10 },
    { name: 'a', value: 11 },
    { name: 'a', value: 12 },
    { name: 'b', value: 20 },
    { name: 'b', value: 1 }
  ]

  private static demoComparator = (v1: any, v2: any) => {
    return v1['name'] === v2['name'];
  }

  private static demoOnDublicate = (uniqueRow, dublicateRow) => {
    uniqueRow['value'] += dublicateRow['value'];    
  };

berufung 

groupBy(data, demoComparator, demoOnDublicate) 

führt eine Gruppe aus, die die Summe des Wertes berechnet.

{name: "a", value: 33}
{name: "b", value: 21}

Wir können so viele dieser Rückruffunktionen erstellen, wie vom Projekt benötigt, und die Werte nach Bedarf aggregieren. In einem Fall musste ich zum Beispiel zwei Arrays zusammenführen, anstatt die Daten zu summieren.

let x  = [
  {
    "id": "6",
    "name": "SMD L13",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "7",
    "name": "SMD L15",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  },
  {
    "id": "2",
    "name": "SMD L1",
    "equipmentType": {
      "id": "1",
      "name": "SMD"
    }
  }
];

function groupBy(array, property) {
  return array.reduce((accumulator, current) => {
    const object_property = current[property];
    delete current[property]

    let classified_element = accumulator.find(x => x.id === object_property.id);
    let other_elements = accumulator.filter(x => x.id !== object_property.id);

   if (classified_element) {
     classified_element.children.Push(current)
   } else {
     classified_element = {
       ...object_property, 
       'children': [current]
     }
   }
   return [classified_element, ...other_elements];
 }, [])
}

console.log( groupBy(x, 'equipmentType') )

/* output 

[
  {
    "id": "1",
    "name": "SMD",
    "children": [
      {
        "id": "6",
        "name": "SMD L13"
      },
      {
        "id": "7",
        "name": "SMD L15"
      },
      {
        "id": "2",
        "name": "SMD L1"
      }
    ]
  }
]

*/
0

Normalerweise verwende ich die JavaScript-Hilfsbibliothek Lodash mit einer vorgefertigten groupBy() -Methode. Es ist ziemlich einfach zu bedienen, siehe mehr Details hier .

0
Ping Woo

Sie können forEach für ein Array verwenden und eine neue Gruppe von Elementen erstellen. So machen Sie das mit FlowType Annotation

// @flow

export class Group<T> {
  tag: number
  items: Array<T>

  constructor() {
    this.items = []
  }
}

const groupBy = (items: Array<T>, map: (T) => number) => {
  const groups = []

  let currentGroup = null

  items.forEach((item) => {
    const tag = map(item)

    if (currentGroup && currentGroup.tag === tag) {
      currentGroup.items.Push(item)
    } else {
      const group = new Group<T>()
      group.tag = tag
      group.items.Push(item)
      groups.Push(group)

      currentGroup = group
    }
  })

  return groups
}

export default groupBy

Ein Spaßtest kann wie sein

// @flow

import groupBy from './groupBy'

test('groupBy', () => {
  const items = [
    { name: 'January', month: 0 },
    { name: 'February', month: 1 },
    { name: 'February 2', month: 1 }
  ]

  const groups = groupBy(items, (item) => {
    return item.month
  })

  expect(groups.length).toBe(2)
  expect(groups[1].items[1].name).toBe('February 2')
})
0
onmyway133

Ich habe mir diese Methode aus Underscore.js Fiddler ausgeliehen.

window.helpers=(function (){
    var lookupIterator = function(value) {
        if (value == null){
            return function(value) {
                return value;
            };
        }
        if (typeof value === 'function'){
                return value;
        }
        return function(obj) {
            return obj[value];
        };
    },
    each = function(obj, iterator, context) {
        var breaker = {};
        if (obj == null) return obj;
        if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
            obj.forEach(iterator, context);
        } else if (obj.length === +obj.length) {
            for (var i = 0, length = obj.length; i < length; i++) {
                if (iterator.call(context, obj[i], i, obj) === breaker) return;
            }
        } else {
            var keys = []
            for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.Push(key)
            for (var i = 0, length = keys.length; i < length; i++) {
                if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
            }
        }
        return obj;
    },
    // An internal function used for aggregate "group by" operations.
    group = function(behavior) {
        return function(obj, iterator, context) {
            var result = {};
            iterator = lookupIterator(iterator);
            each(obj, function(value, index) {
                var key = iterator.call(context, value, index, obj);
                behavior(result, key, value);
            });
            return result;
        };
    };

    return {
      groupBy : group(function(result, key, value) {
        Object.prototype.hasOwnProperty.call(result, key) ? result[key].Push(value) :              result[key] = [value];
        })
    };
})();

var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}];
 console.dir(helpers.groupBy(arr,"b"));
 console.dir(helpers.groupBy(arr,function (el){
   return el.b>2;
 }));
0
Roman Yudin

Ich habe die akzeptierte Antwort dahingehend erweitert, dass sie die Gruppierung nach mehreren Eigenschaften umfasst. Dann fügen Sie sie hinzu und machen sie ohne Mutation rein funktional. Eine Demo finden Sie unter https://stackblitz.com/edit/TypeScript-ezydzv

export interface Group {
  key: any;
  items: any[];
}

export interface GroupBy {
  keys: string[];
  thenby?: GroupBy;
}

export const groupBy = (array: any[], grouping: GroupBy): Group[] => {
  const keys = grouping.keys;
  const groups = array.reduce((groups, item) => {
    const group = groups.find(g => keys.every(key => item[key] === g.key[key]));
    const data = Object.getOwnPropertyNames(item)
      .filter(prop => !keys.find(key => key === prop))
      .reduce((o, key) => ({ ...o, [key]: item[key] }), {});
    return group
      ? groups.map(g => (g === group ? { ...g, items: [...g.items, data] } : g))
      : [
          ...groups,
          {
            key: keys.reduce((o, key) => ({ ...o, [key]: item[key] }), {}),
            items: [data]
          }
        ];
  }, []);
  return grouping.thenby ? groups.map(g => ({ ...g, items: groupBy(g.items, grouping.thenby) })) : groups;
};
0
Adrian Brand

Die folgende Funktion ermöglicht die Gruppierung (und Summe der Werte - welche OP benötigt) beliebiger Felder. In Lösung definieren wir die Funktion cmp, um zwei Objekte nach gruppierter fields zu vergleichen. In let w=... erstellen wir eine Kopie der Felder des Subset-Objekts x. In y[sumBy]=+y[sumBy]+(+x[sumBy]) verwenden wir '+', um die Zeichenfolge in eine Zahl umzuwandeln.

function groupBy(data, fields, sumBy='Value') {
  let r=[], cmp= (x,y) => fields.reduce((a,b)=> a && x[b]==y[b], true);
  data.forEach(x=> {
    let y=r.find(z=>cmp(x,z));
    let w= [...fields,sumBy].reduce((a,b) => (a[b]=x[b],a), {})
    y ? y[sumBy]=+y[sumBy]+(+x[sumBy]) : r.Push(w);
  });
  return r;
}
const d = [ 
    { 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" }
];



function groupBy(data, fields, sumBy='Value') {
  let r=[], cmp= (x,y) => fields.reduce((a,b)=> a && x[b]==y[b], true);
  data.forEach(x=> {
    let y=r.find(z=>cmp(x,z));
    let w= [...fields,sumBy].reduce((a,b) => (a[b]=x[b],a), {})
    y ? y[sumBy]=+y[sumBy]+(+x[sumBy]) : r.Push(w);
  });
  return r;
}


// TEST
let p=(t,o) => console.log(t, JSON.stringify(o));
console.log('GROUP BY:');

p('Phase', groupBy(d,['Phase']) );
p('Step', groupBy(d,['Step']) );
p('Phase-Step', groupBy(d,['Phase', 'Step']) );
p('Phase-Task', groupBy(d,['Phase', 'Task']) );
p('Step-Task', groupBy(d,['Step', 'Task']) );
p('Phase-Step-Task', groupBy(d,['Phase','Step', 'Task']) );
0

Hier ist eine böse, schwer zu lesende Lösung mit ES6:

export default (array, key) => {
  return array.reduce(
    (r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).Push(v), r),
    {}
  );
};
0
darkndream

Ich würde prüfen deklarativ-jsgroupBy es scheint genau das zu tun, wonach Sie suchen. Es ist auch:

  • sehr performant (performance benchmark )
  • geschrieben in TypeScript, so dass alle Typpings enthalten sind.
  • Die Verwendung von Array-ähnlichen Objekten von Drittanbietern wird nicht erzwungen.
import { Reducers } from 'declarative-js'
import groupBy = Reducers.groupBy
import Map = Reducers.Map

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

data.reduce(groupBy(element=> element.Step), Map())
data.reduce(groupBy('Step'), Map())
0
Pasa89

Sie könnten es auf folgende Weise tun. Ich habe gerade ein neues Array gebildet und es von groupBy function zurückgegeben. Berechneter Zählwert aus nur einer Schleife durch die Funktion .map

var arr = [ 
        { 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 groupBy = (arr, pahse, step='') => {

   var pahseArr = [];
   var resultArr = [];

   arr.map((item)=>{
     var pushed = false;
     pahseArr.map((ele)=>{
       if(ele===item.Phase){
         pushed = true;
       }
     })
     if(!pushed){
       pahseArr.Push(item.Phase);
     }     
   })

   pahseArr.map((item)=>{
      var sum = 0;
      arr.map((ele)=>{
        if(ele.Phase===item){
          sum += parseFloat(ele.Value)
        }
      })
      resultArr.Push({
        Phase: item,
        Value: sum
      })
   })

   if(step!=''){
     var resultArr = [];


     pahseArr.map((item)=>{
         var stepArr = [];

         arr.map((item2)=>{
           var pushed = false;
           stepArr.map((ele)=>{
             if(ele===item2.Step){
               pushed = true;
             }
           })
           if(!pushed){
             stepArr.Push(item2.Step);
           } 
         })

         stepArr.map((item1)=>{
            var sum = 0;
            arr.map((ele)=>{
              if(ele.Step===item1 && ele.Phase===item){
                sum += parseFloat(ele.Value)
              }
            })
            resultArr.Push({
              Phase: item,
              Step: item1,
              Value: sum
            })
         })

     })
     return resultArr;
   }   
   return resultArr;

}

console.log(groupBy(arr, 'Phase'));
console.log(groupBy(arr, 'Phase', 'Step'));
0
Vishal Raut
data = [{id:1, name:'BMW'}, {id:2, name:'AN'}, {id:3, name:'BMW'}, {id:1, name:'NNN'}]
key = 'id'//try by id or name
data.reduce((previous, current)=>{
    previous[current[key]] && previous[current[key]].length != 0 ? previous[current[key]].Push(current) : previous[current[key]] = new Array(current)
    return previous;
}, {})
0
Landaida