在JavaScript中按属性对对象进行分组

31

如何将此转换:

[
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
]

转换成这样:

[
    {type: 'fruit', foods: ['apple', 'banana']},
    {type: 'vegetable', foods: ['potato']}
]

使用 JavaScript 或 Underscore


2
@Harley 最好展示一下你尝试过的内容。简洁并不像展示努力那样重要。这不仅仅是一个空洞的请求,看到你的进展可以让我们更好地创造出完全符合需求水平的东西。 - ErikE
1
@ErikE 或者说,这是一个非常简单易懂的问题,可以通过高效的谷歌搜索得到答案,没有那些失败和复杂的解决方案。这种类型的问题会得到最多的赞同,因为它是通用的,答案也是如此,最终使每个人受益。 - SSH This
@SSHThis 每个人都有自己的观点,尽管有些观点可能比其他观点更符合现实。我认为你会发现,一旦你在这个网站上参与得更久,展示努力是社区价值观的重要组成部分... - ErikE
1
@ErikE 不需要这么尖刻...再说一遍。 - SSH This
@SSHThis,我和你有一个事实上的分歧,仅此而已! - ErikE
8个回答

93
假设原始列表包含在名为list的变量中:
_
.chain(list)
.groupBy('type')
.map(function(value, key) {
    return {
        type: key,
        foods: _.pluck(value, 'food')
    }
})
.value();

2
当你拥有下划线和MongoDB时,谁还需要冗长的SQL呢? - Alex
需要让代码性能良好、避免在 RAM 上执行数据查询操作并利用数据库服务器的人。 - Rodrigo Rodrigues

17

不使用下划线:

var origArr = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'}
];

/*[
    {type: 'fruit', foods: ['apple', 'banana']},
    {type: 'vegetable', foods: ['potato']}
]*/

function transformArr(orig) {
    var newArr = [],
        types = {},
        i, j, cur;
    for (i = 0, j = orig.length; i < j; i++) {
        cur = orig[i];
        if (!(cur.type in types)) {
            types[cur.type] = {type: cur.type, foods: []};
            newArr.push(types[cur.type]);
        }
        types[cur.type].foods.push(cur.food);
    }
    return newArr;
}

console.log(transformArr(origArr));

演示: http://jsfiddle.net/ErikE/nSLua/3/

感谢@ErikE改进并简化了我的原始代码,帮助我解决了冗余问题 :)


建议:将 newArr.push(types[cur.type]); 放在你的 if 块内,并完全删除第二个 for 循环。 - ErikE
@ErikE 是的,我已经在重构了。我知道双重循环不好,所以我回去改了。等一下,也许我正在做你说的事情。 - Ian
索引间接不需要。我认为新的代码很别扭。请参见这个链接 - ErikE
@ErikE 啊是的,我又被骗了。我忘记了这样做,新对象是通过引用推送的(我没有意识到),所以您可以修改原始对象(在“types”中),并且它将反映在“newArr”中。谢谢您指出这一点!尽管我几乎不会称我的旧代码为笨拙...至少对我来说作为“查找”是有道理的 :) - Ian
我坦率地认为,您使用对象来跟踪部件的想法非常好。只是要有效地实现它需要一些工作。而且,JavaScript中的对象是真正的对象,并且通过引用访问。 - ErikE
显示剩余2条评论

6

这里是 @Ian 回答的略微不同但更通用的版本。

注意:结果与 OP 的要求略有不同,但像我这样偶然看到这个问题的人可能会从一个更通用的答案中受益,依我之见。

var origArr = [
   {food: 'apple', type: 'fruit'},
   {food: 'potato', type: 'vegetable'},
   {food: 'banana', type: 'fruit'}
];

function groupBy(arr, key) {
        var newArr = [],
            types = {},
            newItem, i, j, cur;
        for (i = 0, j = arr.length; i < j; i++) {
            cur = arr[i];
            if (!(cur[key] in types)) {
                types[cur[key]] = { type: cur[key], data: [] };
                newArr.push(types[cur[key]]);
            }
            types[cur[key]].data.push(cur);
        }
        return newArr;
}

console.log(groupBy(origArr, 'type'));

您可以在这里找到一个 jsfiddle 示例。


嗨。 这正是我要找的!谢谢! -- 我认为展示 console.log 打印出来的内容是个好主意。 - onebree
未使用的变量newItem和j。为什么? - maXp

3

这是一个关于旧问题的ES6解决方案:

使用Array#reduce进行迭代,并将项按组收集到Map中。使用spread将Map#values转换回数组:

const data = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
];

const result = [...data.reduce((hash, { food, type }) => {
  const current = hash.get(type) || { type, foods: [] };
  
  current.foods.push({ food });
  
  return hash.set(type, current);
}, new Map).values()];

console.log(result);


2
var foods = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'}
];

var newFoods = _.chain( foods ).reduce(function( memo, food ) {
  memo[ food.type ] = memo[ food.type ] || [];
  memo[ food.type ].push( food.food );
  return memo;
}, {}).map(function( foods, type ) {
    return {
        type: type,
        foods: foods
    };
}).value();

http://jsbin.com/etaxih/2/edit


0

您可以使用Alasql库按照其中一个字段对对象数组进行分组。以下示例与您的示例完全相同:

var res = alasql('SELECT type, ARRAY(food) AS foods FROM ? GROUP BY type',[food]);

试试这个例子{{link1:在jsFiddle上}}。


嗨,agershum,我尝试按照您在由Ajax创建的数组上编写的方式操作,但结果是未定义的。但是,如果我尝试console.log(array [0] .property);控制台中的数据是正确的。 - sundsx
嗨,agershum,我尝试按照你写的方法在从ajax创建的数组上操作,但是不起作用。结果总是未定义。例如:var array = [{ ​​ status: undefinedstudent_id: "32" ​​ student_name: "Marco Bruno" ​​ student_year_id: "2",status: absent} ...到500条记录 ]但是如果我尝试console.log(array [0] .property);控制台中的数据就没问题了。我想计算student_id并按状态分组。谢谢 - sundsx

0

您可以使用 Array.group() 方法以及目前处于实验阶段的 Array.forEach() 方法来实现此目的。在生产环境中使用之前,建议您仔细查看 浏览器兼容性表

以下代码片段仅适用于 Firefox Nightly,因为目前 Array.group() 不支持其他任何浏览器。

// Input array
const inventory = [
    {food: 'apple', type: 'fruit'},
    {food: 'potato', type: 'vegetable'},
    {food: 'banana', type: 'fruit'},
];

// Return the object grouped with type property.
const result = inventory.group(({ type }) => type);

// declaring an empty array to store the final results.
const arr = [];

// Iterating the group object to structure the final result.
Object.keys(result).forEach((item, index) => {
  result[item] = result[item].map(({ food }) => food);
  arr.push({
    type: item,
    foods: result[item]
  })
});

// Output
console.log(arr);


0

您还可以使用其他ES6功能,例如:

function groupArray(arr, groupBy, keepProperty) {
        let rArr = [], i;
        arr.forEach(item => {
            if((i = rArr.findIndex(obj => obj[groupBy] === item[groupBy])) !== -1)
                rArr[i][`${keepProperty}s`].push(item[keepProperty]);
            else rArr.push({
                [groupBy]: item[groupBy],
                [`${keepProperty}s`]: [item[keepProperty]]
            });
        });
        return rArr;
    }

    groupArray(yourArray, 'type', 'food');

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