Javascript - agrupar matriz de objetos por valores comuns com rótulo

Sep 08 2020

Estou tentando transformar uma matriz de objetos em outra matriz de objetos agrupando por um valor específico e adicionando esse valor como rótulo e retirando-o do objeto na nova matriz.

Entrada: Por exemplo, eu tenho esta matriz de objetos:

let tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

Esperado: estou tentando descobrir como posso chegar a isso, onde há um rótulo para cada um dos agrupamentos exclusivos e array de opções contendo os valores desse agrupamento.

[
  {
    label: "Front End",
    options: [
      { id: 1, value: "HTML" },
      { id: 2, value: "React" },
    ],
  },
  {
    label: "Back End",
    options: [
      { id: 3, value: "Node" },
      { id: 4, value: "PHP" },
    ],
  },
]

O mais próximo que consegui chegar foi usando reduzir para agrupar pela chave de agrupamento:

const groupedTech = tech.reduce((acc, value) => {
  // Group initialization
  if (!acc[value.grouping]) {
    acc[value.grouping] = [];
  }
 
  // Grouping
  acc[value.grouping].push(value);
 
  return acc;
}, {});

O que me dá isso:

{
  "Front End": [
    { id: 1, grouping: "Front End", value: "HTML" },
    { id: 2, grouping: "Front End", value: "React" },
  ],
  "Back End": [
    { id: 3, grouping: "Back End", value: "Node" },
    { id: 4, grouping: "Back End", value: "PHP" },
  ],
}

Mas isso retorna o objeto não um array e não remove o valor de agrupamento. Não fui capaz de descobrir como agrupar corretamente porque na matriz de objetos não encontrei uma maneira eficiente de comparar para ver se o agrupamento existe e, se houver, adicionar a essa matriz aninhada. Eu ficaria melhor usando algo como .map () ? Aprecie qualquer oportunidade / aprendizado!

Respostas

3 HaoWu Sep 08 2020 at 12:18

Você está muito perto, basta envolver as entradas de valor-chave do resultado obtido em uma mapfunção:

let tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

const groupedTech = Object.entries(
  // What you have done
  tech.reduce((acc, { id, grouping, value }) => {
    // Group initialization
    if (!acc[grouping]) {
      acc[grouping] = [];
    }
    
    // Grouping
    // FIX: only pushing the object that contains id and value
    acc[grouping].push({ id, value });

    return acc;
  }, {})
).map(([label, options]) => ({ label, options }));

console.log(groupedTech);

2 hgb123 Sep 08 2020 at 12:18

Você só precisa fazer mais uma manipulação com Object.entriese.map

let tech = [
  { id: 1, grouping: 'Front End', value: 'HTML' },
  { id: 2, grouping: 'Front End', value: 'React' },
  { id: 3, grouping: 'Back End', value: 'Node' },
  { id: 4, grouping: 'Back End', value: 'PHP' }
]

const groupedTech = tech.reduce((acc, value) => {
  // Group initialization
  if (!acc[value.grouping]) {
    acc[value.grouping] = []
  }

  // Grouping
  acc[value.grouping].push(value)

  return acc
}, {})

const res = Object.entries(groupedTech).map(([label, options]) => ({
  label,
  options
}))

console.log(res)

2 Nick Sep 08 2020 at 12:23

Uma pequena variação nas outras duas respostas se você quiser obter exatamente a saída que você especificou:

let tech = [{
    id: 1,
    grouping: "Front End",
    value: "HTML"
  },
  {
    id: 2,
    grouping: "Front End",
    value: "React"
  },
  {
    id: 3,
    grouping: "Back End",
    value: "Node"
  },
  {
    id: 4,
    grouping: "Back End",
    value: "PHP"
  },
];

const groupedTech = Object.entries(
    tech.reduce((acc, value) => {
      // Group initialization
      if (!acc[value.grouping]) {
        acc[value.grouping] = [];
      }

      // Grouping
      acc[value.grouping].push({
        id: acc[value.grouping].length+1,
        value: value.value
      });

      return acc;
    }, {}))
  .map(([label, options]) => ({
    label,
    options
  }));

console.log(groupedTech);

2 Phil Sep 08 2020 at 12:25

Normalmente gosto de construir Mappares de chave / valor e, em seguida, transformar essas entradas no resultado final (normalmente usando Array.prototype.map()ou Array.from()).

const tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

const groupedMap = tech.reduce((map, { grouping, ...option }) => {
  if (!map.has(grouping)) {
    map.set(grouping, [])
  }
  map.get(grouping).push(option)
  return map
}, new Map())

const groupedTech = Array.from(groupedMap, ([ label, options ]) => ({
  label,
  options
}))

console.log(groupedTech)

2 charlietfl Sep 08 2020 at 12:37

Usando um mapa e valores de nº de mapa ()

const grouped = tech.reduce((m,{grouping:label, ...rest})=>{
    const group = m.get(label) || {label, options:[]};
    group.options.push({...rest})
    return m.set(label, group)
},new Map)

console.log([...grouped.values()])
<script>
let tech=[{id:1,grouping:"Front End",value:"HTML"},{id:2,grouping:"Front End",value:"React"},{id:3,grouping:"Back End",value:"Node"},{id:4,grouping:"Back End",value:"PHP"}];
</script>