Comment regrouper un tableau d'objets par clé

154

Quelqu'un connaît-il un moyen (lodash si possible aussi) de regrouper un tableau d'objets par une clé d'objet, puis de créer un nouveau tableau d'objets basé sur le regroupement? Par exemple, j'ai un tableau d'objets voiture:

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

Je souhaite créer un nouveau tableau d'objets voiture regroupés par 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
la source
1
Avez-vous regardé groupBy?
SLaks
2
votre résultat n'est pas valide.
Nina Scholz
Existe-t-il une approche similaire pour obtenir une carte au lieu d'un objet?
Andrea Bergonzo

Réponses:

104

La réponse de Timo est comment je le ferais. Facile_.groupBy , et autorise certaines duplications dans les objets de la structure groupée.

Cependant, l'OP a également demandé la makesuppression des clés en double . Si vous vouliez aller jusqu'au bout:

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

console.log(grouped);

Rendements:

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

Si vous vouliez faire cela en utilisant Underscore.js, notez que sa version de _.mapValuesest appelée _.mapObject.

Jonathan Eunice
la source
279

En Javascript brut, vous pouvez utiliser Array#reduceavec un objet

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
la source
1
comment puis-je itérer les resultrésultats?
Mounir Elfassi
1
vous pouvez prendre les entrées avec Object.entrieset parcourir les paires clé / valeur.
Nina Scholz
Existe-t-il un moyen de supprimer l'ensemble makede données une fois groupé? Cela prend plus de place.
Mercurial
oui, par Rest in Object Destructuring .
Nina Scholz
Que signifient r et a? Serait-il correct de supposer que r est l'accumulateur et a la valeur actuelle?
Omar
68

Vous recherchez _.groupBy().

La suppression de la propriété par laquelle vous regroupez des objets devrait être triviale si nécessaire:

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>


En prime, vous obtenez une syntaxe encore plus agréable avec les fonctions fléchées ES6:

const grouped = _.groupBy(cars, car => car.make);
Timo
la source
18
Et si vous voulez qu'il soit encore plus court, var grouped = _.groupBy(cars, 'make');pas besoin de fonction du tout, si l'accesseur est un simple nom de propriété.
Jonathan Eunice
1
Que signifie «_»?
Adrian Grzywaczewski
@AdrianGrzywaczewski c'était la convention par défaut pour l'espacement des noms 'lodash' ou 'underscore'. Maintenant que les librairies sont modulaires, elles ne sont plus nécessaires. npmjs.com/package/lodash.groupby
vilsbole
5
Et comment puis-je m'intéresser au résultat?
Luis Antonio Pestana
36

La version courte pour regrouper un tableau d'objets par une certaine clé dans es6:

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

La version plus longue:

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

Il semble que la question d'origine demande comment regrouper les voitures par marque, mais omettez la marque dans chaque groupe. La réponse ressemblerait donc à ceci:

result = cars.reduce((h, {model,year,make}) => {
  return Object.assign(h, { [make]:( h[make] || [] ).concat({model,year})})
}, {})
metakungfu
la source
ce n'est certainement pas es5
Shinigami
Cela fonctionne juste!. Quelqu'un peut-il élaborer cette fonction de réduction?
Jeevan
J'ai aimé vos deux réponses, mais je vois qu'elles fournissent toutes les deux un champ "make" en tant que membre de chaque tableau "make". J'ai fourni une réponse basée sur la vôtre où la sortie fournie correspond à la sortie attendue. Merci!
Daniel Vukasovich
15

Voici votre propre groupByfonction qui est une généralisation du code 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
la source
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
la source
9

Je laisserais REAL GROUP BYpour l'exemple JS Arrays exactement la même tâche ici

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
la source
7

Vous pouvez essayer de modifier l'objet à l'intérieur de la fonction appelée par itération par _.groupBy func. Notez que le tableau source change ses éléments!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);
nickxbs
la source
1
Bien que ce code puisse résoudre la question, inclure une explication sur la manière et la raison pour laquelle cela résout le problème aiderait vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant! Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limites et des hypothèses applicables.
Makyen
Cela me semble être la meilleure réponse puisque vous ne parcourez le tableau qu'une seule fois pour obtenir le résultat souhaité. Il n'est pas nécessaire d'utiliser une autre fonction pour supprimer la makepropriété, et elle est également plus lisible.
Carrm
7

C'est également possible avec une simple forboucle:

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }
Jonas Wilms
la source
Et probablement plus rapide aussi, et plus simple. J'ai développé votre extrait pour être un peu plus dynamique car j'avais une longue liste de champs d'une table de base de données que je ne voulais pas saisir. Notez également que vous devrez remplacer const par let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
adrien le
TIL, merci! medium.com/@mautayro/…
adrien
5

Pour les cas où la clé peut être nulle et que nous voulons les regrouper comme d' autres

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
la source
4

Créer une méthode qui peut être réutilisée

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

Ensuite, ci-dessous, vous pouvez regrouper selon n'importe quel critère

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
la source
3

Version prototype utilisant également ES6. Fondamentalement, cela utilise la fonction de réduction pour transmettre un accumulateur et un élément actuel, qui l'utilise ensuite pour créer vos tableaux "groupés" en fonction de la clé transmise. la partie interne de la réduction peut sembler compliquée, mais essentiellement, elle teste pour voir si la clé de l'objet transmis existe et si elle ne crée pas un tableau vide et ajoute l'élément actuel à ce tableau nouvellement créé, sinon en utilisant la propagation L'opérateur passe tous les objets du tableau de clés actuel et ajoute l'élément actuel. J'espère que cela aide quelqu'un !.

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
la source
1

Vous pouvez également utiliser une array#forEach()méthode comme celle-ci:

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

BlackBeard
la source
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
la source
1

Essayez simplement celui-ci, cela fonctionne bien pour moi.

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

agravat.in
la source
2
Uncaught ReferenceError: _ n'est pas défini - vous devez être clair que votre solution nécessite d'installer une bibliothèque tierce juste pour résoudre ce problème.
metakungfu
2
désolé, je pense que tout le monde le sait. _ se tient et est principalement utilisé pour lodash lib. vous devez donc utiliser lodash. Veuillez lire la question afin que vous sachiez qu'il / elle demande du lodash. bien merci. je m'en souviendrai. et n'oubliez jamais d'écrire lib.
agravat.in
1

J'ai fait un benchmark pour tester les performances de chaque solution qui n'utilise pas de bibliothèques externes.

JSBen.ch

L' reduce()option, publiée par @Nina Scholz semble être la meilleure.

leonardofmed
la source
0

J'ai aimé la réponse @metakunfu, mais elle ne fournit pas exactement le résultat attendu. Voici une mise à jour qui supprime "make" dans la charge utile 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));

Production:

{  
   "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
la source
0

Avec lodash / fp, vous pouvez créer une fonction avec _.flow()ce premier groupe par une touche, puis mapper chaque groupe et omettre une clé de chaque élément:

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
la source
0

En vous appuyant sur la réponse de @Jonas_Wilms si vous ne souhaitez pas saisir tous vos champs:

    var result = {};

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

Je n'ai pas fait de benchmark, mais je pense que l'utilisation d'une boucle for serait plus efficace que tout ce qui est suggéré dans cette réponse .

adrien
la source
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
la source
0

Tableau d'objet groupé en texte manuscrit avec ceci:

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
la source
Cela semble inefficace lorsque vous effectuez une recherche pour chaque clé. La recherche a très probablement une complexité de O (n).
Leukipp
0

J'adore l'écrire sans dépendance / complexité juste des js simples et purs.

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
la source
-1

Voici une autre solution. Comme demandé.

Je souhaite créer un nouveau tableau d'objets de voiture regroupés par marque:

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

Production:

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

Voici une solution inspirée de 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());
}

Cela donnera un objet Map.

// Usage

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

Rahul Sethi
la source