You can reduce()
the array into a Map
which makes accumulating duplicates straightforward. You can also just have a running tally of listeners thus avoiding a second iteration.
The result is a Map
of the shape:
{
"Artist1": {
"songs": [
{...},
{...}
]
"listeners": 0
},
"Artist2": {
"songs": [
{...},
{...}
]
"listeners": 0
}
...
}
const songs = [
{
"name": "The Less I Know the Better",
"duration": "0",
"listeners": "439958",
"mbid": "",
"url": "https://www.last.fm/music/Tame+Impala/_/The+Less+I+Know+the+Better",
"artist": {
"name": "Tame Impala",
"mbid": "63aa26c3-d59b-4da4-84ac-716b54f1ef4d",
"url": "https://www.last.fm/music/Tame+Impala"
},
"@attr": {
"rank": "0"
},
"genre": "reggae"
},
{
"name": "Creep",
"duration": "239",
"listeners": "1647583",
"mbid": "d11fcceb-dfc5-4d19-b45d-f4e8f6d3eaa6",
"url": "https://www.last.fm/music/Radiohead/_/Creep",
"artist": {
"name": "Radiohead",
"mbid": "a74b1b7f-71a5-4011-9441-d0b5e4122711",
"url": "https://www.last.fm/music/Radiohead"
},
"@attr": {
"rank": "1"
},
"genre": "jazz"
},
{
"name": "Go to Sleep",
"duration": "239",
"listeners": "16583",
"mbid": "d11fcceb-dfc5-4d19-b45d-f4e8f6d3eaa6",
"url": "https://www.last.fm/music/Radiohead/_/Go to Sleep",
"artist": {
"name": "Radiohead",
"mbid": "a74b1b7f-71a5-4011-9441-d0b5e4122711",
"url": "https://www.last.fm/music/Radiohead"
},
"@attr": {
"rank": "3"
},
"genre": "jazz"
}
]
const byArtist = songs.reduce((acc, song) => {
const artistName = song.artist.name;
const match = acc.get(artistName);
if (match) {
match.songs.push({...song});
match.listeners += parseInt(song.listeners);
} else {
acc.set(artistName, {songs: [{...song}], listeners: +song.listeners});
}
return acc;
}, new Map);
console.log(Object.fromEntries(byArtist));
Explanation
You can read up on Map
and reduce()
for further insight.
const byArtist = songs.reduce((acc, song) => {
// get the artist name from the current song object
const artistName = song.artist.name;
// try to get the element referenced by artistName,
// returns undefined if we haven't added that artist yet
// otherwise it returns the object holding "songs" and "listeners"
const match = acc.get(artistName);
if (match) {
// if match is truthy (successfully retrieved an entry)
// push a copy of the song into the "songs" array
match.songs.push({...song});
// increment the listener count by the songs listeners
match.listeners += parseInt(song.listeners);
} else {
// else set a new key: value pair useing artistName as the key
// and creating a new object contianing "songs" and "listeners"
acc.set(artistName, {songs: [{...song}], listeners: +song.listeners});
}
// return the mutated accumulator for the next iteration to use.
return acc;
}, new Map); // this initializes the accumulator as a new Map object
// creates an Object from the Map so that it can be logged easily
console.log(Object.fromEntries(byArtist));
CLICK HERE to find out more related problems solutions.