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

我的数组是这样的:

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, {???})
}

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

132479 次浏览

首先,在 JavaScript 中,使用 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对其进行迭代是合适的。

附录

FWIW,如果您想要避免重复的颜色条目在结果数组中,您可以添加一个 if语句以上的行 groups[groupName].push(myArray[i].color);,以防止重复。使用 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);
}

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

一个选择是:

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/

此版本利用了对象键是唯一的这一点。我们处理原始数组并在新对象中按组收集颜色。然后从 group-> color array map 创建新对象。

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

请注意,这并没有考虑到同一组的重复颜色,因此在一个组的数组中可能有多个相同颜色。

你可以这样做:

function convert(items) {
var result = [];


items.forEach(function (element) {
var existingElement = result.filter(function (item) {
return item.group === element.group;
})[0];


if (existingElement) {
existingElement.color.push(element.color);
} else {
element.color = [element.color];
result.push(element);
}


});


return result;
}

首先创建组名称到值的映射。 然后转换成您想要的格式。

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

使用象 减少地图这样的 Array 实例方法可以为您提供强大的高级构造,从而可以省去手动循环的许多麻烦。

使用 loash 的 groupby方法

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

所以使用 loash,你可以在一行中得到你想要的

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>

除了具有两步法的给定方法之外,如果发现新的组,可以采用单循环方法推送组。

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

var array = [{
id: "123",
name: "aaaaaaaa"
}, {
id: "123",
name: "aaaaaaaa"
}, {
id: '456',
name: 'bbbbbbbbbb'
}, {
id: '789',
name: 'ccccccccc'
}, {
id: '789',
name: 'ccccccccc'
}, {
id: '098',
name: 'dddddddddddd'
}];
//if you want to group this array
group(array, key) {
console.log(array);
let finalArray = [];
array.forEach(function(element) {
var newArray = [];
array.forEach(function(element1) {
if (element[key] == element1[key]) {
newArray.push(element)
}
});
if (!(finalArray.some(arrVal => newArray[0][key] == arrVal[0][key]))) {
finalArray.push(newArray);
}
});
return finalArray
}
//and call this function
groupArray(arr, key) {
console.log(this.group(arr, key))
}

使用 Array 的 reducefindIndex方法可以实现这一点。

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


var transformedArray = myArray.reduce((acc, arr) => {
var index = acc.findIndex(function(element) {
return element.group === arr.group;
});
if (index === -1) {
return acc.push({
group: arr.group,
color: [arr.color]
});
}
  

acc[index].color.push(arr.color);
return acc;
}, []);


console.log(transformedArray);

通过使用 reduce函数,数组是迭代器,新值存储在 acc (accumulating)参数中。为了检查具有给定 group的对象是否已经存在,我们可以使用 findIndex函数。

如果 findIndex()返回 -1,则该值不存在,因此在 acc参数中添加数组。

如果 findIndex()返回索引,则用 arr值更新 index

另一个选项是使用 reduce()new Map()对数组进行分组。使用 Spread syntax将设置的对象转换为数组。

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

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

您可以使用下面的代码扩展数组功能:

Array.prototype.groupBy = function(prop) {
var result = this.reduce(function (groups, item) {
const val = item[prop];
groups[val] = groups[val] || [];
groups[val].push(item);
return groups;
}, {});
return Object.keys(result).map(function(key) {
return result[key];
});
};

用法例子:

/* re-usable function */
Array.prototype.groupBy = function(prop) {
var result = this.reduce(function (groups, item) {
const val = item[prop];
groups[val] = groups[val] || [];
groups[val].push(item);
return groups;
}, {});
return Object.keys(result).map(function(key) {
return result[key];
});
};


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


console.log(myArray.groupBy('group'));

来源: @ Wahinya Brian

试试(h = {})

myArray.forEach(x=> h[x.group]= (h[x.group]||[]).concat(x.color) );
myArray = Object.keys(h).map(k=> ({group:k, color:h[k]}))

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


let h={};


myArray.forEach(x=> h[x.group]= (h[x.group]||[]).concat(x.color) );
myArray = Object.keys(h).map(k=> ({group:k, color:h[k]}))


console.log(myArray);

我用减速器的方法是:

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


console.log(myArray.reduce( (acc, curr) => {
const itemExists = acc.find(item => curr.group === item.group)
if(itemExists){
itemExists.color = [...itemExists.color, curr.color]
}else{
acc.push({group: curr.group, color: [curr.color]})
}
return acc;
}, []))

如果您不希望颜色值重复,这将为您提供唯一的颜色

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


var arra = [...new Set(arr.map(x => x.group))]


let reformattedArray = arra.map(obj => {
let rObj = {}
rObj['color'] = [...new Set(arr.map(x => x.group == obj ? x.color:false ))]
.filter(x => x != false)
rObj['group'] = obj
return rObj
})
console.log(reformattedArray)

这个 repo 提供了 loash 中的解决方案和本地 Js 中的替代方案,您可以找到如何实现 groupby。 Https://github.com/you-dont-need/you-dont-need-lodash-underscore#_groupby

使用 ES6,可以很好地使用 .reduce()Map作为累加器,然后使用带映射功能的 Array.from()将每个分组映射条目映射到一个对象:

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

如果你的对象中除了 groupcolor之外还有其他属性,你可以采取一种更通用的方法,将一个分组的对象设置为映射的值,如下所示:

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

如果你能够支持 可选的锁链空结合运算符(? ?),你可以简化以下方法:

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

我喜欢使用 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);


因为 group字段用于在 reduce步骤中进行分组,所以它创建的对象的形式为

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

因此,要获得所需格式的值数组,可以对 reduce结果使用 Object.values

let myArray = [
{group: "one", color: "red"},
{group: "two", color: "blue"},
{group: "one", color: "green"},
{group: "one", color: "black"}
]
let res = Object.values(myArray.reduce((acc,{group,color}) => {
acc[group] = acc[group] || {group,color:[]}
acc[group].color.push(color)
return acc
},{}))


console.log(res)


/*
//If need to overrite myArray
myArray = Object.values(myArray.reduce((acc,{group,color}......
*/