javascript - metodos - El método más eficiente para agruparse en una matriz de objetos
object.keys map (30)
¿Cuál es la forma más eficiente de agrupar objetos en una matriz?
Por ejemplo, dada esta matriz de objetos:
[
{ 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" }
]
Estoy mostrando esta información en una tabla. Me gustaría agrupar por métodos diferentes, pero quiero sumar los valores.
Estoy usando Underscore.js para su función groupby, que es útil, pero no hace todo el truco, porque no quiero que se "dividan" sino que se "fusionen", más como el método SQL group by
método.
Lo que estoy buscando podría ser capaz de totalizar valores específicos (si se solicita).
Así que si lo hice grupalmente, querría recibir:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Y si hiciera Groupy Phase
/ Step
, recibiría:
[
{ 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 un script útil para esto, o debo seguir usando Underscore.js y luego recorrer el objeto resultante para hacer los totales yo mismo?
Aquí hay una versión de ES6 que no se romperá en miembros nulos.
function groupBy (arr, key) {
return (arr || []).reduce((acc, x = {}) => ({
...acc,
[x[key]]: [...acc[x[key]] || [], x]
}), {})
}
Aunque la pregunta tiene algunas respuestas y las respuestas parecen un poco complicadas, sugiero usar Javascript de vainilla para agrupar.
Esta solución presenta una función que toma una matriz con una matriz de datos y un nombre de propiedad para la col
devolución y un nombre de propiedad para contar un valor de value
.
La función se basa en un objeto, que actúa como una tabla hash para el resultado.
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>'');
Aunque la respuesta de linq es interesante, también es bastante pesada. Mi enfoque es algo diferente:
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)});
});
Puedes verlo en acción en JSBin .
No vi nada en el guión bajo que haga lo que has
hacer, aunque podría faltar. Es muy _.contains
, pero usa _.isEqual
lugar de ===
para las comparaciones. Aparte de eso, el resto de esto es específico del problema, aunque con un intento de ser genérico.
Ahora DataGrouper.sum(data, ["Phase"])
devuelve
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
Y DataGrouper.sum(data, ["Phase", "Step"])
devuelve
[
{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}
]
Pero la sum
es solo una función potencial aquí. Puedes registrar otros como quieras:
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)});
});
y ahora DataGrouper.max(data, ["Phase", "Step"])
regresará
[
{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}
]
o si has registrado esto:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
luego, llamando a DataGrouper.tasks(data, ["Phase", "Step"])
obtendrás
[
{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
sí es una función. Puede llamarlo con sus datos y una lista de las propiedades que desea agrupar por. Devuelve una matriz cuyos elementos son objeto con dos propiedades: key
es la colección de propiedades agrupadas, vals
es una matriz de objetos que contienen las propiedades restantes que no están en la clave. Por ejemplo, DataGrouper(data, ["Phase", "Step"])
producirá:
[
{
"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
acepta una función y crea una nueva función que acepta los datos iniciales y las propiedades para agrupar por. Esta nueva función toma el formato de salida como se muestra arriba y ejecuta su función contra cada una de ellas, devolviendo una nueva matriz. La función que se genera se almacena como una propiedad de DataGrouper
acuerdo con el nombre que proporciona y también se devuelve si solo desea una referencia local.
Bueno, eso es mucha explicación. El código es razonablemente sencillo, espero!
Basándome en la idea original de , modifiqué el código y creé una función groupBy usando mecanografía.
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;
}, []);
};
Esta función acepta una devolución de llamada (comparador) que compara las filas y encuentra las duplicadas y una segunda devolución de llamada (onDublicate) que agrega las duplicadas.
ejemplo de uso:
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''];
};
vocación
groupBy(data, demoComparator, demoOnDublicate)
Realizará un grupo por el que calcula la suma de valor.
{name: "a", value: 33}
{name: "b", value: 21}
Podemos crear tantas de estas funciones de devolución de llamada como lo requiera el proyecto y agregar los valores según sea necesario. En un caso, por ejemplo, necesitaba unir dos matrices en lugar de sumar los datos.
Basado en respuestas anteriores
const groupBy = (prop) => (xs) =>
xs.reduce((rv, x) =>
Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});
y es un poco más agradable de ver con la sintaxis de propagación de objetos, si su entorno es compatible.
const groupBy = (prop) => (xs) =>
xs.reduce((acc, x) => ({
...acc,
[ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
}), {});
Aquí, nuestro reductor toma el valor de retorno parcialmente formado (comenzando con un objeto vacío), y devuelve un objeto compuesto por los miembros dispersos del valor de retorno anterior, junto con un nuevo miembro cuya clave se calcula a partir del valor actual del titular en prop
y cuyo valor es una lista de todos los valores para ese prop junto con el valor actual.
Con función de clasificación
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;
}, []);
};
Muestra:
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 responda y de este post ,
Aprovecho la ventaja de JSON.stringify()
para ser la identidad de las columnas múltiples de PRIMITIVE VALUE por.
Sin tercero
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);
Con Lodash tercero
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);
Esta solución toma cualquier función arbitraria (no una clave), por lo que es más flexible que las soluciones anteriores y permite las funciones de flecha , que son similares a las expresiones lambda usadas en LINQ :
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
NOTA: si desea extender el prototipo de Array
depende de usted.
Ejemplo soportado en la mayoría de los navegadores:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
Ejemplo usando funciones de flecha (ES6):
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
Ambos ejemplos anteriores regresan:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
Esto probablemente se haga más fácilmente con linq , que pretende ser una verdadera implementación de LINQ en JavaScript ( 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();
resultado:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
O, más simplemente usando los selectores basados en cadenas ( DEMO ):
linq.GroupBy("$.Phase", "",
"k,e => { Phase:k, Value:e.Sum(''$.Value|0'') }").ToArray();
He ampliado la respuesta aceptada para incluir la agrupación por múltiples propiedades, agregarla luego y hacerla puramente funcional sin mutación. Vea una demostración en 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 respuesta de Ceasar es buena, pero funciona solo para las propiedades internas de los elementos dentro de la matriz (longitud en caso de cadena).
Esta implementación funciona más como: este enlace.
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;
}, {});
};
espero que esto ayude...
MDN tiene este ejemplo en su documentación 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 }]
// }
Me gustaría sugerir mi enfoque. Primero, agrupar y agregar por separado. Permite declarar prototípicamente la función "agrupar por". Se necesita otra función para producir una cadena "hash" para que cada elemento de la matriz se agrupe.
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};
});
}
cuando se realiza la agrupación, puede agregar datos como necesite, en su caso
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 común funciona. He probado este código en la consola de Chrome. y siéntase libre de mejorar y encontrar errores;)
Permite generar una herramienta genérica Array.prototype.groupBy()
. Solo por variedad, utilicemos la elegancia de ES6 como operador de propagación para algunos patrones de haskelles en un enfoque recursivo. También hagamos que nuestro Array.prototype.groupBy()
acepte una devolución de llamada que tome el elemento ( e
) el índice ( i
) y la matriz aplicada ( a
) como argumentos.
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);
Podrías hacerlo de la siguiente manera. Acabo de formar una nueva matriz y la devolví de la función groupBy . Recuento calculado a partir de solo bucle por función .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''));
Puedes construir un Map
ES6 desde array.reduce()
.
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
Esto tiene algunas ventajas sobre las otras soluciones:
- No requiere ninguna biblioteca (a diferencia de, por ejemplo,
_.groupBy()
) - Obtiene un
Map
JavaScript en lugar de un objeto (por ejemplo, como lo devuelve_.groupBy()
). Esto tiene muchos beneficios , incluyendo:- recuerda el orden en que se agregaron los artículos por primera vez,
- Las claves pueden ser de cualquier tipo en lugar de cadenas.
- Un
Map
es un resultado más útil que una matriz de matrices. Pero si desea una matriz de matrices, puede llamar aArray.from(groupedMap.entries())
(para una matriz de pares[key, group array]
) oArray.from(groupedMap.values())
(para una simple matriz de matrices). - Es bastante flexible; a menudo, todo lo que planea hacer a continuación con este mapa se puede hacer directamente como parte de la reducción.
Como ejemplo del último punto, imagine que tengo una matriz de objetos en los que quiero hacer una fusión (superficial) por ID, como esto:
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"}]
Para hacer esto, generalmente comenzaría agrupando por id, y luego fusionando cada uno de los arreglos resultantes. En su lugar, puede hacer la combinación directamente en la reduce()
:
const mergedArray = Array.from(
objsToMerge.reduce(
(entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
new Map()
).values()
);
Puedes hacerlo con la biblioteca de JavaScript de Alasql :
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]);
Prueba este ejemplo en jsFiddle .
Por cierto: en arreglos grandes (100000 registros y más) Alasql más rápido que Linq. Ver prueba en jsPref .
Comentarios:
- Aquí pongo el valor entre corchetes, porque VALUE es una palabra clave en SQL
- Tengo que usar la función CAST () para convertir los valores de cadena al tipo de número.
Si desea evitar bibliotecas externas, puede implementar de forma concisa una versión de vainilla de groupBy()
así:
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"]}
Sin mutaciones:
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"]}
Solo para agregar a la answer Scott Sauyet, algunas personas preguntaban en los comentarios cómo usar su función para agrupar valor1, valor2, etc., en lugar de agrupar solo un valor.
Todo lo que necesitas es editar su función de suma:
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)}
);
});
dejando el principal (DataGrouper) sin cambios:
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;
}());
Tomé prestado este método de 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;
}));
Usando el objeto de mapa 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;
}
Ejemplo de uso:
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/
Acerca del mapa: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
con ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
Revisaría el grupo de lodash, porque parece hacer exactamente lo que estás buscando. También es bastante ligero y muy simple.
Ejemplo de Fiddle: https://jsfiddle.net/r7szvt5k/
Siempre que el nombre de su matriz sea arr
groupBy with lodash es solo:
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
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>
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]
};
});
};
_.groupBy([{tipo: ''A'' },{tipo: ''A''}, {tipo: ''B''}], ''tipo'');
>> Object {A: Array[2], B: Array[1]}
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;
}, {})
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;
}, []);
}
Éste da salida a la matriz.
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''));