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

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.


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


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


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

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

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


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


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


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

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

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


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

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


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

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


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

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

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


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.


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"
      }
   ]
}

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>


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 .


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

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

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)


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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.