Jak pogrupować tablicę obiektów według klucza

153

Czy ktoś zna (jeśli to możliwe, również lodash) sposób grupowania tablicy obiektów za pomocą klucza obiektu, a następnie tworzenia nowej tablicy obiektów na podstawie grupowania? Na przykład mam tablicę obiektów samochodów:

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

Chcę utworzyć nową tablicę obiektów samochodów pogrupowanych według 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
źródło
1
Patrzyłeś na groupBy?
SLaks
2
Twój wynik jest nieprawidłowy.
Nina Scholz
Czy istnieje podobne podejście do pobierania mapy zamiast obiektu?
Andrea Bergonzo,

Odpowiedzi:

104

Odpowiedź Timo brzmi: jak bym to zrobił. Proste _.groupByi pozwalają na pewne duplikacje obiektów w zgrupowanej strukturze.

Jednak OP poprosił również o usunięcie zduplikowanych makekluczy. Jeśli chcesz iść na całość:

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

console.log(grouped);

Plony:

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

Jeśli chcesz to zrobić za pomocą Underscore.js, zwróć uwagę, że jego wersja _.mapValuesnazywa się _.mapObject.

Jonathan Eunice
źródło
278

W zwykłym Javascript można by użyć Array#reduceobiektu

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
źródło
1
jak mogę iterować resultwyniki?
Mounir Elfassi
1
możesz wziąć wpisy Object.entrieszi zapętlić pary klucz / wartość.
Nina Scholz
Czy istnieje sposób na usunięcie makezbioru danych po zgrupowaniu? Zajmuje dodatkową przestrzeń.
Mercurial
tak, przez Restrukturyzację obiektów .
Nina Scholz
Co oznacza r and a? Czy poprawne byłoby założenie, że r jest akumulatorem, a wartością bieżącą?
Omar
68

Szukasz _.groupBy().

Usunięcie właściwości, według której grupujesz, z obiektów powinno być w razie potrzeby banalne:

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>


Jako bonus otrzymujesz jeszcze ładniejszą składnię dzięki funkcjom strzałkowym ES6:

const grouped = _.groupBy(cars, car => car.make);
Timo
źródło
18
A jeśli chcesz, aby była jeszcze krótsza, var grouped = _.groupBy(cars, 'make');w ogóle nie potrzebujesz funkcji, jeśli akcesor jest prostą nazwą właściwości.
Jonathan Eunice
1
Co oznacza „_”?
Adrian Grzywaczewski
@AdrianGrzywaczewski była to domyślna konwencja odstępów między nazwami „lodash” lub „underscore”. Teraz, gdy biblioteki są modułowe, nie są już wymagane, tj. npmjs.com/package/lodash.groupby
vilsbole
5
A jak mogę interweniować w wyniku?
Luis Antonio Pestana
36

Krótka wersja grupowania tablicy obiektów według określonego klucza w es6:

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

Dłuższa wersja:

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

Wydaje się, że pierwotne pytanie dotyczy grupowania samochodów według marki, ale pomija markę w każdej grupie. Więc odpowiedź wyglądałaby tak:

result = cars.reduce((h, {model,year,make}) => {
  return Object.assign(h, { [make]:( h[make] || [] ).concat({model,year})})
}, {})
metakungfu
źródło
to zdecydowanie nie jest es5
Shinigami
Po prostu działa! Czy ktoś może rozwinąć tę funkcję redukcji?
Jeevan,
Podobały mi się obie twoje odpowiedzi, ale widzę, że obie zapewniają pole „make” jako element składowy każdej tablicy „make”. Udzieliłem odpowiedzi na podstawie Twojej, w której dostarczone wyniki odpowiadają oczekiwanym wynikom. Dzięki!
Daniel Vukasovich
15

Oto Twoja własna groupByfunkcja, która jest uogólnieniem kodu z: 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
źródło
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
źródło
8

Chciałbym zostawić REAL GROUP BYna przykład JS Array dokładnie tak samo to zadanie tutaj

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
źródło
7

Możesz spróbować zmodyfikować obiekt wewnątrz funkcji wywoływanej przez iterację przez _.groupBy func. Zauważ, że tablica źródłowa zmienia jego elementy!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);
nickxbs
źródło
1
Chociaż ten kod może rozwiązać problem, w tym wyjaśnienie, jak i dlaczego to rozwiązuje problem, naprawdę pomogłoby poprawić jakość twojego postu. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a nie tylko osoba, która zapyta teraz! Zmień swoją odpowiedź, dodając wyjaśnienie i wskaż, jakie ograniczenia i założenia mają zastosowanie.
Makyen
Wygląda na to, że jest to dla mnie najlepsza odpowiedź, ponieważ przechodzisz przez tablicę tylko raz, aby uzyskać pożądany wynik. Nie ma potrzeby używania innej funkcji, aby usunąć makewłaściwość, a także jest bardziej czytelna.
Carrm
7

Jest to również możliwe dzięki prostej forpętli:

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }
Jonas Wilms
źródło
I prawdopodobnie także szybszy i prostszy. Rozszerzyłem Twój fragment kodu, aby był nieco bardziej dynamiczny, ponieważ miałem długą listę pól z tabeli db, której nie chciałem wpisywać. Zauważ również, że będziesz musiał zamienić const na let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
adrien
TIL, dzięki! medium.com/@mautayro/…
adrien
5

W przypadkach, w których klucz może być zerowy i chcemy je zgrupować jako inne

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
źródło
4

Utwórz metodę, której można użyć ponownie

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

Następnie poniżej możesz grupować według dowolnych kryteriów

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
źródło
3

Wersja prototypowa również korzystająca z ES6. Zasadniczo wykorzystuje to funkcję redukuj do przekazania akumulatora i aktualnego elementu, który następnie używa tego do zbudowania "zgrupowanych" tablic na podstawie przekazanego klucza. wewnętrzna część redukcji może wyglądać na skomplikowaną, ale zasadniczo testuje, czy klucz przekazanego obiektu istnieje, a jeśli nie, tworzy pustą tablicę i dołącza bieżący element do tej nowo utworzonej tablicy, w przeciwnym razie używając rozkładówki operator przekazuje wszystkie obiekty bieżącej tablicy kluczy i dołącza bieżący element. Mam nadzieję, że to komuś pomoże!

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
źródło
1

Możesz także skorzystać z array#forEach()takiej metody:

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

Czarnobrody
źródło
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
źródło
1

Po prostu spróbuj tego, który działa dobrze dla mnie.

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

agravat.in
źródło
2
Uncaught ReferenceError: _ nie jest zdefiniowane - powinno być jasne, że rozwiązanie wymaga zainstalowania biblioteki innej firmy, aby rozwiązać ten problem.
metakungfu
1
przepraszam, chyba każdy wie. _ stoi i najczęściej używany do lodash lib. więc musisz użyć lodash. przeczytaj pytanie, abyś wiedział, że prosi o lodash. Dziękuję. zapamiętam to. i nigdy nie zapomnij napisać lib.
agravat w
1

Zrobiłem test porównawczy, aby przetestować wydajność każdego rozwiązania, które nie korzysta z zewnętrznych bibliotek.

JSBen.ch

reduce()Opcja, wysłane przez @Nina Scholza wydaje się być jedna optymalna.

leonardofmed
źródło
0

Podobała mi się odpowiedź @metakunfu, ale nie zapewnia ona dokładnie oczekiwanych wyników. Oto aktualizacja, która usuwa słowo „make” w końcowym ładunku 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));

Wynik:

{  
   "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
źródło
0

Dzięki lodash / fp możesz utworzyć funkcję z _.flow()pierwszą grupą za pomocą klucza, a następnie mapować każdą grupę i pomijać klucz z każdej pozycji:

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
źródło
0

Opierając się na odpowiedzi @Jonas_Wilms, jeśli nie chcesz wpisywać wszystkich pól:

    var result = {};

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

Nie wykonałem żadnego testu porównawczego, ale uważam, że użycie pętli for byłoby bardziej wydajne niż cokolwiek sugerowanego w tej odpowiedzi .

adrien
źródło
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
źródło
0

Zgrupowana tablica obiektów w maszynopisie z tym:

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
źródło
Wydaje się to nieefektywne, gdy szukasz każdego klucza. Wyszukiwanie ma najprawdopodobniej złożoność O (n).
Leukipp
0

Uwielbiam pisać to bez zależności / złożoności, tylko po prostu proste 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)

Mohamed Abu Galala
źródło
-1

Oto inne rozwiązanie tego problemu. Zgodnie z prośbą.

Chcę utworzyć nową tablicę obiektów samochodów pogrupowanych według marki:

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

Wynik:

console.log('Grouped by make key:',groupBy())
Eugen Sunic
źródło
-1

Oto rozwiązanie zainspirowane Collectors.groupingBy () w Javie:

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

To da obiekt Map.

// Usage

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

Rahul Sethi
źródło