So gruppieren Sie ein Array von Objekten nach Schlüssel


152

Kennt jemand eine (wenn möglich auch lodash) Möglichkeit, ein Array von Objekten nach einem Objektschlüssel zu gruppieren und dann basierend auf der Gruppierung ein neues Array von Objekten zu erstellen? Zum Beispiel habe ich eine Reihe von Autoobjekten:

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

Ich möchte eine neue Reihe von Autoobjekten erstellen, die gruppiert sind nach 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
Hast du angeschaut groupBy?
SLaks

2
Ihr Ergebnis ist ungültig.
Nina Scholz

Gibt es einen ähnlichen Ansatz, um eine Karte anstelle eines Objekts zu erhalten?
Andrea Bergonzo

Antworten:


103

Timos Antwort ist, wie ich es machen würde. Einfach _.groupByund einige Duplikate in den Objekten in der gruppierten Struktur zulassen.

Das OP forderte makejedoch auch die Entfernung der doppelten Schlüssel. Wenn Sie den ganzen Weg gehen wollten:

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

console.log(grouped);

Ausbeuten:

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

Wenn Sie dies mit Underscore.js tun möchten, beachten Sie, dass die Version von _.mapValuesaufgerufen wird _.mapObject.


277

In einfachem Javascript können Sie Array#reducemit einem Objekt verwenden

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
Wie kann ich resultErgebnisse iterieren ?
Mounir Elfassi

1
Sie können die Einträge mit nehmen Object.entriesund die Schlüssel / Wert-Paare durchlaufen.
Nina Scholz

Gibt es eine Möglichkeit, den makeeinmal gruppierten Datensatz zu entfernen ? Es braucht zusätzlichen Platz.
Mercurial


Wofür steht r und a? Wäre es richtig anzunehmen, dass r der Akkumulator und a der aktuelle Wert ist?
Omar

68

Sie suchen _.groupBy().

Das Entfernen der Eigenschaft, nach der Sie gruppieren, aus den Objekten sollte bei Bedarf trivial sein:

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>


Als Bonus erhalten Sie mit ES6-Pfeilfunktionen eine noch schönere Syntax:

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

18
Und wenn Sie es noch kürzer haben möchten, var grouped = _.groupBy(cars, 'make');brauchen Sie überhaupt keine Funktion, wenn der Accessor ein einfacher Eigenschaftsname ist.
Jonathan Eunice

1
Wofür steht '_'?
Adrian Grzywaczewski

@AdrianGrzywaczewski war die Standardkonvention für den Namensabstand 'lodash' oder 'underscore'. Jetzt, da die Bibliotheken modular aufgebaut sind, ist sie nicht mehr erforderlich, d. H. npmjs.com/package/lodash.groupby
vilsbole

5
Und wie kann ich am Ergebnis teilnehmen?
Luis Antonio Pestana

36

Die Kurzversion zum Gruppieren eines Arrays von Objekten nach einem bestimmten Schlüssel in es6:

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

Die längere Version:

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

Es scheint, dass die ursprüngliche Frage fragt, wie Autos nach Marke gruppiert werden sollen, aber die Marke in jeder Gruppe weggelassen wird. Die Antwort würde also so aussehen:

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

Dies ist definitiv nicht es5
Shinigami

Es funktioniert einfach! Kann jemand diese Reduktionsfunktion näher erläutern?
Jeevan

Ich mochte beide Ihrer Antworten, aber ich sehe, dass beide das Feld "make" als Mitglied jedes "make" -Arrays bereitstellen. Ich habe eine Antwort basierend auf Ihrer angegeben, bei der die gelieferte Ausgabe mit der erwarteten Ausgabe übereinstimmt. Vielen Dank!
Daniel Vukasovich

15

Hier ist Ihre eigene groupByFunktion, die eine Verallgemeinerung des Codes von: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore ist

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


14

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


8

Ich würde REAL GROUP BYfür JS Arrays Beispiel genau die gleiche Aufgabe hier lassen

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

Sie können versuchen, das Objekt innerhalb der von _.groupBy func pro Iteration aufgerufenen Funktion zu ändern. Beachten Sie, dass das Quell-Array seine Elemente ändert!

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

1
Dieser Code kann zwar die Frage lösen, aber eine Erklärung, wie und warum dies das Problem löst, würde wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt! Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, und geben Sie an, welche Einschränkungen und Annahmen gelten.
Makyen

Es scheint mir die beste Antwort zu sein, da Sie das Array nur einmal durchlaufen, um das gewünschte Ergebnis zu erzielen. Es ist nicht erforderlich, eine andere Funktion zum Entfernen der makeEigenschaft zu verwenden, und sie ist auch besser lesbar.
Carrm

7

Es ist auch mit einer einfachen forSchleife möglich:

 const result = {};

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

Und wahrscheinlich auch schneller und einfacher. Ich habe Ihr Snippet etwas dynamischer erweitert, da ich eine lange Liste von Feldern aus einer DB-Tabelle hatte, die ich nicht eingeben wollte. Beachten Sie auch, dass Sie const durch let ersetzen müssen. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
Adrien


5

In Fällen, in denen der Schlüssel null sein kann und wir sie als andere gruppieren möchten

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

3

Erstellen Sie eine Methode, die wiederverwendet werden kann

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

Dann können Sie unten nach beliebigen Kriterien gruppieren

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

Prototyp-Version auch mit ES6. Grundsätzlich verwendet dies die Reduktionsfunktion, um einen Akkumulator und ein aktuelles Element zu übergeben, die dann verwendet werden, um Ihre "gruppierten" Arrays basierend auf dem übergebenen Schlüssel zu erstellen. Der innere Teil der Reduzierung mag kompliziert aussehen, aber im Wesentlichen wird geprüft, ob der Schlüssel des übergebenen Objekts vorhanden ist. Wenn dies nicht der Fall ist, wird ein leeres Array erstellt und das aktuelle Element an das neu erstellte Array angehängt, andernfalls wird der Spread verwendet Der Operator übergibt alle Objekte des aktuellen Schlüsselarrays und hängt das aktuelle Element an. Hoffe das hilft jemandem!.

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

Sie können auch eine array#forEach()Methode wie die folgende verwenden:

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

Probieren Sie es einfach aus, es funktioniert gut für mich.

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


2
Nicht erfasster Referenzfehler: _ ist nicht definiert. Sie sollten sich darüber im Klaren sein, dass für Ihre Lösung eine Bibliothek eines Drittanbieters installiert werden muss, um dieses Problem zu beheben.
Metakung Fu

1
Entschuldigung, ich denke jeder weiß es. _ steht und wird meistens für lodash lib verwendet. Sie müssen also lodash verwenden. Bitte lesen Sie die Frage, damit Sie wissen, dass er / sie nach lodash fragt. Nun, danke. ich werde mich daran erinnern. und vergiss nie lib zu schreiben.
Agravat.in

1

Ich habe einen Benchmark erstellt, um die Leistung jeder Lösung zu testen, die keine externen Bibliotheken verwendet.

JSBen.ch

Die reduce()von @Nina Scholz gepostete Option scheint die optimale zu sein.


0

Ich mochte die @ metakunfu-Antwort, aber sie liefert nicht genau die erwartete Ausgabe. Hier ist eine Aktualisierung, die "make" in der endgültigen JSON-Nutzlast entfernt.

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

Ausgabe:

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

0

Mit lodash / fp können Sie eine Funktion mit _.flow()diesen ersten Gruppen durch einen Schlüssel erstellen , dann jede Gruppe zuordnen und einen Schlüssel aus jedem Element weglassen:

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

Aufbauend auf der Antwort von @Jonas_Wilms, wenn Sie nicht alle Ihre Felder eingeben möchten:

    var result = {};

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

Ich habe keinen Benchmark erstellt, aber ich glaube, dass die Verwendung einer for-Schleife effizienter ist als alles, was in dieser Antwort vorgeschlagen wird .


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

Gruppiertes Array von Objekten in Typoskript mit diesem:

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

Dies sieht ineffizient aus, wenn Sie nach jedem Schlüssel suchen. Die Suche hat höchstwahrscheinlich eine Komplexität von O (n).
Leukipp

0

Ich liebe es, es ohne Abhängigkeit / Komplexität zu schreiben, nur reine einfache 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

Hier ist eine andere Lösung dafür. Wie gewünscht.

Ich möchte eine neue Reihe von Autoobjekten erstellen, die nach make gruppiert sind:

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

Ausgabe:

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

-1

Hier ist eine Lösung, die von Collectors.groupingBy () in Java inspiriert wurde:

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

Dies ergibt ein Kartenobjekt.

// Usage

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.