Beantworten wir die ursprüngliche Frage vollständig, indem wir bereits geschriebenen Code (d. h. Underscore) wiederverwenden. Du kannst viel mehr mit Underscore machen, wenn du Mähdrescher seine >100 Funktionen. Die folgende Lösung veranschaulicht dies.
Schritt 1: Gruppieren Sie die Objekte im Array nach einer beliebigen Kombination von Eigenschaften. Dies nutzt die Tatsache, dass _.groupBy
akzeptiert eine Funktion, die die Gruppe eines Objekts zurückgibt. Sie verwendet auch _.chain
, _.pick
, _.values
, _.join
y _.value
. Beachten Sie, dass _.value
ist hier nicht unbedingt erforderlich, da verkettete Werte bei der Verwendung als Eigenschaftsname automatisch abgewickelt werden. Ich füge sie ein, um Verwirrung vorzubeugen, falls jemand versucht, ähnlichen Code in einem Kontext zu schreiben, in dem das automatische Entpacken nicht stattfindet.
// Given an object, return a string naming the group it belongs to.
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ').value();
}
// Perform the grouping.
const intermediate = _.groupBy(arrayOfObjects, category);
Angesichts der arrayOfObjects
in der ursprünglichen Frage und Einstellung propertyNames
zu ['Phase', 'Step']
, intermediate
wird der folgende Wert angezeigt:
{
"Phase 1 Step 1": [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }
],
"Phase 1 Step 2": [
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }
],
"Phase 2 Step 1": [
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }
],
"Phase 2 Step 2": [
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
}
Schritt 2: Reduktion jeder Gruppe auf ein einzelnes flaches Objekt und Rückgabe der Ergebnisse in einem Array. Neben den Funktionen, die wir bereits gesehen haben, verwendet der folgende Code _.pluck
, _.first
, _.pick
, _.extend
, _.reduce
y _.map
. _.first
wird in diesem Fall garantiert ein Objekt zurückgeben, da _.groupBy
erzeugt keine leeren Gruppen. _.value
ist in diesem Fall notwendig.
// Sum two numbers, even if they are contained in strings.
const addNumeric = (a, b) => +a + +b;
// Given a `group` of objects, return a flat object with their common
// properties and the sum of the property with name `aggregateProperty`.
function summarize(group) {
const valuesToSum = _.pluck(group, aggregateProperty);
return _.chain(group).first().pick(propertyNames).extend({
[aggregateProperty]: _.reduce(valuesToSum, addNumeric)
}).value();
}
// Get an array with all the computed aggregates.
const result = _.map(intermediate, summarize);
Angesichts der intermediate
die wir zuvor erhalten haben, und die Einstellung aggregateProperty
zu Value
erhalten wir die result
die der Fragesteller wünscht:
[
{ 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 }
]
Wir können dies alles in einer Funktion zusammenfassen, die arrayOfObjects
, propertyNames
y aggregateProperty
als Parameter. Beachten Sie, dass arrayOfObjects
kann eigentlich auch ein einfaches Objekt mit String-Schlüsseln sein, denn _.groupBy
akzeptiert entweder. Aus diesem Grund habe ich den Namen arrayOfObjects
zu collection
.
function aggregate(collection, propertyNames, aggregateProperty) {
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ');
}
const addNumeric = (a, b) => +a + +b;
function summarize(group) {
const valuesToSum = _.pluck(group, aggregateProperty);
return _.chain(group).first().pick(propertyNames).extend({
[aggregateProperty]: _.reduce(valuesToSum, addNumeric)
}).value();
}
return _.chain(collection).groupBy(category).map(summarize).value();
}
aggregate(arrayOfObjects, ['Phase', 'Step'], 'Value')
wird uns nun die gleiche result
wieder.
Wir können noch einen Schritt weiter gehen und dem Aufrufer ermöglichen, Folgendes zu berechnen jede Statistik über die Werte in jeder Gruppe. Wir können dies tun und dem Aufrufer außerdem ermöglichen, der Zusammenfassung jeder Gruppe beliebige Eigenschaften hinzuzufügen. All dies können wir erreichen, indem wir unseren Code kürzer . Wir ersetzen die aggregateProperty
Parameter durch einen iteratee
Parameter und übergeben diesen direkt an _.reduce
:
function aggregate(collection, propertyNames, iteratee) {
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ');
}
function summarize(group) {
return _.chain(group).first().pick(propertyNames)
.extend(_.reduce(group, iteratee)).value();
}
return _.chain(collection).groupBy(category).map(summarize).value();
}
Wir verlagern einen Teil der Verantwortung auf den Anrufer; er muss eine iteratee
die übergeben werden können an _.reduce
, so dass der Aufruf von _.reduce
ein Objekt mit den Aggregateigenschaften, die sie hinzufügen möchte, erzeugt. Wir erhalten zum Beispiel das gleiche result
wie zuvor mit dem folgenden Ausdruck:
aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
Value: +memo.Value + +value.Value
}));
Ein Beispiel für ein etwas anspruchsvolleres iteratee
Angenommen, wir wollen die maximal Value
der einzelnen Gruppen anstelle der Summe, und dass wir eine Tasks
Eigenschaft, die alle Werte von Task
die in der Gruppe auftreten. Hier ist eine Möglichkeit, wie wir dies tun können, indem wir die letzte Version von aggregate
oben (und _.union
):
aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
Value: Math.max(memo.Value, value.Value),
Tasks: _.union(memo.Tasks || [memo.Task], [value.Task])
}));
Wir erhalten das folgende Ergebnis:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 10, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 1", Step: "Step 2", Value: 20, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 2", Step: "Step 1", Value: 30, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 2", Step: "Step 2", Value: 40, Tasks: [ "Task 1", "Task 2" ] }
]
Dank an @much2learn, der auch eine Antwort die beliebige reduzierende Funktionen verarbeiten kann. Ich habe ein paar weitere SO-Antworten geschrieben, die zeigen, wie man durch die Kombination mehrerer Underscore-Funktionen anspruchsvolle Dinge erreichen kann: