Reputation: 7490
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
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
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
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
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
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