JavaScript | 对象分组

59

我有一个物品。它看起来像下面这样:

[
  {
    "name":"Display",
    "group":"Technical detals",
    "id":"60",
    "value":"4"
  },
  {
    "name":"Manufacturer",
    "group":"Manufacturer",
    "id":"58",
    "value":"Apple"
  },
  {
    "name":"OS",
    "group":"Technical detals",
    "id":"37",
    "value":"Apple iOS"
  }
]

我想按照分组字段对这些数据进行分组,并获得以下对象:

var obj = {
    0 = [
    {
       'group'   = 'Technical detals',
       'name'    = 'Display',
       'id'      = '60',
       'value'   = '4'
    },
    {
       'group'   = 'Technical detals',
       'name'    = 'OS',
       'id'      = '37',
       'value'   = 'Apple iOS'
    }],
    1   = [
    {
       'group'   = 'Manufacturer',
       'name'    = 'Manufacturer',
       'id'      = '58',
       'value'   = 'Apple'
    }]
}

我该如何对我的第一个对象进行分组?


你真的想要一个对象字面量吗?既然你的索引是0和1,使用数组不是更好吗? - MaxArt
我认为这是可能的。 - XTRUST.ORG
16个回答

92

Reduce 在这种情况下十分有用。假设以下 list 是您的输入数据:

const list = [{
    'name': 'Display',
    'group': 'Technical detals',
    'id': '60',
    'value': '4'
  },
  {
    'name': 'Manufacturer',
    'group': 'Manufacturer',
    'id': '58',
    'value': 'Apple'
  },
  {
    'name': 'OS',
    'group': 'Technical detals',
    'id': '37',
    'value': 'Apple iOS'
  }
];

const groups = list.reduce((groups, item) => {
  const group = (groups[item.group] || []);
  group.push(item);
  groups[item.group] = group;
  return groups;
}, {});

console.log(groups);

如果想要不可变性,您可以像这样编写reduce

const list = [{
    'name': 'Display',
    'group': 'Technical detals',
    'id': '60',
    'value': '4'
  },
  {
    'name': 'Manufacturer',
    'group': 'Manufacturer',
    'id': '58',
    'value': 'Apple'
  },
  {
    'name': 'OS',
    'group': 'Technical detals',
    'id': '37',
    'value': 'Apple iOS'
  }
];

const groups = list.reduce((groups, item) => ({
  ...groups,
  [item.group]: [...(groups[item.group] || []), item]
}), {});

console.log(groups);

根据您的环境是否支持扩展语法。


44

尝试使用类似以下的内容:

function groupBy(collection, property) {
    var i = 0, val, index,
        values = [], result = [];
    for (; i < collection.length; i++) {
        val = collection[i][property];
        index = values.indexOf(val);
        if (index > -1)
            result[index].push(collection[i]);
        else {
            values.push(val);
            result.push([collection[i]]);
        }
    }
    return result;
}

var obj = groupBy(list, "group");

请记住,在IE8及更早版本中,Array.prototype.indexOf未定义,但有常见的polyfill可供使用。


如何将0、1等更改为键的名称,例如“group”? - silvia zulinka
2
我知道这已经过时了,但我想有些人可能会偶然发现这个问题,并希望将分组值作为键,使用相同的函数只需修改if else部分,像这样:if (index > -1) result[val].push(collection[i]); else { values.push(val); result[val] = []; result[val].push([collection[i]]); } - Jojofoulk

22
如果您喜欢使用ES6 Map,那么这就是为您准备的。

function groupBy(arr, prop) {
    const map = new Map(Array.from(arr, obj => [obj[prop], []]));
    arr.forEach(obj => map.get(obj[prop]).push(obj));
    return Array.from(map.values());
}

const data = [{ name: "Display", group: "Technical detals", id: 60, value: 4 }, { name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" }, { name: "OS", group: "Technical detals", id: 37, value: "Apple iOS" }];
 
console.log(groupBy(data, "group"));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Map实例是从输入数组中生成的键值对创建的。键是按属性分组的值,值被初始化为空数组。

然后这些数组被填充。最后返回map的值(即填充的数组)。


1
有人能帮我分解一下这个吗: obj => [obj[prop], []] - Ankit Agarwal
@AnkitAgarwal,这是一个函数(箭头函数语法)。它接受一个参数(obj),并返回一个包含两个条目的数组。第一个条目将具有值 obj[prop](要分组的属性值)。第二个条目是一个空数组。此函数作为回调参数传递给 Array.from,该函数将为 arr 中的每个对象调用它。Map 构造函数可以处理这样一对小数组,因此 new Map 将为每个组获取一个键,并且相应的值将是每个组的空数组。 - trincot

18

如果你的应用程序中使用了underscore.js,那么你可以简单地执行以下操作:

var groups = _.groupBy(data, 'group'); // data is your initial collection

如果您不喜欢使用任何库,那么您可以自己来实现:

var groups = { };
data.forEach(function(item){
   var list = groups[item.group];

   if(list){
       list.push(item);
   } else{
      groups[item.group] = [item];
   }
});

您可以在这里看到这两个示例的演示。


14

使用reducefilter 方法。

假设你的初始数组被赋值给data

data.reduce((acc, d) => {
    if (Object.keys(acc).includes(d.group)) return acc;

    acc[d.group] = data.filter(g => g.group === d.group); 
    return acc;
}, {})

这将为您提供类似于以下内容的东西

{
    "Technical detals" = [
    {
       'group'   = 'Technical detals',
       'name'    = 'Display',
       'id'      = '60',
       'value'   = '4'
    },
    {
       'group'   = 'Technical detals',
       'name'    = 'OS',
       'id'      = '37',
       'value'   = 'Apple iOS'
    }],
    "Manufacturer"   = [
    {
       'group'   = 'Manufacturer',
       'name'    = 'Manufacturer',
       'id'      = '58',
       'value'   = 'Apple'
    }]
}

复杂度从传统的O(n)变为O(n^2)。如果性能是关键因素,应考虑另一种方法。 - deathangel908

8
你可以使用哈希表来对组进行处理,使用 Array#forEach 来迭代数组。
然后检查哈希表是否存在,如果不存在,则将一个空数组分配给它,并将其推送到结果集中。
稍后将实际元素推送到哈希表的数组中。

function groupBy(array, group) {
    var hash = Object.create(null),
        result = [];

    array.forEach(function (a) {
        if (!hash[a[group]]) {
            hash[a[group]] = [];
            result.push(hash[a[group]]);
        }
        hash[a[group]].push(a);
    });
    return result;
}

var data = [{ name: "Display", group: "Technical detals", id: 60, value: 4 }, { name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" }, { name: "OS", group: "Technical detals", id: 37, value: "Apple iOS" }];
    
console.log(groupBy(data, "group"));
.as-console-wrapper { max-height: 100% !important; top: 0; }

更新 2022

您可以使用可能即将推出的Array#groupBy或一个 polyfill,并只取对象的值。

这些方法需要一个回调函数来进行分组。

({ group }) => group    // take property group and return it

Array.prototype.groupBy ??= function(callbackfn, thisArg) {
    const
        O = Object(this),
        len = O.length >>> 0;

    if (typeof callbackfn !== 'function') {
        throw new TypeError(callbackfn + ' is not a function');
    }

    let k = 0;
    const groups = {};

    while (k < len) {
        const
            Pk = Number(k).toString(),
            kValue = O[Pk],
            propertyKey = callbackfn.call(thisArg, kValue, Number(k), O);

        (groups[propertyKey] ??= []).push(kValue);
        ++k;
    }
    return groups;
}

const
    data = [{ name: "Display", group: "Technical details", id: 60, value: 4 }, { name: "Manufacturer", group: "Manufacturer", id: 58, value: "Apple" }, { name: "OS", group: "Technical details", id: 37, value: "Apple iOS" }],
    result = Object.values(data.groupBy(({ group }) => group));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }


你能在函数中以某种方式将其更改为降序吗? - Datacrawler
@ApoloRadomer,您说的“倒序”是什么意思?指哪个属性? - Nina Scholz
当我进行分组时,我可以看到JSON已按升序排序。我猜我必须使用reverse来使其降序。@Nina Scholz - Datacrawler

1
如果您正在使用lodash,您可以使用 groupBy。它支持数组和对象。示例:
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }

// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }

1

根据Anthony Awuley的回答,我为TypeScript准备了通用解决方案!

const groupBy = <T, K extends keyof T>(value: T[], key: K) =>
  value.reduce((acc, curr) => {
    if (acc.get(curr[key])) return acc;
    acc.set(curr[key], value.filter(elem => elem[key] === curr[key]));
    return acc;
  }, new Map<T[K], T[]>());

1

这是通过使用键值获取器来实现的,因此您可以动态地对数组对象进行分组。

const groupBy = (arr, keyGetter) => {
  const out = {};
  for (let item of arr) {
    const key = keyGetter(item);
    out[key] ??= [];
    out[key].push(item);
  }
  return out;
};

const tasks = [
  {task: 'Do the laundry', dueTime: "4/27 13:00"},
  {task: 'Do the dishes', dueTime: "4/27 15:30"},
  {task: 'Do homework', dueTime: "4/28 13:30"},
  {task: 'Clean the garage', dueTime: "4/29 14:00"}
];

// Group by the day a task is due
const grouped = groupBy(tasks, (item) => item.dueTime.slice(0, -6));
console.log(grouped);


1

有一个稍微不同的方法,我们有一个普通的对象列表,并希望按属性对其进行分组,但包括所有相关内容。

const data = [{'group':'1', 'name':'name1'},
{'group':'2', 'name':'name2'},
{'group':'2', 'name':'name3'},
,{'group':'1', 'name':'name4'}]; 

const list = data.map( i => i.group);
const uniqueList = Array.from(new Set(list));
const groups= uniqueList.map( c => { 
            return  { group:c, names:[]};
        } ); 

data.forEach( d => { 
            groups.find( g => g.group === d.group).names.push(d.name);
});

所以结果将会是这样的:
[ {'group':'1', 'names':['name1', 'name4']},
{'group':'2', 'names':['name2', 'name3']}

使用TypeScript和reduce实现相同的功能:

export function groupBy2 <T>( key: string, arr: T[]): { group: string, values: T[] }[] {
     return arr.reduce((storage, item) => {
         const a = storage.find(g => g.group === item[key]);
         if (a) { a.values.push(item); }
         else { storage.push({group: item[key], values: [item]}); }
         return storage;
     }, [] as {group: string, values: T[] }[]);
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接