user882670
user882670

Reputation:

Group array items using object

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

Answers (20)

Nick Parsons
Nick Parsons

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

KARTHIKEYAN.A
KARTHIKEYAN.A

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

cmgchess
cmgchess

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

trincot
trincot

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

Muhammed Moussa
Muhammed Moussa

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

Stefan Avramovic
Stefan Avramovic

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

David
David

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

Kamil Kiełczewski
Kamil Kiełczewski

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

anayarojo
anayarojo

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

GAURAV
GAURAV

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

Eddie
Eddie

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

Anurag Singh Bisht
Anurag Singh Bisht

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

om sinha
om sinha

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

Nina Scholz
Nina Scholz

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

husayt
husayt

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

1983
1983

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

neuronaut
neuronaut

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

Papzord
Papzord

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

Brandon
Brandon

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

Ram
Ram

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

http://jsfiddle.net/dvgwodxq/

Upvotes: 6

Related Questions