Як групувати масив об’єктів за клавішами


153

Хтось знає про спосіб (лодаш, якщо це можливо) також згрупувати масив об'єктів за клавішею об'єкта, а потім створити новий масив об'єктів на основі групування? Наприклад, у мене є масив автомобільних об'єктів:

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?
СЛАкс

2
ваш результат недійсний.
Ніна Шольц

Чи існує подібний підхід до отримання Карти замість об'єкта?
Андреа Бергонцо

Відповіді:


104

Відповідь Тімо - як я це зробив би. Просте _.groupByі допускає деяке дублювання об’єктів у згрупованій структурі.

Однак ОП також просила 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.


278

У звичайному 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результати?
Мунір Ельфассі

1
Ви можете взяти записи Object.entriesта провести цикл через пари ключ / значення.
Ніна Шольц

Чи є спосіб видалити makeз набору даних, щойно згруповані? Це займає додаткове місце.
Меркуріал


Що означає r та підставка? Чи було б правильно вважати, що r - акумулятор і поточна величина?
Омар

68

Ви шукаєте _.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);

18
Якщо ви хочете все-таки коротше, var grouped = _.groupBy(cars, 'make');функція взагалі не потрібна, якщо аксесуар - це проста назва власності.
Джонатан Юніс

1
Що означає "_"?
Adrian Grzywaczewski

@AdrianGrzywaczewski це було умовою за замовчуванням для проміжків імен «lodash» або «підкреслення». Тепер, коли бібліотеки є модульними, це більше не потрібно, тобто. npmjs.com/package/lodash.groupby
vilsbole

5
І як я можу вступати в результат?
Луїс Антоніо Пестана

36

Коротка версія для групування масиву об’єктів за певним ключем у es6:

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

Більш довга версія:

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

Здається, в оригінальному запитанні задається питання про те, як згрупувати машини за маркою, але пропустити марку в кожній групі. Тож відповідь виглядатиме так:

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

це, безумовно , НЕ 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);


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

Я б залишив 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);


7

Ви можете спробувати змінити об'єкт всередині функції, яка називається за ітерацією функцією _.groupBy func. Зауважте, що вихідний масив змінює його елементи!

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

1
Хоча цей код може вирішити питання, включаючи пояснення, як і чому це вирішує проблему, справді допоможе покращити якість вашої публікації. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз запитує! Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Makyen

Це виглядає як найкраща відповідь для мене, оскільки ви проходите масив лише один раз, щоб отримати бажаний результат. Немає необхідності використовувати іншу функцію для видалення makeвластивості, і вона також зручніше для читання.
Карм

7

Також це можливо за допомогою простого 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 }); }
adrien

ТІЛ, спасибі! medium.com/@mautayro/…
adrien

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

4

Створіть метод, який можна повторно використовувати

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


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

Просто спробуйте цей, він добре працює для мене.

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


2
Uncaught ReferenceError: _ не визначено - вам повинно бути зрозуміло, що для вирішення цього рішення потрібно встановити сторонню бібліотеку.
metakungfu

1
вибачте, я думаю, що кожен знає. _ підставки і в основному використовуються для lodash lib. тому потрібно використовувати лодаш. будь ласка, прочитайте питання, щоб ви дізналися, що він / вона просить подати. добре дякую я пам’ятаю це. і ніколи не забудьте написати lib.
agravat.in

1

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

JSBen.ch

reduce()Варіант, відправлений @Nina Scholz здається оптимальним.


0

Мені сподобалася відповідь @metakunfu, але вона не дає точно очікуваного результату. Ось оновлений, який позбудеться "зробити" в остаточному навантаженні 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

За допомогою lodash / fp ви можете створити функцію з цими першими _.flow()групами за ключем, а потім скласти карту кожної групи та опустити ключ від кожного елемента:

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

Я не робив жодного орієнтиру, але вважаю, що використання циклу було б більш ефективним, ніж усе, що пропонується в цій відповіді .


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

Згрупований масив об’єктів у машинописному тексті з цим:

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

0

Я люблю писати це без залежності / складності, просто чисті прості 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

Ось ще одне його рішення. Як вимагалось.

Я хочу створити новий масив автомобільних об'єктів, згрупованих по 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);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.