Quel est le moyen le plus efficace de grouper des objets dans un tableau?
Par exemple, étant donné ce tableau d'objets:
[
{ 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" }
]
J'affiche cette information dans un tableau. Je voudrais grouper par différentes méthodes, mais je veux additionner les valeurs.
J'utilise Underscore.js pour sa fonction groupby, ce qui est utile, mais ne fait pas l'affaire en entier, car je ne veux pas qu'ils soient "divisés" mais "fusionnés", plutôt que la méthode SQL group by
.
Ce que je recherche permettrait de totaliser des valeurs spécifiques (si demandé).
Donc, si j’ai groupé par Phase
, je voudrais recevoir:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Et si j’ai groupé Phase
/Step
, je recevrais:
[
{ 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 }
]
Existe-t-il un script utile pour cela, ou devrais-je m'en tenir à Underscore.js, puis parcourir en boucle l'objet résultant pour effectuer les totaux moi-même?
Si vous voulez éviter les bibliothèques externes, vous pouvez implémenter de manière concise une version Vanilla de groupBy()
comme ceci:
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"]}
Utilisation de l'objet Carte ES6:
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;
}
Exemple d'utilisation:
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/
À propos de la carte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
avec ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
Bien que la réponse linq soit intéressante, elle est également assez lourde. Mon approche est quelque peu différente:
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)});
});
Vous pouvez le voir en action sur JSBin .
Je n'ai rien vu dans Underscore qui fasse ce que has
fait, bien que cela puisse me manquer. C'est à peu près la même chose que _.contains
, mais utilise _.isEqual
plutôt que ===
pour les comparaisons. Autre que cela, le reste de ceci est spécifique au problème, bien qu'avec une tentative d'être générique.
Maintenant DataGrouper.sum(data, ["Phase"])
retourne
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
Et DataGrouper.sum(data, ["Phase", "Step"])
retourne
[
{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}
]
Mais sum
n'est qu'une fonction potentielle ici. Vous pouvez inscrire d'autres personnes comme vous le souhaitez:
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)});
});
et maintenant DataGrouper.max(data, ["Phase", "Step"])
va revenir
[
{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}
]
ou si vous avez enregistré ceci:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
puis appeler DataGrouper.tasks(data, ["Phase", "Step"])
vous obtiendrez
[
{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
est une fonction. Vous pouvez l'appeler avec vos données et une liste des propriétés que vous souhaitez regrouper. Il retourne un tableau dont les éléments sont des objets avec deux propriétés: key
est la collection de propriétés groupées, vals
est un tableau d'objets contenant les propriétés restantes non contenues dans la clé. Par exemple, DataGrouper(data, ["Phase", "Step"])
donnera:
[
{
"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
accepte une fonction et crée une nouvelle fonction qui accepte les données initiales et les propriétés à regrouper. Cette nouvelle fonction prend alors le format de sortie comme ci-dessus et l'exécute tour à tour pour renvoyer un nouveau tableau. La fonction générée est stockée en tant que propriété de DataGrouper
en fonction du nom que vous avez fourni et est également renvoyée si vous souhaitez simplement une référence locale.
Eh bien c'est beaucoup d'explications. Le code est assez simple, j'espère!
Ceci est probablement plus facile à faire avec linq.js
, qui est une véritable implémentation de LINQ en JavaScript ( DÉMO ):
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();
résultat:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Ou, plus simplement, en utilisant les sélecteurs basés sur des chaînes ( DEMO ):
linq.GroupBy("$.Phase", "",
"k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
Je voudrais vérifier lodash groupBy il semble faire exactement ce que vous recherchez. Il est également très léger et très simple.
Exemple de violon: https://jsfiddle.net/r7szvt5k/
Si votre nom de groupe est arr
, le groupBy with lodash est simplement:
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
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}
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]
};
});
};
Vous pouvez le faire avec Alasql librairie JavaScript:
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]);
Essayez cet exemple à jsFiddle .
BTW: Sur les grands tableaux (100 000 enregistrements et plus) plus rapide que Linq. Voir test à jsPref .
Commentaires:
Vous pouvez créer une ES6 Map
à partir de array.reduce()
.
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
Cela présente quelques avantages par rapport aux autres solutions:
_.groupBy()
)Map
plutôt qu'un objet (par exemple, tel que renvoyé par _.groupBy()
). Cela a de nombreux avantages , notamment: Map
est un résultat plus utile qu'un tableau de tableaux. Mais si vous voulez un tableau de tableaux, vous pouvez alors appeler Array.from(groupedMap.entries())
(pour un tableau de paires [key, group array]
) ou Array.from(groupedMap.values())
(pour un tableau simple de tableaux).Comme exemple du dernier point, imaginons que j’ai un tableau d’objets sur lequel je veux faire une fusion (peu profonde) par id, comme ceci:
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"}]
Pour ce faire, je commencerais généralement par regrouper par ID, puis par fusionner chacun des tableaux résultants. Au lieu de cela, vous pouvez faire la fusion directement dans la reduce()
:
const mergedArray = Array.from(
objsToMerge.reduce(
(entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
new Map()
).values()
);
Bien que la question ait des réponses et que les réponses semblent un peu compliquées, je suggère d'utiliser Javascript Javascript pour les groupes.
Cette solution comporte une fonction qui prend un tableau avec les données array
et un nom de propriété pour return col
et un nom de propriété pour compter une valeur value
.
La fonction repose sur un objet, qui agit comme une table de hachage pour le résultat.
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>');
MDN a cet exemple dans sa documentation Array.reduce()
.
// 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 }]
// }
Cette solution prend n'importe quelle fonction arbitraire (pas une clé), elle est donc plus flexible que les solutions précédentes et autorise fonctions de flèche , qui sont similaires à expressions lambda utilisé dans LINQ:
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).Push(val);
return acc;
}, {});
};
REMARQUE: le choix de prolonger le prototype de Array
dépend de vous.
Exemple supporté par la plupart des navigateurs:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
Exemple utilisant les fonctions de flèche (ES6):
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
Les deux exemples ci-dessus renvoient:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
Sans mutations:
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"]}
je voudrais suggérer mon approche. Tout d'abord, séparez le regroupement et l'agrégation. Permet de déclarer la fonction prototype "groupe par". Il faut une autre fonction pour produire la chaîne "hash" pour chaque élément de tableau à grouper.
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};
});
}
lorsque le regroupement est terminé, vous pouvez regrouper les données dont vous avez besoin, dans votre cas
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;
});
en commun cela fonctionne. J'ai testé ce code en console chromée. et n'hésitez pas à vous améliorer et à trouver des erreurs;)
Basé sur les réponses précédentes
const groupBy = (prop) => (xs) =>
xs.reduce((rv, x) =>
Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});
et c'est un peu plus agréable de regarder avec la syntaxe de propagation d'objet, si votre environnement le supporte.
const groupBy = (prop) => (xs) =>
xs.reduce((acc, x) => ({
...acc,
[ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
}), {});
Ici, notre réducteur prend la valeur de retour partiellement formée (en commençant par un objet vide), et retourne un objet composé des membres étalés de la valeur de retour précédente, avec un nouveau membre dont la clé est calculée à partir de la valeur de l'itérée actuelle prop
et dont la valeur est une liste de toutes les valeurs de cet accessoire avec la valeur actuelle.
La réponse de Ceasar est bonne, mais ne fonctionne que pour les propriétés internes des éléments à l'intérieur du tableau (longueur dans le cas d'une chaîne).
cette implémentation fonctionne plus comme: ce lien
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;
}, {});
};
j'espère que cela t'aides...
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;
}, []);
}
Celui-ci sort un tableau.
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>
Permet de générer un outil Array.prototype.groupBy()
générique. Juste pour la variété, utilisons ES6 fantaisie, l’opérateur de propagation pour certains motifs de Haskellesque correspondant à une approche récursive. Faisons également en sorte que notre Array.prototype.groupBy()
accepte un rappel qui prend pour élément (e
) l'index (i
) et le tableau appliqué (a
) en tant qu'arguments.
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);
Réponse cochée - ne pas grouper résoudre, est cependant une réponse directe.
REAL GROUP BY pour Tableau d'objets par un champ avec un nom de clé calculé.
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);
Jouez avec estKey
- vous pouvez grouper par plus d'un champ
Vous pouvez aussi grouper les données de manière récursive. Par exemple, groupez initialement par Phase
, puis par Step
champ.
Vérifiez-le par vous-même, lancez-le. То то самое оно, что люди называют группировкой?
Je vous souhaite un succès.
Да здравствуют высокие показатели мастерства программистов Ра, товарищи!
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'));
ES6 reduce
version basée avec le support de la fonction iteratee
.
Fonctionne comme prévu si la fonction iteratee
n'est pas fournie:
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
Dans le contexte de la question du PO:
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
Une autre version ES6 qui inverse le regroupement et utilise values
comme keys
et la keys
comme 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))
Remarque: les fonctions sont affichées sous leur forme abrégée (une ligne) par souci de concision et pour ne faire que relater l'idée. Vous pouvez les développer et ajouter une vérification d'erreur supplémentaire, etc.
Avec fonction de tri
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;
}, []);
};
Échantillon:
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')));
Voici une version ES6 qui ne cassera pas sur les membres nuls
function groupBy (arr, key) {
return (arr || []).reduce((acc, x = {}) => ({
...acc,
[x[key]]: [...acc[x[key]] || [], x]
}), {})
}
De @mortb, @jmarceli répond et de cet article ,
Je profite de JSON.stringify()
pour être l’identité de plusieurs colonnes de groupe par PRIMITIVE VALUE.
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);
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);
Pour ajouter au commentaire answer de Scott Sauyet, certaines personnes demandaient dans les commentaires comment utiliser sa fonction pour grouper valeur1, valeur2, etc.
Il suffit d’éditer sa fonction sum:
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)}
);
});
laissant le principal (DataGrouper) inchangé:
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;
}());
Sur la base de l'idée originale de @Ceasar Bautista , j'ai modifié le code et créé une fonction groupBy à l'aide de TypeScript.
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;
}, []);
};
Cette fonction accepte un rappel (comparateur) qui compare les lignes et trouve les doublons et un second rappel (onDublicate) qui regroupe les doublons.
exemple d'utilisation:
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'];
};
appel
groupBy(data, demoComparator, demoOnDublicate)
effectuera un groupe par qui calcule la somme de la valeur.
{name: "a", value: 33}
{name: "b", value: 21}
Nous pouvons créer autant de fonctions de rappel que le projet l'exige et agréger les valeurs si nécessaire. Dans un cas, par exemple, j'avais besoin de fusionner deux tableaux au lieu de sommer les données.
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"
}
]
}
]
*/
Vous pouvez utiliser forEach
sur array et construire un nouveau groupe d'éléments. Voici comment faire cela avec l'annotation FlowType
// @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
Un test de plaisanterie peut être comme
// @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')
})
J'ai emprunté cette méthode à underscore.js fiddler
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;
}));
J'ai développé la réponse acceptée pour inclure le regroupement par plusieurs propriétés, puis ajouter et la rendre purement fonctionnelle sans mutation. Voir une démo sur 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;
};
La fonction ci-dessous permet de grouper (et de sommer - ce dont l'OP a besoin) de champs arbitraires. Dans la solution, nous définissons la fonction cmp
pour comparer deux objets en fonction de fields
groupé. Dans let w=...
, nous créons une copie des sous-ensembles d'objets x. Dans y[sumBy]=+y[sumBy]+(+x[sumBy])
, nous utilisons '+' pour convertir une chaîne en nombre.
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']) );
Voici une solution difficile à lire utilisant ES6:
export default (array, key) => {
return array.reduce(
(r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).Push(v), r),
{}
);
};
Je voudrais vérifier déclarative-jsgroupBy
il semble faire exactement ce que vous recherchez. C'est aussi:
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())
Vous pouvez le faire de la manière suivante. Je viens de former un nouveau tableau et je le retourne de groupBy function. Nombre calculé à partir de la mise en boucle par .map function
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'));
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;
}, {})