atmd
atmd

Reputation: 7490

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

Upvotes: 1

Views: 230

Answers (5)

Naga Sai A
Naga Sai A

Reputation: 10975

To achieve expected result, use reduce and add objects based on surname key

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

console.log(data.reduce((acc,v)=> {
   acc[v.surname]? (acc[v.surname].push(v)) : (acc[v.surname] =[v])
  return acc
}, {}))

codepen - https://codepen.io/nagasai/pen/EJdamx?editors=1010

Upvotes: 0

FZs
FZs

Reputation: 18619

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)

Upvotes: 2

connexo
connexo

Reputation: 56744

Using a combination of a list of unique keys (de-duplicated using a Set), reduce, map and filter works:

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

const result = [...new Set(data.map(x => x.surname))]
  .reduce((acc, val) => { 
    return {...acc, [val]: [...(data
        .filter(x => x.surname === val)
        .map(x => { return { city: x.city } }))]};
  }, {});

console.log(result);

Upvotes: 1

Franck Abgrall
Franck Abgrall

Reputation: 141

You can use groupBy from lodash :

import { groupBy } from 'lodash'

const result = groupBy(data, item => item.surname)

The result will be :

{ jones:
   [ { city: 'London', surname: 'jones' },
     { city: 'Manchester', surname: 'jones' } ],
  smith:
   [ { city: 'Leeds', surname: 'smith' },
     { city: 'Birmingham', surname: 'smith' } ],
  clarkson: [ { city: 'Rhyl', surname: 'clarkson' } ],
  walker:
   [ { city: 'Blackpool', surname: 'walker' },
     { city: 'Rhyl', surname: 'walker' } ],
  fletcher: [ { city: 'Blackpool', surname: 'fletcher' } ] }

Upvotes: 1

junvar
junvar

Reputation: 11574

How do you want to handle persons with the same surname that do not occur continuously. E.g.: jones, jones, smith, jones?

If you would like to group them all, reduce is pretty simple to use as it's designed for this use case essentially:

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

let grouped = data.reduce((acc, d) => {
  acc[d.surname] = acc[d.surname] || [];
  acc[d.surname].push(d.city);
  return acc;
}, {});

console.log(grouped);

If you'd like to overwrite previous sequences of the same surname (e.g. jones, jones, smith, jones would map to 1 smith and 1 (the last) jones), then you can also keep track of the previous person iterated and compare surnames:

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"},
  {city: "non-continuous jones", surname: "jones"},
];

let grouped = data.reduce(([acc, prevSurname], d) => {
  if (prevSurname !== d.surname)
    acc[d.surname] = [];
  acc[d.surname].push(d.city);
  return [acc, d.surname];
}, [{}])[0];

console.log(grouped);

Upvotes: 0

Related Questions