Come raggruppare una matrice di oggetti per chiave


154

Qualcuno conosce un modo (se possibile anche troppo) per raggruppare una matrice di oggetti in base a una chiave oggetto, quindi creare una nuova matrice di oggetti basata sul raggruppamento? Ad esempio, ho una serie di oggetti auto:

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'
    },
];

Voglio creare una nuova serie di oggetti auto raggruppati per 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'
        }
    ]
}

1
Hai guardato groupBy?
SLaks,

2
il tuo risultato non è valido.
Nina Scholz,

Esiste un approccio simile per ottenere una mappa anziché un oggetto?
Andrea Bergonzo,

Risposte:


104

La risposta di Timo è come lo farei. Semplice_.groupBy e consente alcune duplicazioni negli oggetti nella struttura raggruppata.

Tuttavia, l'OP ha anche chiesto la makerimozione delle chiavi duplicate . Se volevi andare fino in fondo:

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

console.log(grouped);

I rendimenti:

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

Se vuoi farlo utilizzando Underscore.js, nota che _.mapValuesviene chiamata la sua versione di _.mapObject.


279

In Javascript semplice, è possibile utilizzare Array#reducecon un oggetto

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; }


1
come posso iterare i resultrisultati?
Mounir Elfassi,

1
puoi prendere le voci con Object.entriese scorrere tra le coppie chiave / valore.
Nina Scholz,

C'è un modo per rimuovere il makeset di dati una volta raggruppati? Sta prendendo spazio extra.
Mercurial


Cosa significa re una posizione? Sarebbe corretto supporre che r sia l'accumulatore e a the currentValue?
Omar,

68

Stai cercando _.groupBy() .

Rimuovere la proprietà che stai raggruppando dagli oggetti dovrebbe essere banale se necessario:

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>


Come bonus, ottieni una sintassi ancora migliore con le funzioni della freccia ES6:

const grouped = _.groupBy(cars, car => car.make);

18
E se lo vuoi ancora più corto, var grouped = _.groupBy(cars, 'make');Non è necessaria alcuna funzione, se l'accessor è un semplice nome di proprietà.
Jonathan Eunice,

1
Cosa significa "_"?
Adrian Grzywaczewski,

@AdrianGrzywaczewski era la convenzione predefinita per la spaziatura dei nomi 'lodash' o 'underscore'. Ora che le librerie sono modulari non è più necessario, ad es. npmjs.com/package/lodash.groupby
vilsbole,

5
E come posso interagire nel risultato?
Luis Antonio Pestana,

36

La versione breve per raggruppare una matrice di oggetti in base a una determinata chiave in es6:

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

La versione più lunga:

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

Sembra che la domanda originale chieda come raggruppare le auto per marca, ma omettere la marca in ciascun gruppo. Quindi la risposta sarebbe simile a questa:

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

questo sicuramente non è es5
Shinigami,

Funziona e basta! Qualcuno può elaborare questa funzione di riduzione?
Jeevan,

Mi sono piaciute entrambe le risposte, ma vedo che entrambi forniscono il campo "make" come membro di ogni array "make". Ho fornito una risposta basata sulla tua in cui l'output consegnato corrisponde all'output previsto. Grazie!
Daniel Vukasovich,

15

Ecco la tua groupByfunzione personale che è una generalizzazione del codice da: 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);


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);


9

Vorrei REAL GROUP BYper l'esempio di JS Arrays esattamente lo stesso questo compito qui

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);


7

Puoi provare a modificare l'oggetto all'interno della funzione chiamata per iterazione da _.groupBy func. Si noti che l'array di origine cambia i suoi elementi!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);

1
Sebbene questo codice possa risolvere la domanda, inclusa una spiegazione di come e perché questo risolva il problema, potrebbe davvero aiutare a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo per la persona che chiede ora! Modifica la tua risposta per aggiungere spiegazioni e fornire un'indicazione di quali limitazioni e ipotesi si applicano.
Makyen

Mi sembra la risposta migliore da quando passi attraverso l'array solo una volta per ottenere il risultato desiderato. Non è necessario utilizzare un'altra funzione per rimuovere la makeproprietà ed è anche più leggibile.
Carrm,

7

È anche possibile con un semplice forloop:

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }

E probabilmente anche più veloce e più semplice. Ho ampliato il tuo snippet in modo che sia un po 'più dinamico dato che avevo una lunga lista di campi da una tabella db che non volevo digitare. Nota anche che dovrai sostituire const con let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
Adrien,


5

Per i casi in cui la chiave può essere nulla e vogliamo raggrupparli come altri

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));

4

Crea un metodo che può essere riutilizzato

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
      }, {})
    };

Quindi sotto puoi raggruppare per qualsiasi 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


3

Versione del prototipo che utilizza anche ES6. Fondamentalmente questo usa la funzione di riduzione per passare un accumulatore e un oggetto corrente, che poi usa questo per costruire le tue matrici "raggruppate" in base alla chiave passata. la parte interna della riduzione può sembrare complicata ma essenzialmente sta testando per vedere se esiste la chiave dell'oggetto passato e se non crea quindi un array vuoto e accoda l'elemento corrente a quel nuovo array creato altrimenti usando lo spread l'operatore passa tutti gli oggetti dell'array di chiavi corrente e aggiunge l'elemento corrente. Spero che questo aiuti qualcuno !.

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"));

1

Puoi anche usare un array#forEach()metodo come questo:

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);


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');

1

Basta provare questo funziona bene per me.

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


2
Uncaught ReferenceError: _ non è definito - dovresti essere chiaro che la tua soluzione richiede l'installazione di una libreria di terze parti solo per risolvere questo problema.
Metakungfu,

2
scusa, penso che tutti lo sappiano. _ sta e viene usato principalmente per lodash lib. quindi devi usare lodash. si prega di leggere la domanda in modo da sapere che sta chiedendo lodash. bene grazie. lo ricorderò. e non dimenticare mai di scrivere lib.
agravat.in

1

Ho creato un benchmark per testare le prestazioni di ogni soluzione che non utilizza librerie esterne.

JSBen.ch

L' reduce()opzione, pubblicata da @Nina Scholz, sembra essere quella ottimale.


0

Mi è piaciuta la risposta di @metakunfu, ma non fornisce esattamente il risultato atteso. Ecco un aggiornamento che elimina "make" nel payload JSON finale.

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));

Produzione:

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

0

Con lodash / fp puoi creare una funzione con _.flow()quel primo gruppo con un tasto, quindi mappare ciascun gruppo e omettere un tasto da ciascun 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>


0

Basandosi sulla risposta di @Jonas_Wilms se non si desidera digitare in tutti i campi:

    var result = {};

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

Non ho fatto alcun benchmark ma credo che usare un ciclo for sarebbe più efficiente di qualsiasi cosa suggerita anche in questa risposta .


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'));

0

Matrice raggruppata di oggetti in dattiloscritto con questo:

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;
});

Questo sembra inefficiente mentre fai una ricerca per ogni chiave. La ricerca ha molto probabilmente una complessità di O (n).
Leukipp

0

Adoro scriverlo senza alcuna dipendenza / complessità solo semplici semplici js.

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)


-1

Ecco un'altra soluzione ad esso. Come richiesto.

Voglio creare una nuova gamma di oggetti auto raggruppati per 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
    }]
  }), {})
}

Produzione:

console.log('Grouped by make key:',groupBy())

-1

Ecco una soluzione ispirata a Collectors.groupingBy () in 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());
}

Questo darà un oggetto Mappa.

// Usage

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.