Cómo agrupar una matriz de objetos por clave

153

¿Alguien sabe de una manera (lodash si es posible también) de agrupar una matriz de objetos por una clave de objeto y luego crear una nueva matriz de objetos basada en la agrupación? Por ejemplo, tengo una variedad de objetos de automóviles:

var cars = [
    {
        'make': 'audi',
        'model': 'r8',
        'year': '2012'
    }, {
        'make': 'audi',
        'model': 'rs5',
        'year': '2013'
    }, {
        'make': 'ford',
        'model': 'mustang',
        'year': '2012'
    }, {
        'make': 'ford',
        'model': 'fusion',
        'year': '2015'
    }, {
        'make': 'kia',
        'model': 'optima',
        'year': '2012'
    },
];

Quiero hacer una nueva matriz de objetos de automóviles que se agrupen por make:

var cars = {
    'audi': [
        {
            'model': 'r8',
            'year': '2012'
        }, {
            'model': 'rs5',
            'year': '2013'
        },
    ],

    'ford': [
        {
            'model': 'mustang',
            'year': '2012'
        }, {
            'model': 'fusion',
            'year': '2015'
        }
    ],

    'kia': [
        {
            'model': 'optima',
            'year': '2012'
        }
    ]
}
Trung Tran
fuente
1
¿Lo miraste groupBy?
SLaks
2
Su resultado no es válido.
Nina Scholz el
¿Existe un enfoque similar para obtener un Mapa en lugar de un objeto?
Andrea Bergonzo

Respuestas:

104

La respuesta de Timo es cómo lo haría. Simple _.groupBy, y permite algunas duplicaciones en los objetos en la estructura agrupada.

Sin embargo, el OP también solicitó makeque se eliminen las claves duplicadas . Si quisieras ir hasta el final:

var grouped = _.mapValues(_.groupBy(cars, 'make'),
                          clist => clist.map(car => _.omit(car, 'make')));

console.log(grouped);

Rendimientos:

{ audi:
   [ { model: 'r8', year: '2012' },
     { model: 'rs5', year: '2013' } ],
  ford:
   [ { model: 'mustang', year: '2012' },
     { model: 'fusion', year: '2015' } ],
  kia: [ { model: 'optima', year: '2012' } ] }

Si desea hacer esto usando Underscore.js, tenga en cuenta que _.mapValuesse llama a su versión de _.mapObject.

Jonathan Eunice
fuente
278

En Javascript simple, puedes usarlo Array#reducecon un objeto

var cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }],
    result = cars.reduce(function (r, a) {
        r[a.make] = r[a.make] || [];
        r[a.make].push(a);
        return r;
    }, Object.create(null));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Nina Scholz
fuente
1
¿Cómo puedo repetir los resultresultados?
Mounir Elfassi
1
puede tomar las entradas con Object.entriesy recorrer los pares clave / valor.
Nina Scholz
¿Hay alguna forma de eliminar el makeconjunto de datos una vez agrupado? Está tomando espacio extra.
Mercurial
Sí, por Descanso en Destrucción de Objetos .
Nina Scholz
¿Qué significa r y a? ¿Sería correcto suponer que r es el acumulador y a el valor actual?
Omar
68

Que busca _.groupBy().

Eliminar la propiedad por la que está agrupando los objetos debería ser trivial si es necesario:

var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'},];

var grouped = _.groupBy(cars, function(car) {
  return car.make;
});

console.log(grouped);
<script src='https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js'></script>


Como beneficio adicional, obtienes una sintaxis aún mejor con las funciones de flecha ES6:

const grouped = _.groupBy(cars, car => car.make);
Timo
fuente
18
Y si lo desea aún más corto, var grouped = _.groupBy(cars, 'make');no necesita ninguna función, si el descriptor de acceso es un nombre de propiedad simple.
Jonathan Eunice el
1
¿Qué significa '_'?
Adrian Grzywaczewski
@AdrianGrzywaczewski era la convención predeterminada para el espaciado de nombres 'lodash' o 'subrayado'. Ahora que las bibliotecas son modulares, ya no es necesario, es decir. npmjs.com/package/lodash.groupby
vilsbole
55
¿Y cómo puedo interactuar en el resultado?
Luis Antonio Pestana
36

La versión corta para agrupar una matriz de objetos por una determinada clave en es6:

result = array.reduce((h, obj) => Object.assign(h, { [obj.key]:( h[obj.key] || [] ).concat(obj) }), {})

La versión más larga:

result = array.reduce(function(h, obj) {
  h[obj.key] = (h[obj.key] || []).concat(obj);
  return h; 
}, {})

Parece que la pregunta original pregunta cómo agrupar los automóviles por marca, pero omita la marca en cada grupo. Entonces la respuesta se vería así:

result = cars.reduce((h, {model,year,make}) => {
  return Object.assign(h, { [make]:( h[make] || [] ).concat({model,year})})
}, {})
metakungfu
fuente
esto definitivamente no es es5
Shinigami
¡Simplemente funciona! ¿Alguien puede elaborar esta función de reducción?
Jeevan
Me gustaron sus dos respuestas, pero veo que ambas proporcionan el campo "make" como miembro de cada matriz "make". He proporcionado una respuesta basada en la suya donde la salida entregada coincide con la salida esperada. ¡Gracias!
Daniel Vukasovich
15

Aquí está su propia groupByfunción, que es una generalización del código de: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

function groupBy(xs, f) {
  return xs.reduce((r, v, i, a, k = f(v)) => ((r[k] || (r[k] = [])).push(v), r), {});
}

const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];

const result = groupBy(cars, (c) => c.make);
console.log(result);

cdiggins
fuente
15

var cars = [{
  make: 'audi',
  model: 'r8',
  year: '2012'
}, {
  make: 'audi',
  model: 'rs5',
  year: '2013'
}, {
  make: 'ford',
  model: 'mustang',
  year: '2012'
}, {
  make: 'ford',
  model: 'fusion',
  year: '2015'
}, {
  make: 'kia',
  model: 'optima',
  year: '2012'
}].reduce((r, car) => {

  const {
    model,
    year,
    make
  } = car;

  r[make] = [...r[make] || [], {
    model,
    year
  }];

  return r;
}, {});

console.log(cars);

G.aziz
fuente
8

Me iría REAL GROUP BYpara JS Arrays ejemplo exactamente la misma tarea aquí

const inputArray = [ 
    { 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 outObject = inputArray.reduce(function(a, e) {
  // GROUP BY estimated key (estKey), well, may be a just plain key
  // a -- Accumulator result object
  // e -- sequentally checked Element, the Element that is tested just at this itaration

  // new grouping name may be calculated, but must be based on real value of real field
  let estKey = (e['Phase']); 

  (a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
  return a;
}, {});

console.log(outObject);

SynCap
fuente
7

Puede intentar modificar el objeto dentro de la función llamada por iteración por _.groupBy func. ¡Observe que la matriz fuente cambia sus elementos!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);
nickxbs
fuente
1
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación. ¡Recuerde que está respondiendo la pregunta para los lectores en el futuro, no solo la persona que pregunta ahora! Edite su respuesta para agregar una explicación e indique qué limitaciones y supuestos se aplican.
Makyen
Parece la mejor respuesta para mí, ya que revisa la matriz solo una vez para obtener el resultado deseado. No es necesario usar otra función para eliminar la makepropiedad, y también es más legible.
Carrm
7

También es posible con un forbucle simple :

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }
Jonas Wilms
fuente
Y probablemente también más rápido y más simple. He ampliado su fragmento para que sea un poco más dinámico, ya que tenía una larga lista de campos de una tabla de base de datos que no quería escribir. También tenga en cuenta que deberá reemplazar const por let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
adrien el
TIL, gracias! medium.com/@mautayro/…
adrien el
5

Para casos en los que la clave puede ser nula y queremos agruparlos como otros

var cars = [{'make':'audi','model':'r8','year':'2012'},{'make':'audi','model':'rs5','year':'2013'},{'make':'ford','model':'mustang','year':'2012'},{'make':'ford','model':'fusion','year':'2015'},{'make':'kia','model':'optima','year':'2012'},
            {'make':'kia','model':'optima','year':'2033'},
            {'make':null,'model':'zen','year':'2012'},
            {'make':null,'model':'blue','year':'2017'},

           ];


 result = cars.reduce(function (r, a) {
        key = a.make || 'others';
        r[key] = r[key] || [];
        r[key].push(a);
        return r;
    }, Object.create(null));
exexzian
fuente
4

Cree un método que pueda reutilizarse

Array.prototype.groupBy = function(prop) {
      return this.reduce(function(groups, item) {
        const val = item[prop]
        groups[val] = groups[val] || []
        groups[val].push(item)
        return groups
      }, {})
    };

A continuación, puede agrupar por cualquier criterio

const groupByMake = cars.groupBy('make');
        console.log(groupByMake);

var cars = [
    {
        'make': 'audi',
        'model': 'r8',
        'year': '2012'
    }, {
        'make': 'audi',
        'model': 'rs5',
        'year': '2013'
    }, {
        'make': 'ford',
        'model': 'mustang',
        'year': '2012'
    }, {
        'make': 'ford',
        'model': 'fusion',
        'year': '2015'
    }, {
        'make': 'kia',
        'model': 'optima',
        'year': '2012'
    },
];
  //re-usable method
Array.prototype.groupBy = function(prop) {
	  return this.reduce(function(groups, item) {
		const val = item[prop]
		groups[val] = groups[val] || []
		groups[val].push(item)
		return groups
	  }, {})
	};
  
 // initiate your groupBy. Notice the recordset Cars and the field Make....
  const groupByMake = cars.groupBy('make');
		console.log(groupByMake);
    
    //At this point we have objects. You can use Object.keys to return an array

Wahinya Brian
fuente
3

Versión prototipo usando ES6 también. Básicamente, esto usa la función de reducción para pasar un acumulador y un elemento actual, que luego usa esto para construir sus matrices "agrupadas" en función de la clave pasada. la parte interna de la reducción puede parecer complicada, pero esencialmente se está probando para ver si la clave del objeto pasado existe y, si no es así, crear una matriz vacía y agregar el elemento actual a esa matriz recién creada, de lo contrario, usar la propagación El operador pasa todos los objetos de la matriz de claves actual y agrega el elemento actual. ¡Espero que esto ayude a alguien!.

Array.prototype.groupBy = function(k) {
  return this.reduce((acc, item) => ((acc[item[k]] = [...(acc[item[k]] || []), item]), acc),{});
};

const projs = [
  {
    project: "A",
    timeTake: 2,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 4,
    desc: "this is a description"
  },
  {
    project: "A",
    timeTake: 12,
    desc: "this is a description"
  },
  {
    project: "B",
    timeTake: 45,
    desc: "this is a description"
  }
];

console.log(projs.groupBy("project"));
Azayda
fuente
1

También puede utilizar un array#forEach()método como este:

const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];

let newcars = {}

cars.forEach(car => {
  newcars[car.make] ? // check if that array exists or not in newcars object
    newcars[car.make].push({model: car.model, year: car.year})  // just push
   : (newcars[car.make] = [], newcars[car.make].push({model: car.model, year: car.year})) // create a new array and push
})

console.log(newcars);

Barba Negra
fuente
1
function groupBy(data, property) {
  return data.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}
groupBy(people, 'age');
sama vamsi
fuente
1

Solo prueba este, me funciona bien.

let grouped = _.groupBy(cars, 'make');

agravat.in
fuente
2
Error de referencia no capturado: _ no está definido: debe tener claro que su solución requiere instalar una biblioteca de terceros solo para resolver esto.
metakungfu
1
lo siento, creo que todos lo saben. _ se encuentra y se usa principalmente para lodash lib. entonces necesitas usar lodash. por favor lea la pregunta para saber que él / ella está pidiendo lodash. bueno, gracias. voy a recordar esto. y nunca olvides escribir lib.
agravat.en
1

Hice un punto de referencia para probar el rendimiento de cada solución que no usa bibliotecas externas.

JSBen.ch

La reduce()opción, publicada por @Nina Scholz parece ser la óptima.

Leonardofmed
fuente
0

Me gustó la respuesta @metakunfu, pero no proporciona exactamente la salida esperada. Aquí hay una actualización que elimina "make" en la carga útil final de JSON.

var cars = [
    {
        'make': 'audi',
        'model': 'r8',
        'year': '2012'
    }, {
        'make': 'audi',
        'model': 'rs5',
        'year': '2013'
    }, {
        'make': 'ford',
        'model': 'mustang',
        'year': '2012'
    }, {
        'make': 'ford',
        'model': 'fusion',
        'year': '2015'
    }, {
        'make': 'kia',
        'model': 'optima',
        'year': '2012'
    },
];

result = cars.reduce((h, car) => Object.assign(h, { [car.make]:( h[car.make] || [] ).concat({model: car.model, year: car.year}) }), {})

console.log(JSON.stringify(result));

Salida:

{  
   "audi":[  
      {  
         "model":"r8",
         "year":"2012"
      },
      {  
         "model":"rs5",
         "year":"2013"
      }
   ],
   "ford":[  
      {  
         "model":"mustang",
         "year":"2012"
      },
      {  
         "model":"fusion",
         "year":"2015"
      }
   ],
   "kia":[  
      {  
         "model":"optima",
         "year":"2012"
      }
   ]
}
Daniel Vukasovich
fuente
0

Con lodash / fp puede crear una función con _.flow()ese primer grupo mediante una tecla, y luego asignar cada grupo y omitir una tecla de cada elemento:

const { flow, groupBy, mapValues, map, omit } = _;

const groupAndOmitBy = key => flow(
  groupBy(key),
  mapValues(map(omit(key)))
);

const cars = [{ make: 'audi', model: 'r8', year: '2012' }, { make: 'audi', model: 'rs5', year: '2013' }, { make: 'ford', model: 'mustang', year: '2012' }, { make: 'ford', model: 'fusion', year: '2015' }, { make: 'kia', model: 'optima', year: '2012' }];

const groupAndOmitMake = groupAndOmitBy('make');

const result = groupAndOmitMake(cars);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>

Ori Drori
fuente
0

Sobre la base de la respuesta de @Jonas_Wilms si no desea escribir en todos sus campos:

    var result = {};

    for ( let { first_field, ...fields } of your_data ) 
    { 
       result[first_field] = result[first_field] || [];
       result[first_field].push({ ...fields }); 
    }

No hice ningún punto de referencia, pero creo que usar un bucle for también sería más eficiente que cualquier cosa sugerida en esta respuesta .

adrien
fuente
0
const reGroup = (list, key) => {
    const newGroup = {};
    list.forEach(item => {
        const newItem = Object.assign({}, item);
        delete newItem[key];
        newGroup[item[key]] = newGroup[item[key]] || [];
        newGroup[item[key]].push(newItem);
    });
    return newGroup;
};
const animals = [
  {
    type: 'dog',
    breed: 'puddle'
  },
  {
    type: 'dog',
    breed: 'labradoodle'
  },
  {
    type: 'cat',
    breed: 'siamese'
  },
  {
    type: 'dog',
    breed: 'french bulldog'
  },
  {
    type: 'cat',
    breed: 'mud'
  }
];
console.log(reGroup(animals, 'type'));
const cars = [
  {
      'make': 'audi',
      'model': 'r8',
      'year': '2012'
  }, {
      'make': 'audi',
      'model': 'rs5',
      'year': '2013'
  }, {
      'make': 'ford',
      'model': 'mustang',
      'year': '2012'
  }, {
      'make': 'ford',
      'model': 'fusion',
      'year': '2015'
  }, {
      'make': 'kia',
      'model': 'optima',
      'year': '2012'
  },
];

console.log(reGroup(cars, 'make'));
AKelley
fuente
0

Matriz agrupada de objetos en mecanografiado con esto:

groupBy (list: any[], key: string): Map<string, Array<any>> {
    let map = new Map();
    list.map(val=> {
        if(!map.has(val[key])){
            map.set(val[key],list.filter(data => data[key] == val[key]));
        }
    });
    return map;
});
Oluwafisayo Owolo
fuente
Esto parece ineficiente cuando realiza una búsqueda de cada clave. Lo más probable es que la búsqueda tenga una complejidad de O (n).
Leukipp
0

Me encanta escribirlo sin dependencia / complejidad, simplemente js simple.

const mp = {}
const cars = [
  {
    model: 'Imaginary space craft SpaceX model',
    year: '2025'
  },
  {
    make: 'audi',
    model: 'r8',
    year: '2012'
  },
  {
    make: 'audi',
    model: 'rs5',
    year: '2013'
  },
  {
    make: 'ford',
    model: 'mustang',
    year: '2012'
  },
  {
    make: 'ford',
    model: 'fusion',
    year: '2015'
  },
  {
    make: 'kia',
    model: 'optima',
    year: '2012'
  }
]

cars.forEach(c => {
  if (!c.make) return // exit (maybe add them to a "no_make" category)

  if (!mp[c.make]) mp[c.make] = [{ model: c.model, year: c.year }]
  else mp[c.make].push({ model: c.model, year: c.year })
})

console.log(mp)

Mohamed Abu Galala
fuente
-1

Aquí hay otra solución para ello. De acuerdo a lo pedido.

Quiero hacer una nueva matriz de objetos de automóviles que se agrupen por marca:

function groupBy() {
  const key = 'make';
  return cars.reduce((acc, x) => ({
    ...acc,
    [x[key]]: (!acc[x[key]]) ? [{
      model: x.model,
      year: x.year
    }] : [...acc[x[key]], {
      model: x.model,
      year: x.year
    }]
  }), {})
}

Salida:

console.log('Grouped by make key:',groupBy())
Eugen Sunic
fuente
-1

Aquí hay una solución inspirada en Collectors.groupingBy () en Java:

function groupingBy(list, keyMapper) {
  return list.reduce((accummalatorMap, currentValue) => {
    const key = keyMapper(currentValue);
    if(!accummalatorMap.has(key)) {
      accummalatorMap.set(key, [currentValue]);
    } else {
      accummalatorMap.set(key, accummalatorMap.get(key).push(currentValue));
    }
    return accummalatorMap;
  }, new Map());
}

Esto le dará un objeto Map.

// Usage

const carMakers = groupingBy(cars, car => car.make);

Rahul Sethi
fuente