使用对象对数组项进行分组

78

我的数组大概是这样的:

myArray = [
  {group: "one", color: "red"},
  {group: "two", color: "blue"},
  {group: "one", color: "green"},
  {group: "one", color: "black"}
]

我希望您能将这个转换为:

myArray = [
  {group: "one", color: ["red", "green", "black"]}
  {group: "two", color: ["blue"]}
]

所以,基本上就是按 group 分组。

我在尝试:

for (i in myArray){
  var group = myArray[i].group;
  //myArray.push(group, {???})
}

我只是不知道如何处理相似组值的分组。


你有尝试过什么吗?SO上已经有许多相关的问题了。请参考这个这个这个 - p.s.w.g
1
那里有很多语法错误。请在发布前测试您的代码。 - 1983
20个回答

87

首先创建一个组名到值的映射。然后将其转换为所需的格式。

var myArray = [
    {group: "one", color: "red"},
    {group: "two", color: "blue"},
    {group: "one", color: "green"},
    {group: "one", color: "black"}
];

var group_to_values = myArray.reduce(function (obj, item) {
    obj[item.group] = obj[item.group] || [];
    obj[item.group].push(item.color);
    return obj;
}, {});

var groups = Object.keys(group_to_values).map(function (key) {
    return {group: key, color: group_to_values[key]};
});

var pre = document.createElement("pre");
pre.innerHTML = "groups:\n\n" + JSON.stringify(groups, null, 4);
document.body.appendChild(pre);

使用数组实例方法如reducemap等可以帮助你构建强大的高级结构,从而避免手动循环的痛苦。


很棒的答案。使用Object.entries和ES6,我们还可以这样做:groups = Object.entries(group_to_values).map(([group, color]) => ({ group, color })); - a15n
太棒了!我的问题是使用Object.keys来提取键以形成对象数组。 - William
如果我的原始数组看起来像这样{group: "one", color: "red", size:"big"},我该如何获取最终对象中的另一个元素? - Jonathan

36

首先,在JavaScript中,通常不建议使用for ... in来迭代数组。有关详细信息,请参见为什么在数组迭代中使用for...in是个坏主意?

因此,您可以尝试像这样做:

var groups = {};
for (var i = 0; i < myArray.length; i++) {
  var groupName = myArray[i].group;
  if (!groups[groupName]) {
    groups[groupName] = [];
  }
  groups[groupName].push(myArray[i].color);
}
myArray = [];
for (var groupName in groups) {
  myArray.push({group: groupName, color: groups[groupName]});
}

在这里使用中介对象groups可以提高速度,因为它允许您避免嵌套循环来搜索数组。另外,由于groups是一个对象(而不是一个数组),使用for ... in来迭代它是适当的。

附录

顺便说一下,如果您想在结果数组中避免重复的颜色条目,您可以在groups[groupName].push(myArray[i].color);这行代码上面添加一个if语句来防止重复。使用jQuery的话会像这样:

if (!$.inArray(myArray[i].color, groups[groupName])) {
  groups[groupName].push(myArray[i].color);
}

如果没有jQuery,您可能需要添加一个函数,执行与jQuery的inArray相同的操作:

Array.prototype.contains = function(value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] === value)
      return true;
  }
  return false;
}

然后像这样使用:

if (!groups[groupName].contains(myArray[i].color)) {
  groups[groupName].push(myArray[i].color);
}

请注意,不论哪种情况,由于所有额外的迭代,您都会稍微减慢速度。因此,如果您不需要避免在结果数组中出现重复的颜色条目,我建议避免使用此额外代码。


1
很抱歉,如果在 _myArray_ 中有两个相同的对象,则无法正常工作。 使用 var myArray = [ {group: "one", color: "red"}, {group: "two", color: "blue"}, {group: "one", color: "green"}, {group: "one", color: "black"}, {group: "one", color: "black"} ]; 您将得到 myArray[0].color = ["red", "green", "black", "black"] - Ivan
“不起作用”是主观的。OP从未说明在这种情况下该怎么做。 - Jan
@Lends,OP从未指定这是一个要求。实际上,鉴于OP的评论,我认为这个解决方案确实“可行”。 - neuronaut
@neuronaut 抱歉,也许作者只是没有检查它,并且除了数组中对象的格式之外没有任何要求。不用担心,你的答案已经得到确认,我不会评判你。但如果你有时间纠正这个错误,那就太好了,这样其他人就可以使用你的代码而不是重复这个主题。谢谢! - Ivan
@Lends 不是一个“错误”,因此不需要纠正。你似乎想让我解决一个可能根本不存在的问题。在我的经验中,解决不存在的问题会导致大多数软件系统性能不佳和/或行为不正确。话虽如此,我想单独指出它对于那些好奇的人来说也无妨 - 我会添加一点内容来展示如何做到这一点。 - neuronaut
显示剩余2条评论

9
使用ES6,可以使用.reduce()Map作为累加器来完成这个操作,然后使用Array.from()和其映射函数将每个分组的map-entry映射到一个对象:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];

const res = Array.from(arr.reduce((m, {group, color}) => 
    m.set(group, [...(m.get(group) || []), color]), new Map
  ), ([group, color]) => ({group, color})
);

console.log(res);

上面是一个可重复使用的函数:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];

const groupAndMerge = (arr, groupBy, mergeInto) => {
  return Array.from(arr.reduce((m, obj) => 
      m.set(obj[groupBy], [...(m.get(obj[groupBy]) || []), obj[mergeInto]]), new Map
    ), ([grouped, merged]) => ({[groupBy]: grouped, [mergeInto]: merged})
  );
};

console.log(groupAndMerge(arr, "group", "color"));

如果您的对象除了“group”和“color”以外还有其他属性,您可以采取更一般的方法,通过将分组的对象设置为地图的值来实现,如下所示:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];

const groupAndMerge = (arr, groupBy, mergeInto) => 
  Array.from(arr.reduce((m, o) => {
    const curr = m.get(o[groupBy]);
    return m.set(o[groupBy], {...o, [mergeInto]: [...(curr && curr[mergeInto] || []), o[mergeInto]]});
  }, new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));

如果你可以支持可选链nullish coalescing operator (??),你可以将上面的方法简化为以下内容:

const arr = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}];
const groupAndMerge = (arr, groupBy, mergeWith) =>
  Array.from(arr.reduce((m, o) => m.set(o[groupBy], {...o, [mergeWith]: [...(m.get(o[groupBy])?.[mergeWith] ?? []), o[mergeWith]]}), new Map).values());

console.log(groupAndMerge(arr, 'group', 'color'));


7

使用 Lodash 的 groupby 方法

通过迭代器运行集合中的每个元素产生的键创建一个对象。分组值的顺序由它们在集合中出现的顺序决定。每个键对应的值是生成该键的元素数组。迭代器接受一个参数:(value)。

因此,使用 lodash,您可以在一行代码中得到想要的结果。请看下面:

let myArray = [
  {group: "one", color: "red"},
  {group: "two", color: "blue"},
  {group: "one", color: "green"},
  {group: "one", color: "black"},
]
let grouppedArray=_.groupBy(myArray,'group')
console.log(grouppedArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>


4
结果不符合所需的数据结构。 - Ibrahim Mohammed

6

一个选项是:

var res = myArray.reduce(function(groups, currentValue) {
    if ( groups.indexOf(currentValue.group) === -1 ) {
      groups.push(currentValue.group);
    }
    return groups;
}, []).map(function(group) {
    return {
        group: group,
        color: myArray.filter(function(_el) {
          return _el.group === group;
        }).map(function(_el) { return _el.color; })
    }
});

http://jsfiddle.net/dvgwodxq/


3
非常优雅而自包含,不错。我唯一的抱怨是它不像一个更简单的“for”循环那样立即可读。 - Jan
1
reduce,indexOf,map,filter。map 对我来说过于复杂了。 - 1983
1
@KingMob 定义“复杂”。那些都是普通的数组方法。 - Ram
它能够工作,但我需要一些时间来查看代码并理解它。非常感谢你的帮助! - user882670
1
@NunoNogueira 非常欢迎。那只是其中一种选择。 - Ram

5
除了使用两次扫描的方法,您还可以采用单循环方法,如果发现新组,则将该组推入堆栈中。

var array = [{ group: "one", color: "red" }, { group: "two", color: "blue" }, { group: "one", color: "green" }, { group: "one", color: "black" }],
    groups = Object.create(null),
    grouped = [];

array.forEach(function (o) {
    if (!groups[o.group]) {
        groups[o.group] = [];
        grouped.push({ group: o.group, color: groups[o.group] });
    }
    groups[o.group].push(o.color);
});

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


3

myArray = [
  {group: "one", color: "red"},
  {group: "two", color: "blue"},
  {group: "one", color: "green"},
  {group: "one", color: "black"}
];


let group = myArray.map((item)=>  item.group ).filter((item, i, ar) => ar.indexOf(item) === i).sort((a, b)=> a - b).map(item=>{
    let new_list = myArray.filter(itm => itm.group == item).map(itm=>itm.color);
    return {group:item,color:new_list}
});
console.log(group);


2
另一种选择是使用reduce()new Map()来对数组进行分组。使用Spread语法将set对象转换为数组。

var myArray = [{"group":"one","color":"red"},{"group":"two","color":"blue"},{"group":"one","color":"green"},{"group":"one","color":"black"}]

var result = [...myArray.reduce((c, {group,color}) => {
  if (!c.has(group)) c.set(group, {group,color: []});
  c.get(group).color.push(color);
  return c;
}, new Map()).values()];

console.log(result);


2

我喜欢使用Map构造函数回调创建分组(map键)。第二步是填充该地图的值,最后提取地图数据以所需的输出格式:

let myArray = [{group: "one", color: "red"},{group: "two", color: "blue"},
               {group: "one", color: "green"},{group: "one", color: "black"}];

let map = new Map(myArray.map(({group}) => [group, { group, color: [] }]));
for (let {group, color} of myArray) map.get(group).color.push(color);
let result = [...map.values()];

console.log(result);

 


2

这个版本利用了对象键的唯一性。我们处理原始数组,并在新对象中按组收集颜色。然后从该组创建新对象 -> 颜色数组映射。

var myArray = [{
      group: "one",
      color: "red"
    }, {
      group: "two",
      color: "blue"
    }, {
      group: "one",
      color: "green"
    }, {
      group: "one",
      color: "black"
    }];

    //new object with keys as group and
    //color array as value
    var newArray = {};

    //iterate through each element of array
    myArray.forEach(function(val) {
      var curr = newArray[val.group]

      //if array key doesnt exist, init with empty array
      if (!curr) {
        newArray[val.group] = [];
      }

      //append color to this key
      newArray[val.group].push(val.color);
    });

    //remove elements from previous array
    myArray.length = 0;

    //replace elements with new objects made of
    //key value pairs from our created object
    for (var key in newArray) {
      myArray.push({
        'group': key,
        'color': newArray[key]
      });
    }

请注意,此处未考虑同一分组内重复颜色的情况,因此在数组中可能会出现多个相同颜色的情况。

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