Группировка элементов в массиве с помощью нескольких свойств

Во время работы мне была задана эта задача: группировать элементы со схожими свойствами в массиве.

В общем случае проблема такова:

var list = [
    {name: "1", lastname: "foo1", age: "16"},
    {name: "2", lastname: "foo", age: "13"},
    {name: "3", lastname: "foo1", age: "11"},
    {name: "4", lastname: "foo", age: "11"},
    {name: "5", lastname: "foo1", age: "16"},
    {name: "6", lastname: "foo", age: "16"},
    {name: "7", lastname: "foo1", age: "13"},
    {name: "8", lastname: "foo1", age: "16"},
    {name: "9", lastname: "foo", age: "13"},
    {name: "0", lastname: "foo", age: "16"}
];

Если я сгруппирую эти элементы с помощью lastname и age, я получу этот результат:

var result = [
    [
        {name: "1", lastname: "foo1", age: "16"},
        {name: "5", lastname: "foo1", age: "16"}, 
        {name: "8", lastname: "foo1", age: "16"}
    ],
    [
        {name: "2", lastname: "foo", age: "13"},
        {name: "9", lastname: "foo", age: "13"}
    ],
    [
        {name: "3", lastname: "foo1", age: "11"}
    ],
    [
        {name: "4", lastname: "foo", age: "11"}
    ],
    [
        {name: "6", lastname: "foo", age: "16"},
        {name: "0", lastname: "foo", age: "16"}
    ],
    [
        {name: "7", lastname: "foo1", age: "13"}
    ]         
];

После некоторых экспериментов я пришел к следующему решению:

    Array.prototype.groupByProperties = function(properties){
        var arr = this;
        var groups = [];
        for(var i = 0, len = arr.length; i<len; i+=1){
            var obj = arr[i];
            if(groups.length == 0){
                groups.push([obj]);
            }
            else{
                var equalGroup = false;
                for(var a = 0, glen = groups.length; a<glen;a+=1){
                    var group = groups[a];
                    var equal = true;
                    var firstElement = group[0];
                    properties.forEach(function(property){

                        if(firstElement[property] !== obj[property]){
                            equal = false;
                        }

                    });
                    if(equal){
                        equalGroup = group;
                    }
                }
                if(equalGroup){
                    equalGroup.push(obj);
                }
                else {
                    groups.push([obj]);
                }
            }
        }
        return groups;
    };

Это решение работает, но является ли это правильным и лучшим способом? Это все еще выглядит немного уродливо для меня.

40 голосов | спросил Saike 10 TueEurope/Moscow2013-12-10T15:44:11+04:00Europe/Moscow12bEurope/MoscowTue, 10 Dec 2013 15:44:11 +0400 2013, 15:44:11

4 ответа


46

Я чувствовал себя вынужденным написать, что вы, вероятно, должны объединиться для каждого и составить карту с ответом Алексея Лебедева.

function groupBy( array , f )
{
  var groups = {};
  array.forEach( function( o )
  {
    var group = JSON.stringify( f(o) );
    groups[group] = groups[group] || [];
    groups[group].push( o );  
  });
  return Object.keys(groups).map( function( group )
  {
    return groups[group]; 
  })
}

var result = groupBy(list, function(item)
{
  return [item.lastname, item.age];
});
ответил konijn 11 WedEurope/Moscow2013-12-11T18:06:19+04:00Europe/Moscow12bEurope/MoscowWed, 11 Dec 2013 18:06:19 +0400 2013, 18:06:19
17

Основная проблема с вашей функцией - квадратичная временная сложность в худшем случае. Кроме того, если мы сначала реализуем общую функцию groupBy, группировка по свойствам становится тривиальной.

function arrayFromObject(obj) {
    var arr = [];
    for (var i in obj) {
        arr.push(obj[i]);
    }
    return arr;
}

function groupBy(list, fn) {
    var groups = {};
    for (var i = 0; i < list.length; i++) {
        var group = JSON.stringify(fn(list[i]));
        if (group in groups) {
            groups[group].push(list[i]);
        } else {
            groups[group] = [list[i]];
        }
    }
    return arrayFromObject(groups);
}

var result = groupBy(list, function(item) {
    return [item.lastname, item.age];
});

Возможно, вы захотите добавить hasOwnProperty в arrayFromObject, если ваше соглашение о кодировании не запрещает продление прототипа объекта.

ответил Alexey Lebedev 11 WedEurope/Moscow2013-12-11T03:27:37+04:00Europe/Moscow12bEurope/MoscowWed, 11 Dec 2013 03:27:37 +0400 2013, 03:27:37
7

Я считаю, что функциональный аспект JavaScript является большим преимуществом. Когда дело доходит до цикла, Array.prototype.forEach и кузены могут помочь вашему коду быть более описательным:

Array.prototype.defineProperty('groupByProperties', {
    value : function(properties){                       
        // will contain grouped items
        var result = []; 

        // iterate over each item in the original array
        this.forEach(function(item){
            // check if the item belongs in an already created group
            var added = result.some(function(group){
                // check if the item belongs in this group
                var shouldAdd = properties.every(function(prop){
                    return (group[0][prop] === item[prop]);
                });
                // add item to this group if it belongs 
                if (shouldAdd) {
                    group.push(item);
                }
                // exit the loop when an item is added, continue if not
                return shouldAdd;
            });

            // no matching group was found, so a new group needs to be created for this item
            if (!added) {
                result.push([item]);
            }
        });
        return result;
    }
});

Хотя я не считаю целесообразным добавлять пользовательские функции к предопределенным объектам (Array.prototype в этом случае), я оставил часть вашего решения. Однако я добавил groupByProperties как неперечислимое свойство groupByProperties, чтобы он не отображался в перечислениях Array.prototype.

ответил Tibos 10 TueEurope/Moscow2013-12-10T16:55:49+04:00Europe/Moscow12bEurope/MoscowTue, 10 Dec 2013 16:55:49 +0400 2013, 16:55:49
0

Другой способ сделать это - использовать _lodash.groupBy или _ lodash.keyBy :

  1. Вам нужно будет написать несколько строк кода для достижения такого же результата:

    const Results = _.groupBy(list, 'lastname')
    

    Это будет группировать ваши результаты по фамилии. Однако в вашем случае вам нужно сгруппировать по нескольким свойствам - вы можете использовать этот фрагмент , чтобы очаровать эту функцию.

  2. Конечно, вы можете использовать этот код несколько раз.

  3. Lodash позволяет вам устанавливать свои модули один за другим (npm i lodash.groupby);

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

ответил nerijusgood 3 MarpmThu, 03 Mar 2016 12:30:00 +03002016-03-03T12:30:00+03:0012 2016, 12:30:00

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132