Qual é a maneira mais eficiente de agrupar objetos em uma matriz?
Por exemplo, dada essa 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" }
]
Estou exibindo essas informações em uma tabela. Gostaria de agrupar por métodos diferentes, mas quero somar os valores.
Estou usando o Underscore.js para sua função de agrupamento, o que é útil, mas não faz todo o truque, porque não quero que eles sejam "divididos", mas "mesclados", mais como o group by
método SQL .
O que eu estou procurando seria capaz de totalizar valores específicos (se solicitado).
Então, se eu fiz groupby Phase
, eu gostaria de receber:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
E se eu fiz groupy Phase
/ Step
, receberia:
[
{ 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 um script útil para isso ou devo usar o Underscore.js e depois percorrer o objeto resultante para fazer os totais sozinho?
fonte
var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Usando o objeto ES6 Map:
Sobre o mapa: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
fonte
get()
método? que é que eu quero que a saída seja exibida sem passar a chaveconsole.log(grouped.entries());
no exemplo jsfiddle, ele retornará um iterável que se comporta como uma matriz de chaves + valores. Você pode tentar isso e ver se isso ajuda?console.log(Array.from(grouped));
Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
com ES6:
fonte
...result
. Agora não consigo dormir por causa disso.Embora a resposta linq seja interessante, também é bastante pesada. Minha abordagem é um pouco diferente:
Você pode vê-lo em ação no JSBin .
Eu não vi nada no Underscore que faça o que
has
faz, embora eu possa estar perdendo. É quase o mesmo que_.contains
, mas usa_.isEqual
mais do que===
para comparações. Fora isso, o restante é específico do problema, embora com uma tentativa de ser genérico.Agora
DataGrouper.sum(data, ["Phase"])
retornaE
DataGrouper.sum(data, ["Phase", "Step"])
retornaMas
sum
é apenas uma função potencial aqui. Você pode registrar outras pessoas como desejar:e agora
DataGrouper.max(data, ["Phase", "Step"])
vai voltarou se você registrou isso:
então ligar
DataGrouper.tasks(data, ["Phase", "Step"])
vai te levarDataGrouper
em si é uma função. Você pode chamá-lo com seus dados e uma lista das propriedades pelas quais deseja agrupar. Ele retorna uma matriz cujos elementos são objetos com duas propriedades:key
é a coleção de propriedades agrupadas,vals
é uma matriz de objetos que contém as propriedades restantes que não estão na chave. Por exemplo,DataGrouper(data, ["Phase", "Step"])
produzirá:DataGrouper.register
aceita uma função e cria uma nova função que aceita os dados iniciais e as propriedades para agrupar. Essa nova função pega o formato de saída como acima e executa sua função em cada uma delas, retornando uma nova matriz. A função gerada é armazenada como uma propriedade deDataGrouper
acordo com o nome fornecido e também retornada se você quiser apenas uma referência local.Bem, isso é muita explicação. O código é razoavelmente direto, espero!
fonte
Eu verificaria o grupo lodash ; parece que ele faz exatamente o que você está procurando. Também é bastante leve e muito simples.
Exemplo de violino: https://jsfiddle.net/r7szvt5k/
Desde que o nome do seu array seja
arr
groupBy com lodash, é apenas:fonte
Provavelmente
linq.js
, isso é feito com mais facilidade , que se destina a ser uma verdadeira implementação do LINQ em JavaScript ( DEMO ):resultado:
Ou, mais simplesmente, usando os seletores baseados em string ( DEMO ):
fonte
GroupBy(function(x){ return x.Phase; })
Você pode criar um ES6 a
Map
partir dearray.reduce()
.Isso tem algumas vantagens sobre as outras soluções:
_.groupBy()
)Map
vez de um objeto (por exemplo, conforme retornado por_.groupBy()
). Isso tem muitos benefícios , incluindo:Map
é um resultado mais útil que uma matriz de matrizes. Mas se você deseja uma matriz de matrizes, pode chamarArray.from(groupedMap.entries())
(para uma matriz de[key, group array]
pares) ouArray.from(groupedMap.values())
(para uma matriz simples de matrizes).Como um exemplo do último ponto, imagine que eu tenho uma matriz de objetos nos quais eu quero fazer uma fusão (rasa) por id, assim:
Para fazer isso, normalmente começaria agrupando por ID e depois mesclando cada uma das matrizes resultantes. Em vez disso, você pode fazer a mesclagem diretamente no
reduce()
:fonte
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
fonte
Você pode fazer isso com a biblioteca JavaScript do Alasql :
Experimente este exemplo em jsFiddle .
BTW: Em matrizes grandes (100000 registros e mais) Alasql mais rápido que o Linq. Veja o teste em jsPref .
Comentários:
fonte
fonte
O MDN tem este exemplo em sua
Array.reduce()
documentação.fonte
Embora a pergunta tenha algumas respostas e as respostas pareçam um pouco complicadas, sugiro usar o Javascript vanilla para agrupar por com um aninhado (se necessário)
Map
.fonte
Sem mutações:
fonte
Essa solução aceita qualquer função arbitrária (não uma chave), portanto, é mais flexível que as soluções acima e permite funções de seta , semelhantes às expressões lambda usadas no LINQ :
NOTA: se você deseja estender
Array
o protótipo, é com você.Exemplo suportado na maioria dos navegadores:
Exemplo usando funções de seta (ES6):
Os dois exemplos acima retornam:
fonte
let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
eu gostaria de sugerir minha abordagem. Primeiro, separe o agrupamento e a agregação. Vamos declarar prototípica a função "agrupar por". É necessária outra função para produzir uma string "hash" para cada elemento da matriz a ser agrupado.
Quando o agrupamento é concluído, você pode agregar dados conforme necessário, no seu caso
em comum, funciona. Eu testei esse código no console do chrome. e sinta-se livre para melhorar e encontrar erros;)
fonte
map[_hash(key)].key = key;
paramap[_hash(key)].key = _hash(key);
.Imagine que você tem algo parecido com isto:
[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]
Ao fazer isso:
const categories = [...new Set(cars.map((car) => car.cat))]
Você receberá isto:
['sedan','sport']
Explicação: 1. Primeiro, estamos criando um novo conjunto passando uma matriz. Como Set permite apenas valores exclusivos, todas as duplicatas serão removidas.
Definir Doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Referência / Operadores / Spread_syntax
fonte
Resposta verificada - apenas agrupamento superficial. É muito bom entender como reduzir. A pergunta também fornece o problema de cálculos agregados adicionais.
Aqui está um REAL GROUP BY para Array of Objects por alguns campos com 1) nome de chave calculado e 2) solução completa para cascata de agrupamento, fornecendo a lista das chaves desejadas e convertendo seus valores exclusivos em chaves raiz como SQL GROUP BY faz.
Jogue com
estKey
- você pode agrupar por mais de um campo, adicionar agregações, cálculos ou outro processamento adicional.Além disso, você pode agrupar dados recursivamente. Por exemplo, agrupe inicialmente por
Phase
, depois porStep
campo e assim por diante. Além disso, remova os dados de repouso gordo.fonte
Este gera um array.
fonte
Com base em respostas anteriores
e é um pouco mais agradável olhar com sintaxe de propagação de objeto, se o seu ambiente suportar.
Aqui, nosso redutor pega o valor de retorno parcialmente formado (começando com um objeto vazio) e retorna um objeto composto pelos membros espalhados do valor de retorno anterior, juntamente com um novo membro cuja chave é calculada a partir do valor atual do iteree em
prop
e cujo valor é uma lista de todos os valores para esse suporte, juntamente com o valor atual.fonte
fonte
Aqui está uma solução desagradável e difícil de ler usando o ES6:
Para aqueles perguntando como é que este mesmo trabalho, aqui está uma explicação:
Em ambos
=>
você tem um livrereturn
A
Array.prototype.reduce
função leva até 4 parâmetros. É por isso que um quinto parâmetro está sendo adicionado para que possamos ter uma declaração de variável barata para o grupo (k) no nível da declaração de parâmetro usando um valor padrão. (sim, isso é feitiçaria)Se nosso grupo atual não existir na iteração anterior, criamos uma nova matriz vazia.
((r[k] || (r[k] = []))
Isso avaliará a expressão mais à esquerda, ou seja, uma matriz existente ou vazia , é por isso que existe umapush
expressão imediata após essa expressão, porque de qualquer maneira, você obterá uma matriz.Quando houver um
return
, o,
operador de vírgula descartará o valor mais à esquerda, retornando o grupo anterior ajustado para esse cenário.Uma versão mais fácil de entender que faz o mesmo é:
fonte
Vamos gerar uma
Array.prototype.groupBy()
ferramenta genérica . Apenas por variedade, vamos usar a fantasia ES6, o operador de espalhamento para alguns padrões Haskellesque correspondentes em uma abordagem recursiva. Também vamosArray.prototype.groupBy()
aceitar aceitar um retorno de chamada que leve o item (e
) o índice (i
) e a matriz aplicada (a
) como argumentos.fonte
A resposta de Ceasar é boa, mas funciona apenas para propriedades internas dos elementos dentro da matriz (comprimento em caso de string).
essa implementação funciona mais como: este link
espero que isto ajude...
fonte
From @mortb, @jmarceli answer e desta publicação ,
Aproveito
JSON.stringify()
para ser a identidade do VALOR PRIMITIVO várias colunas de do grupo por.Sem terceiros
Com terceiros da Lodash
fonte
reduce
Versão com base no ES6, com suporte para a funçãoiteratee
.Funciona exatamente como o esperado se a
iteratee
função não for fornecida:No contexto da pergunta do OP:
Outra ES6 versão que inverte o agrupamento e utiliza o
values
comokeys
eokeys
quanto agrouped values
:Nota: as funções são postadas em sua forma abreviada (uma linha) por questões de brevidade e para relacionar apenas a ideia. Você pode expandi-los e adicionar verificação de erro adicional, etc.
fonte
Gostaria de verificar declarative-js
groupBy
, parece fazer exatamente o que você está procurando. Isso é também:fonte
fonte
Aqui está uma versão do ES6 que não será interrompida em membros nulos
fonte
Apenas para adicionar à resposta de Scott Sauyet , algumas pessoas estavam perguntando nos comentários como usar sua função para agrupar por valor1, valor2, etc., em vez de agrupar apenas um valor.
Basta editar sua função de soma:
deixando o principal (DataGrouper) inalterado:
fonte
Com recurso de classificação
Amostra:
fonte