Как сгруппировать массив объектов по ключу

182

Кто-нибудь знает (если возможно, lodash) способ сгруппировать массив объектов по ключу объекта, а затем создать новый массив объектов на основе группировки? Например, у меня есть массив автомобильных объектов:

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:

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
Вы смотрели groupBy?
SLaks
2
ваш результат недействителен.
Нина Шольц
Есть ли аналогичный подход для получения карты вместо объекта?
Андреа Бергонцо
Если вы используете Typescript (что не относится к OP), у вас уже есть метод groupBy. Вы можете использоватьyour_array.groupBy(...)
Isac Moura

Ответы:

118

Тимо отвечает , как бы я это сделал. Простые _.groupByи допускают некоторые дублирования объектов в сгруппированной структуре.

Однако OP также попросил makeудалить повторяющиеся ключи. Если вы хотите пройти весь путь:

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

console.log(grouped);

Урожайность:

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

Если вы хотите сделать это с помощью Underscore.js, обратите внимание, что его версия _.mapValuesвызывается _.mapObject.

Джонатан Юнис
источник
301

В простом Javascript вы можете использовать Array#reduceс объектом

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
как я могу повторить resultрезультаты?
Mounir Elfassi
2
вы можете взять записи Object.entriesи пройти по парам ключ / значение.
Нина Шольц
Есть ли способ удалить makeиз набора данных после группировки? Это занимает лишнее место.
Mercurial
да, с помощью Rest in Object Destructuring .
Нина Шольц
Что означает r и a? Было бы правильно предположить, что r - аккумулятор, а a - текущее значение?
Омар
76

Вы ищите _.groupBy().

Удаление свойства, которое вы группируете из объектов, должно быть тривиальным, если необходимо:

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>


В качестве бонуса вы получаете еще более приятный синтаксис со стрелочными функциями ES6:

const grouped = _.groupBy(cars, car => car.make);
Тимо
источник
19
А если вы хотите еще короче, var grouped = _.groupBy(cars, 'make');функция вообще не нужна, если аксессор - это простое имя свойства.
Джонатан Юнис
1
Что означает "_"?
Adrian Grzywaczewski
@AdrianGrzywaczewski это было соглашение по умолчанию для интервалов между именами "lodash" или "подчеркивание". Теперь, когда библиотеки являются модульными, это больше не требуется, т.е. npmjs.com/package/lodash.groupby
vilsbole
5
И как я могу повлиять на результат?
Луис Антонио Пестана
50

Нет абсолютно никаких причин загружать стороннюю библиотеку для решения этой простой проблемы, как предполагают приведенные выше решения.

Однострочная версия для группировки listобъектов по определенному keyв es6:

const groupByKey = (list, key) => list.reduce((hash, obj) => ({...hash, [obj[key]]:( hash[obj[key]] || [] ).concat(obj)}), {})

Более длинная версия, которая фильтрует объекты без key:

function groupByKey(array, key) {
   return array
     .reduce((hash, obj) => {
       if(obj[key] === undefined) return hash; 
       return Object.assign(hash, { [obj[key]]:( hash[obj[key]] || [] ).concat(obj)})
     }, {})
}


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

console.log(groupByKey(cars, 'make'))

ПРИМЕЧАНИЕ. Похоже, что в исходном вопросе задается вопрос, как сгруппировать автомобили по маркам, но не указывать марку в каждой группе. Таким образом, короткий ответ без сторонних библиотек будет выглядеть так:

const groupByKey = (list, key, {omitKey=false}) => list.reduce((hash, {[key]:value, ...rest}) => ({...hash, [value]:( hash[value] || [] ).concat(omitKey ? {...rest} : {[key]:value, ...rest})} ), {})

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

console.log(groupByKey(cars, 'make', {omitKey:true}))

метакунгфу
источник
это определенно не es5
Shinigami
Это просто работает! Может ли кто-нибудь разработать эту функцию сокращения?
Дживан
Мне понравились оба ваших ответа, но я вижу, что они оба предоставляют поле «make» как член каждого массива «make». Я дал ответ, основанный на вашем, где доставленный результат соответствует ожидаемому. Благодарность!
Даниил Вукасович
15

Вот ваша собственная groupByфункция, которая является обобщением кода из: 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
источник
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

Это также возможно с помощью простого forцикла:

 const result = {};

 for(const {make, model, year} of cars) {
   if(!result[make]) result[make] = [];
   result[make].push({ model, year });
 }
Йонас Вильмс
источник
И, наверное, быстрее и проще. Я расширил ваш фрагмент, сделав его немного более динамичным, поскольку у меня был длинный список полей из таблицы db, которые я не хотел вводить. Также обратите внимание, что вам нужно будет заменить const на let. for ( let { TABLE_NAME, ...fields } of source) { result[TABLE_NAME] = result[TABLE_NAME] || []; result[TABLE_NAME].push({ ...fields }); }
Адриан 04
ТИЛЬ, спасибо! medium.com/@mautayro/...
Adrien
9

Я бы оставил REAL GROUP BYдля примера JS Arrays точно такую ​​же задачу здесь

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
источник
7

Вы можете попробовать изменить объект внутри функции, вызываемой для каждой итерации с помощью _.groupBy func. Обратите внимание, что исходный массив меняет свои элементы!

var res = _.groupBy(cars,(car)=>{
    const makeValue=car.make;
    delete car.make;
    return makeValue;
})
console.log(res);
console.log(cars);
nickxbs
источник
1
Хотя этот код может решить вопрос, в том числе объяснение того, как и почему он решает проблему, действительно поможет улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос будущих читателей, а не только человеку, который задает его сейчас! Отредактируйте свой ответ, чтобы добавить пояснение и указать, какие ограничения и предположения применяются.
Makyen
Мне кажется, это лучший ответ, так как вы проходите через массив только один раз, чтобы получить желаемый результат. Нет необходимости использовать другую функцию для удаления makeсвойства, к тому же оно более читабельно.
Carrm
5

Создайте метод, который можно использовать повторно

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

Затем ниже вы можете сгруппировать по любым критериям

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

Вахинья Брайан
источник
5

Для случаев, когда ключ может быть нулевым, и мы хотим сгруппировать их как другие

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
источник
3

Версия прототипа также с использованием ES6. В основном это использует функцию уменьшения для передачи аккумулятора и текущего элемента, который затем использует это для построения ваших «сгруппированных» массивов на основе переданного ключа. внутренняя часть сокращения может выглядеть сложным, но по сути это проверка, чтобы увидеть, существует ли ключ переданного в объекте, и если он не создает пустой массив и добавляет текущий элемент к этому вновь созданному массиву, в противном случае, используя распространение оператор передает все объекты текущего массива ключей и добавляет текущий элемент. Надеюсь, это кому-то поможет !.

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

Вы также можете использовать array#forEach()такой метод:

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

Я люблю писать его без зависимости / сложности, просто чистый простой 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

Я сделал тест, чтобы проверить производительность каждого решения, не использующего внешние библиотеки.

JSBen.ch

reduce()Вариант, отправленный @Nina Scholz кажется оптимальным.

Леонардофмед
источник
0

Мне понравился ответ @metakunfu, но он не дает точно ожидаемого результата. Вот обновление, которое избавляет от «make» в окончательной полезной нагрузке 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));

Выход:

{  
   "audi":[  
      {  
         "model":"r8",
         "year":"2012"
      },
      {  
         "model":"rs5",
         "year":"2013"
      }
   ],
   "ford":[  
      {  
         "model":"mustang",
         "year":"2012"
      },
      {  
         "model":"fusion",
         "year":"2015"
      }
   ],
   "kia":[  
      {  
         "model":"optima",
         "year":"2012"
      }
   ]
}
Даниил Вукасович
источник
0

Просто попробуйте этот, он отлично подходит для меня.

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

agravat.in
источник
2
Uncaught ReferenceError: _ не определен - вам должно быть ясно, что ваше решение требует установки сторонней библиотеки только для решения этой проблемы.
metakungfu
2
извините, я думаю, что все знают. _ стоит и в основном используется для lodash lib. поэтому вам нужно использовать lodash. пожалуйста, прочтите вопрос, чтобы вы знали, что он / она просит lodash. что ж, спасибо тебе. я запомню это. и никогда не забывайте писать lib.
agravat.in
Вы должны отредактировать свой ответ, включив в него использование библиотеки lib.
Рафаэль Оливейра,
0

С помощью lodash / fp вы можете создать функцию с _.flow()этой 1-й группой по ключу, а затем сопоставить каждую группу и опустить ключ из каждого элемента:

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

Основываясь на ответе @Jonas_Wilms, если вы не хотите вводить все свои поля:

    var result = {};

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

Я не проводил никаких тестов, но считаю, что использование цикла for будет более эффективным, чем все, что предлагается в этом ответе .

Адриан
источник
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
источник
0

Сгруппированный массив объектов в машинописном тексте следующим образом:

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;
});
Олувафисайо Оволо
источник
Это выглядит неэффективным, поскольку вы ищите каждый ключ. Скорее всего, поиск имеет сложность O (n).
Leukipp
С Typescript у вас уже есть метод groupBy. Вы можете использоватьyour_array.groupBy(...)
Исак Моура,
-1

Вот еще одно решение. Как просили.

Я хочу создать новый массив объектов автомобилей, сгруппированных по make:

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

Выход:

console.log('Grouped by make key:',groupBy())
Евгений Суник
источник
-1

Вот решение, вдохновленное Collectors.groupingBy () в 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());
}

Это даст объект Map.

// Usage

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

Рахул Сетхи
источник