¿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 diferentes métodos, 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 group by
método SQL .
Lo que estoy buscando podría sumar valores específicos (si se solicita).
Entonces, si hice groupby Phase
, me gustarí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 }
]
¿Hay un script útil para esto, o debería seguir usando Underscore.js, y luego recorrer el objeto resultante para hacer los totales yo mismo?
var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Usando el objeto ES6 Map:
Acerca del mapa: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
fuente
get()
método? lo que quiero es que la salida se muestre sin pasar la claveconsole.log(grouped.entries());
en el ejemplo jsfiddle, devuelve un iterable que se comporta como una matriz de claves + valores. ¿Puedes probar eso y ver si te ayuda?console.log(Array.from(grouped));
Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
con ES6:
fuente
...result
. Ahora no puedo dormir por eso.Aunque la respuesta de linq es interesante, también es bastante pesada. Mi enfoque es algo diferente:
Puedes verlo en acción en JSBin .
No vi nada en Underscore que haga lo que
has
hace, aunque podría estar perdiéndolo. Es casi lo mismo que_.contains
, pero utiliza en_.isEqual
lugar de===
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"])
vuelveY
DataGrouper.sum(data, ["Phase", "Step"])
vuelvePero aquí
sum
solo hay una función potencial. Puedes registrar a otros como quieras:y ahora
DataGrouper.max(data, ["Phase", "Step"])
volveremoso si registraste esto:
entonces llamar
DataGrouper.tasks(data, ["Phase", "Step"])
te llevaráDataGrouper
En sí es una función. Puede llamarlo con sus datos y una lista de las propiedades por las que desea agrupar. Devuelve una matriz cuyos elementos son objetos con dos propiedades:key
es la colección de propiedades agrupadas,vals
es una matriz de objetos que contiene las propiedades restantes que no están en la clave. Por ejemplo,DataGrouper(data, ["Phase", "Step"])
rendirá:DataGrouper.register
acepta una función y crea una nueva función que acepta los datos iniciales y las propiedades para agrupar. Esta nueva función luego toma el formato de salida como se indica 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 deDataGrouper
acuerdo con un nombre que proporcione y también se devuelve si solo desea una referencia local.Bueno, eso es mucha explicación. ¡El código es razonablemente sencillo, espero!
fuente
Verificaría lodash group: parece que hace exactamente lo que estás buscando. También es bastante ligero y realmente simple.
Ejemplo de Fiddle: https://jsfiddle.net/r7szvt5k/
Siempre que su nombre de matriz sea
arr
el grupo, con lodash es solo:fuente
Probablemente esto se haga más fácilmente
linq.js
, lo que pretende ser una verdadera implementación de LINQ en JavaScript ( DEMO ):resultado:
O, más simplemente usando los selectores basados en cadenas ( DEMO ):
fuente
GroupBy(function(x){ return x.Phase; })
Puedes construir un ES6
Map
desdearray.reduce()
.Esto tiene algunas ventajas sobre las otras soluciones:
_.groupBy()
)Map
lugar de un objeto (por ejemplo, como lo devuelve_.groupBy()
). Esto tiene muchos beneficios , que incluyen:Map
es un resultado más útil que una matriz de matrices. Pero si desea una matriz de matrices, puede llamarArray.from(groupedMap.entries())
(para una matriz de[key, group array]
pares) oArray.from(groupedMap.values())
(para una matriz simple de matrices).Como ejemplo del último punto, imagine que tengo una matriz de objetos en los que quiero hacer una fusión (superficial) por id, como esta:
Para hacer esto, generalmente comenzaría agrupando por id y luego fusionando cada una de las matrices resultantes. En cambio, puede hacer la fusión directamente en
reduce()
:fuente
a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map())
aproximadamente)De: http://underscorejs.org/#groupBy
fuente
Puedes hacerlo con la biblioteca JavaScript de Alasql :
Pruebe este ejemplo en jsFiddle .
Por cierto: en matrices grandes (100000 registros y más) Alasql más rápido que Linq. Ver prueba en jsPref .
Comentarios:
fuente
fuente
MDN tiene este ejemplo en su
Array.reduce()
documentación.fuente
Aunque la pregunta tiene algunas respuestas y las respuestas parecen un poco complicadas, sugiero usar Javascript de vainilla para agrupar con un anidado (si es necesario)
Map
.fuente
Sin mutaciones:
fuente
Esta solución toma cualquier función arbitraria (no una clave), por lo que es más flexible que las soluciones anteriores y permite funciones de flecha , que son similares a las expresiones lambda utilizadas en LINQ :
NOTA: si desea extender
Array
el prototipo depende de usted.Ejemplo admitido en la mayoría de los navegadores:
Ejemplo usando funciones de flecha (ES6):
Ambos ejemplos anteriores devuelven:
fuente
let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
Me gustaría sugerir mi enfoque. Primero, agrupar y agregar por separado. Vamos a declarar la función prototípica "agrupar por". Se necesita otra función para producir una cadena "hash" para cada elemento de matriz para agrupar.
cuando se realiza la agrupación, puede agregar datos como lo necesite, en su caso
en común funciona. He probado este código en la consola de Chrome. y siéntase libre de mejorar y encontrar errores;)
fuente
map[_hash(key)].key = key;
amap[_hash(key)].key = _hash(key);
.Imagina que tienes algo como esto:
[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]
Al hacer esto:
const categories = [...new Set(cars.map((car) => car.cat))]
Obtendrás esto:
['sedan','sport']
Explicación: 1. Primero, estamos creando un nuevo conjunto pasando una matriz. Debido a que Set solo permite valores únicos, se eliminarán todos los duplicados.
Establecer documento: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Referencia / Operadores / Spread_syntax
fuente
Respuesta marcada: solo agrupación superficial. Es bastante agradable entender la reducción. La pregunta también proporciona el problema de los cálculos agregados adicionales.
Aquí hay un GRUPO REAL POR para la matriz de objetos por algunos campos con 1) nombre de clave calculado y 2) solución completa para la agrupación en cascada al proporcionar la lista de las claves deseadas y convertir sus valores únicos a claves raíz como SQL GROUP BY lo hace.
Juegue con
estKey
: puede agrupar por más de un campo, agregar agregaciones adicionales, cálculos u otro procesamiento.También puede agrupar datos de forma recursiva. Por ejemplo, inicialmente agrupar por
Phase
, luego porStep
campo y así sucesivamente. Además, elimine los datos de reposo gordo.fuente
Esta salida de matriz.
fuente
Basado en respuestas anteriores
y es un poco más agradable de ver con la sintaxis de propagación de objetos, si su entorno lo admite.
Aquí, nuestro reductor toma el valor de retorno parcialmente formado (comenzando con un objeto vacío) y devuelve un objeto compuesto por los miembros extendidos del valor de retorno anterior, junto con un nuevo miembro cuya clave se calcula a partir del valor actual del árbol en
prop
y cuyo valor es una lista de todos los valores para ese accesorio junto con el valor actual.fuente
fuente
Aquí hay una solución desagradable y difícil de leer con ES6:
Para los que preguntan cómo hace esto , incluso el trabajo, aquí hay una explicación:
En ambos
=>
tienes un librereturn
La
Array.prototype.reduce
función toma hasta 4 parámetros. Es por eso que se agrega un quinto parámetro para que podamos tener una declaración de variable barata para el grupo (k) en el nivel de declaración de parámetro mediante el uso de un valor predeterminado. (sí, esto es brujería)Si nuestro grupo actual no existe en la iteración anterior, creamos una nueva matriz vacía
((r[k] || (r[k] = []))
Esto evaluará a la expresión más a la izquierda, en otras palabras, una matriz existente o una matriz vacía , es por eso que hay una inmediatapush
después de esa expresión, porque De cualquier manera obtendrá una matriz.Cuando hay un
return
, el,
operador de coma descartará el valor más a la izquierda, devolviendo el grupo anterior ajustado para este escenario.Una versión más fácil de entender que hace lo mismo es:
fuente
Vamos a generar una
Array.prototype.groupBy()
herramienta genérica . Solo por variedad, usemos la fantasía de ES6, el operador de propagación para algunas coincidencias de patrones Haskellesque en un enfoque recursivo. También hagamosArray.prototype.groupBy()
que aceptemos una devolución de llamada que toma el elemento (e
), el índice (i
) y la matriz aplicada (a
) como argumentos.fuente
La respuesta de Ceasar es buena, pero solo funciona para las propiedades internas de los elementos dentro de la matriz (longitud en el caso de una cadena).
esta implementación funciona más como: este enlace
espero que esto ayude...
fuente
De @mortb, @jmarceli responde y de esta publicación ,
Aprovecho
JSON.stringify()
para ser la identidad del VALOR PRIMITIVO de varias columnas de grupo por.Sin un tercero
Con un tercero de Lodash
fuente
reduce
Versión basada en ES6 con soporte para funcióniteratee
.Funciona como se esperaba si
iteratee
no se proporciona la función:En el contexto de la pregunta OP:
Otra versión de ES6 que invierte la agrupación y usa el
values
askeys
y elkeys
as elgrouped values
:Nota: las funciones se publican en su forma abreviada (una línea) por brevedad y para relacionar solo la idea. Puede expandirlos y agregar verificación de errores adicional, etc.
fuente
Verificaría declarative-js
groupBy
, parece que hace exactamente lo que está buscando. Tambien es:fuente
fuente
Aquí hay una versión ES6 que no se romperá en miembros nulos
fuente
Solo para agregar a la respuesta de Scott Sauyet , algunas personas preguntaban en los comentarios cómo usar su función para agrupar por valor1, valor2, etc., en lugar de agrupar solo un valor.
Todo lo que se necesita es editar su función de suma:
dejando el principal (DataGrouper) sin cambios:
fuente
Con función de clasificación
Muestra:
fuente