Reputation:
My array is something like this:
myArray = [
{group: "one", color: "red"},
{group: "two", color: "blue"},
{group: "one", color: "green"},
{group: "one", color: "black"}
]
I want to convert this into:
myArray = [
{group: "one", color: ["red", "green", "black"]}
{group: "two", color: ["blue"]}
]
So, basically, group by group
.
I'm trying:
for (i in myArray){
var group = myArray[i].group;
//myArray.push(group, {???})
}
I just don't know how to handle the grouping of similar group values.
Upvotes: 78
Views: 139151
Reputation: 50749
You can do this using the new Map.groupBy
method. With it, you can create a Map
keyed by your group
property and then use Array.from()
with a mapping function to convert the grouped Map into your desired array structure:
const res = Array.from(Map.groupBy(arr, o => o.group), ([group, objs]) => ({group, color: objs.map(o => o.color)}));
const arr = [ {group: "one", color: "red"}, {group: "two", color: "blue"}, {group: "one", color: "green"}, {group: "one", color: "black"} ];
const groupBy = "group", mergeOn = "color";
const res = Array.from(
Map.groupBy(arr, obj => obj[groupBy]),
([group, grouped]) => ({[groupBy]: group, [mergeOn]: grouped.map(o => o[mergeOn])})
);
console.log(res);
If you can't support the above approach, you can try using .reduce()
with a Map
as the accumulator, and then using Array.from()
with its mapping function to map each grouped map-entry to an object:
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);
The above as a reusable function:
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"));
If you have additional properties in your objects other than just group
and color
, you can take a more general approach by setting a grouped object as the map's values like so:
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'));
If you can support optional chaining and the nullish coalescing operator (??), you can simplify the above method to the following:
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'));
Upvotes: 9
Reputation: 20088
We can use Array Reducer method and Map method to group similar objects, In my case common properties are clientName, Id, groupId etc.
const ClientDetails = [
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea53",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110117,
"clientName": "Apple",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1579",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea54",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110117,
"clientName": "Apple",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1579",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea55",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110118,
"clientName": "Mango",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1589",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea56",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110118,
"clientName": "Mango",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1589",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea57",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110119,
"clientName": "Orange",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1599",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea58",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110119,
"clientName": "Orange",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1599",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
},
{
"checked": false,
"data": {
"accountId": "eb9e38a8-2e0e-46c2-b50a-fa5c7c18ea59",
"address": {
"address1": "",
"city": "",
"state": "",
"zipcode": "",
"country": ""
},
"Id": 110119,
"clientName": "Orange",
"groupId": "ff686b1c-0d83-4e9e-ac0e-edd4ed7a1599",
},
"isCollapsed": false,
"validationErrors": [
"Some data validations failed. Please correct and submit"
]
}
]
const deMergeMF = manageFunds => {
const result = [
...manageFunds
.reduce((r, { Id, clientName, groupId, ...rest }) => {
r.has(groupId) ||
r.set(groupId, {
groupId,
clientName,
Id,
manageFunds: [],
})
r.get(groupId).manageFunds.push({ ...rest })
return r
}, new Map())
.values(),
]
return result.map(alterResult => ({...alterResult, size: alterResult.manageFunds.length, bulkUploadErrorMessage: ''}))
}
console.log(deMergeMF(ClientDetails.map(item => item.data)))
Upvotes: 0
Reputation: 10247
Since the group
field is used to group in the reduce
step, it creates an object in the form of
{
one: {
color: ["red", "green", "black"],
group: "one"
},
two: {
color: ["blue"],
group: "two"
}
}
So to get the values array in the desired format can use Object.values
on the reduce
result
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}......
*/
Upvotes: 0
Reputation: 350272
I like to use the Map
constructor callback for creating the groups (map keys). The second step is to populate the values of that map, and finally to extract the map's data in the desired output format:
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);
Upvotes: 2
Reputation: 5195
this repo offers solutions in lodash and alternatives in native Js, you can find how to implement groupby. https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_groupby
Upvotes: 1
Reputation: 1353
This gives you unique colors, if you do not want duplicate values for color
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)
Upvotes: 1
Reputation: 529
My approach with a reducer:
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;
}, []))
Upvotes: 1
Reputation: 92417
Try (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);
Upvotes: -1
Reputation: 1205
You can extend array functionality with the next:
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];
});
};
Usage example:
/* 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'));
Credits: @Wahinya Brian
Upvotes: 0
Reputation: 703
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);
Upvotes: 3
Reputation: 26844
Another option is using reduce()
and new Map()
to group the array. Use Spread syntax
to convert set object into an array.
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);
Upvotes: 2
Reputation: 2753
Using Array's reduce
and findIndex
methods, this can be achieved.
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);
By using reduce
function, array is iterator and the new values are stored in acc (accumulating)
parameter. To check if the object with given group
already exists we can use findIndex
function.
If findIndex()
return -1, the value does not exist, so add the array in the acc
parameter.
If findIndex()
return index, then update the index
with the arr
values.
Upvotes: -1
Reputation: 11
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))
}
Upvotes: 1
Reputation: 386604
Beside the given approaches with a two pass approach, you could take a single loop approach by pushing the group if a new group is found.
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; }
Upvotes: 5
Reputation: 15139
Use lodash's groupby
method
Creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).
So with lodash you can get what you want in a single line. See below
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>
Upvotes: 7
Reputation: 5963
Start by creating a mapping of group names to values. Then transform into your desired format.
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);
Using Array instance methods such as reduce and map gives you powerful higher-level constructs that can save you a lot of the pain of looping manually.
Upvotes: 87
Reputation: 2709
First, in JavaScript it's generally not a good idea to iterate over arrays using for ... in
. See Why is using "for...in" with array iteration a bad idea? for details.
So you might try something like this:
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]});
}
Using the intermediary groups
object here helps speed things up because it allows you to avoid nesting loops to search through the arrays. Also, because groups
is an object (rather than an array) iterating over it using for ... in
is appropriate.
Addendum
FWIW, if you want to avoid duplicate color entries in the resulting arrays you could add an if
statement above the line groups[groupName].push(myArray[i].color);
to guard against duplicates. Using jQuery it would look like this;
if (!$.inArray(myArray[i].color, groups[groupName])) {
groups[groupName].push(myArray[i].color);
}
Without jQuery you may want to add a function that does the same thing as jQuery's inArray
:
Array.prototype.contains = function(value) {
for (var i = 0; i < this.length; i++) {
if (this[i] === value)
return true;
}
return false;
}
and then use it like this:
if (!groups[groupName].contains(myArray[i].color)) {
groups[groupName].push(myArray[i].color);
}
Note that in either case you are going to slow things down a bit due to all the extra iteration, so if you don't need to avoid duplicate color entries in the result arrays I would recommend avoiding this extra code. There
Upvotes: 36
Reputation: 408
You can do something like this:
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;
}
Upvotes: 0
Reputation: 294
This version takes advantage that object keys are unique. We process the original array and collect the colors by group in a new object. Then create new objects from that 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]
});
}
Please note that this does not take into account duplicate colors of the same group, so it is possible to have multiple of the same color in the array for a single group.
Upvotes: 2
Reputation: 144689
One option is:
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; })
}
});
Upvotes: 6