Quelle est la méthode la plus efficace pour groupby sur un tableau JavaScript d'objets?
Quelle est la manière la 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. J'aimerais Grouper par différentes méthodes, mais je veux additionner les valeurs.
J'utilise un Underscore.js pour sa fonction groupby, qui est utile, mais ne fait pas tout le tour, parce que je ne veux pas qu'ils "se séparent" mais "fusionnent", plus comme le SQL group by
la méthode.
ce que je cherche pourrait totaliser des valeurs spécifiques (si demandé).
donc si je faisais groupby Phase
,je voudrais recevoir:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Et si je n'ai groupy Phase
/ Step
, j'aimerais recevoir:
[
{ 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 }
]
y a-t-il un script utile pour cela, ou dois-je m'en tenir à L'utilisation de Underscore.js, et ensuite en boucle à travers l'objet résultant pour faire les totaux moi-même?
30 réponses
si vous voulez éviter les bibliothèques externes, vous pouvez implémenter de manière concise une version vanille de groupBy()
comme suit:
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"]}
utilisant ES6 objet de la carte:
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'usage:
const pets = [
{type:"Dog", name:"Spot"},
{type:"Cat", name:"Tiger"},
{type:"Dog", name:"Rover"},
{type:"Cat", name:"Leo"}
];
const grouped = groupBy(pets, pet => pet.type);
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
jsfiddle: https://jsfiddle.net/buko8r5d/
A Propos De La Carte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
bien que la réponse linq soit intéressante, elle est aussi 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 voir en action sur JSBin .
Je n'ai rien vu dans Underscore qui fait ce que has
fait, bien que je puisse le manquer. Il est à peu près le même que _.contains
, mais utilise _.isEqual
plutôt que ===
pour les comparaisons. Autre que cela, le reste de ce problème, bien qu'avec une tentative d'être générique.
Maintenant DataGrouper.sum(data, ["Phase"])
renvoie
[
{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 enregistrer les autres comme vous voulez:
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"])
retournera
[
{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é ce:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
puis appel 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
lui-même est une fonction. Vous pouvez appeler avec vos données et une liste de propriétés que vous souhaitez regrouper. Il retourne un tableau dont les éléments sont un objet 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 dans le 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 de groupe. Cette nouvelle fonction prend alors le format de sortie comme ci-dessus et exécute votre fonction contre chacun d'eux à son tour, retournant un nouveau tableau. La fonction qui est générée est stockée comme une propriété de DataGrouper
selon un nom que vous fournissez et aussi retourné si vous voulez juste un local référence.
C'est beaucoup d'explications. Le code est relativement simple, je l'espère!
c'est probablement plus facile à faire avec linq.js
, qui est destiné à être une véritable mise en œuvre 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 les chaînes de caractères ( Démo ):
linq.GroupBy("$.Phase", "",
"k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
avec ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
je voudrais vérifier lodash groupe il semble faire exactement ce que vous cherchez. Il est également très léger et très simple.
exemple de violon: https://jsfiddle.net/r7szvt5k /
pourvu que votre nom de tableau soit arr
le groupBy avec lodash est juste:
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]}
vous pouvez le faire avec Alasql bibliothèque 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 de grands tableaux (100000 enregistrements et de plus en plus) Alasql plus rapide tham Linq. Voir le test à jsPref .
commentaires:
- ici je mets la valeur en carré crochets, parce que la valeur est un mot-clé dans SQL
- je dois utiliser la fonction CAST() pour convertir des valeurs de chaîne en type de nombre.
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 construire un 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:
- il ne nécessite pas de bibliothèques (contrairement par exemple
_.groupBy()
) - vous obtenez un JavaScript
Map
plutôt qu'un objet (par exemple retourné par_.groupBy()
). Cela a beaucoup d'avantages , y compris:- il se souvient de l'ordre dans lequel les articles ont été ajoutés pour la première fois,
- les clés peuvent être n'importe quel type plutôt que seulement des chaînes.
- Un
Map
est plus utile pour résultat qu'un tableau de tableaux. Mais si vous voulez un tableau de tableaux, vous pouvez ensuite appelerArray.from(groupedMap.entries())
(pour un tableau de[key, group array]
paires) ouArray.from(groupedMap.values())
(pour un simple tableau de tableaux). - c'est assez flexible; souvent, quoi que vous planifiez de faire ensuite avec cette carte peut être fait directement dans le cadre de la réduction.
comme exemple du dernier point, imaginez que j'ai un tableau d'objets que je veux faire une fusion (superficielle) 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 habituellement par Grouper par id, puis fusionner chacun des tableaux résultants. Au lieu de cela, vous pouvez faire la fusion directement dans le 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 trop compliquées, je suggère d'utiliser le Javascript de la vanille pour group-by.
cette solution dispose d'une fonction qui prend un tableau avec les données array
et un nom de propriété pour le retour col
et un nom de propriété pour compter une valeur value
.
, cette fonction s'appuie 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>');
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"]}
j'aimerais suggérer mon approche. Tout d'abord, regrouper et regrouper séparément. Permet de déclarer prototypique "groupe" de la fonction. Il faut une autre fonction pour produire une chaîne de "hachage" pour chaque élément du 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};
});
}
quand le regroupement est fait, vous pouvez agréger des données comme 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 ça marche. j'ai testé ce code dans la console chrome. et n'hésitez pas à progresser et à trouver des erreurs ;)
cette solution prend n'importe quelle fonction arbitraire (pas une clé) de sorte qu'il est plus flexible que les solutions ci-dessus, et permet fonctions Flèche 151980920" ", qui sont similaires à lambda expressions utilisé dans LINQ :
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
NOTE: c'est à vous de décider si vous souhaitez étendre le prototype de Array
.
exemple pris en charge dans la plupart des navigateurs:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
exemple utilisant des fonctions de flèche (ES6):
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
deux exemples ci-dessus retour:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
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 à regarder avec la syntaxe de propagation d'objet, si votre environnement prend en charge.
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 (à partir d'un objet vide), et renvoie 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 actuelle de l'itéré à prop
et dont la valeur est une liste de toutes les valeurs de cette prop avec la valeur actuelle.
permet de générer un outil générique Array.prototype.groupBy()
. Juste pour la variété employons ES6 fanciness l'opérateur de propagation pour un certain motif haskellesque appariement sur une approche récursive. Faisons aussi notre Array.prototype.groupBy()
pour accepter un callback qui prend l'élément ( e
) l'index ( i
) et le tableau appliqué ( a
) comme 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);
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 en cas de 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;
}, {});
};
espérons que cette aide...
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>
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 }]
// }
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;
}, []);
}
ce tableau de sortie.
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'));
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]
}), {})
}
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')));
De @mortb, @jmarceli réponse et de ce post ,
je prends le parti de JSON.stringify()
à l'identité de la VALEUR PRIMITIVE plusieurs colonnes de groupe.
sans tiers
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);
avec Lodash tiers
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);
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;
}));
juste pour ajouter à la réponse de Scott Sauyet 151940920" , certaines personnes demandaient dans les commentaires comment utiliser sa fonction de groupby value1, value2, etc., au lieu de regrouper une seule valeur.
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)}
);
});
en laissant la principale (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;
}());
vous pouvez le faire de la façon suivante. Je viens de former un nouveau tableau et je l'ai retourné de la fonction groupBy . Compte calculé à partir de la boucle juste par .carte fonction
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'));
j'ai développé la réponse acceptée pour inclure le groupement par propriétés multiples, Ajouter thenby et le rendre purement fonctionnel sans mutation. Voir la démo à 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;
};
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;
}, {})
basé sur l'idée originale de @Ceasar Bautista , j'ai modifié le code et créé une fonction groupBy en tapant.
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 callback (comparateur) qui compare les lignes et trouve les dublicates et un second callback (ondulicate) qui agrège les dublicates.
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)
va effectuer un groupe par qui calcule la somme de valeur.
{name: "a", value: 33}
{name: "b", value: 21}
nous pouvons créer autant de ces fonctions de rappel que requis par le projet et agréger les valeurs si nécessaire. Dans un cas, par exemple, j'ai besoin de fusionner deux tableaux au lieu d'additionner les données.