Search code examples
javascriptjavascript-objects

group objects based on a value


Been pulling my hair out for a few hours now so its time to get help from SO

I have an array of objects, like this:

const data = [
  {city: "London", surname: "jones"},
  {city: "Manchester", surname: "jones"},
  {city: "Leeds", surname: "smith"},
  {city: "Birmingham", surname: "smith"},
  {city: "Rhyl", surname: "clarkson"},
  {city: "Blackpool", surname: "walker"},
  {city: "Rhyl", surname: "walker"},
  {city: "Blackpool", surname: "fletcher"}
];
// Actual data is much more complex, this is just a simplified example

I am basically trying to group my data based on a field, in this example surname. The surname values are not known before hand, so its needs to be dynamic. So you'd look at the next item in the array, if it matches the previous one its grouped, but if it doesnt match then it starts a new group.

the final outcome would look something like this:

const outcome = {
  jones: [...2 objects],
  smith: [...2 objects],
  clarkson: [...1 objects],
  walker: [...2 objects],
  fletcher: [...1 objects]
}

My initial thought was that I need to loop through each item and compare it with the next, so initially looked at reduce but couldnt get that right, so then I started looping through the items in the data array and comparing it with the next one (by getting the index from map and incrementing it to see the next item, but this feel really hacky.

algo's and CS isnt my strong point but I feel like the way im doing it is dirty and that there is already a pattern for this as it must be a common task

my next approach would be to loop through each add add something to the data such as a grouping id, i.e. group: 1, keep an increment and just ++ when ever a surname doesnt match the previous, but that still doesnt sound "optimal" as id then have to run a filter on each group to get the data in the right format.

N.B. I've seen several related SO questions to my problem but they miss an important factor about having to sort by a dynamic value, its an easy task if the values are known

Any guidance would be greatly appreciated

Thanks


Solution

  • There are many ways to do it. The reduce is the best, that came into my mind.

    See the following snippet:

    const data = [
      {city: "London", surname: "jones"},
      {city: "Manchester", surname: "jones"},
      {city: "Leeds", surname: "smith"},
      {city: "Birmingham", surname: "smith"},
      {city: "Rhyl", surname: "clarkson"},
      {city: "Blackpool", surname: "walker"},
      {city: "Rhyl", surname: "walker"},
      {city: "Blackpool", surname: "fletcher"}
    ];
    
    function group(array,field){
        return array.reduce((acc,elem)=>{
            //whether the field value exist
            if(acc[elem[field]]){
                //if the field value exist, push elem to it:
                acc[elem[field]].push(elem)
            }else{
                //if the field value doesn't exist, create an array with elem:
                acc[elem[field]]=[elem]
            }
            //return accumulator for later use
            return acc
        },{})
    }
    
    const outcome = group(data,"surname")
    
    console.log(outcome)