Join the Stack Overflow Community
Stack Overflow is a community of 6.5 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I want to summarize an array of objects and return the number of object occurrences in another array of objects. What is the best way to do this?

From this

var arrayOfSongs = [
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];

To this

var newArrayOfSongs = [
  {"title": "Blue", "playCount": 3 },
  {"title": "Green", "playCount": 1}
]

I have tried

 arrayOfSongs.reduce(function(acc, cv) {
   acc[cv.title] = (acc[cv.title] || 0) + 1;
     return acc;
   }, {});
 }

But it returns an object:

 { "Blue": 3, "Green": 1};
share|improve this question
    
"I am trying..." – and what did you try? How far did you get? What wrong? How did it go wrong? – David Thomas Dec 22 at 0:09
    
Object -> array is easy using Object.keys and Array#map ... e.g. Object.keys(obj).map(title => ({title, playCount:obj[title]})); – Jaromanda X Dec 22 at 0:16
    
It should be trivial to change that object into the array that you want, simply by iterating over the properties. – Barmar Dec 22 at 0:28
up vote 1 down vote accepted

You should pass the initial argument to the reduce function as an array instead of object and filter array for the existing value as below,

Working snippet:

var arrayOfSongs = [
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];


var newArrayOfSongs = arrayOfSongs.reduce(function(acc, cv) {
    var arr = acc.filter(function(obj) {
      return obj.title === cv.title;
    });
   
    if(arr.length === 0) {
      acc.push({title: cv.title, playCount: 1});
    } else {
      arr[0].playCount += 1;
    }
    
    return acc;
   }, []);

console.log(newArrayOfSongs);

share|improve this answer

To build on what you already have done, the next step is to "convert" the object to an array

    var arrayOfSongs = [
        {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
        {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
        {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
        {"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
    ];

    var obj = arrayOfSongs.reduce(function(acc, cv) {
       acc[cv.title] = (acc[cv.title] || 0) + 1;
       return acc;
    }, {});

    // *** added code starts here ***
    var newArrayOfSongs = Object.keys(obj).map(function(title) { 
        return {
            title: title, 
            playCount:obj[title]
        };
    });

    console.log(newArrayOfSongs);

share|improve this answer

I recommend doing this in two stages. First, chunk the array by title, then map the chunks into the output you want. This will really help you in future changes. Doing this all in one pass is highly complex and will increase the chance of messing up in the future.

var arrayOfSongs = [
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Blue","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"},
  {"title":"Green","duration":161.71,"audioUrl":"/assets/music/blue","playing":false,"playedAt":"2016-12-21T22:58:55.203Z"}
];

function chunkByAttribute(arr, attr) {
  return arr.reduce(function(acc, e) {
   acc[e[attr]] = acc[e[attr]] || [];
   acc[e[attr]].push(e);
   return acc;
  }, {});
}

var songsByTitle = chunkByAttribute(arrayOfSongs, 'title');

var formattedOutput = Object.keys(songsByTitle).map(function (title) {
  return {
    title: title,
    playCount: songsByTitle[title].length
  };
});

There, now everything is named according to what it does, everything does just one thing, and is a bit easier to follow.

share|improve this answer

https://jsfiddle.net/93e35wcq/

I used a set object to get the unique track titles, then used Array.map to splice those and return a song object that contains play count inside the track title.

The Data:

var arrayOfSongs = [{
  "title": "Blue",
  "duration": 161.71,
  "audioUrl": "/assets/music/blue",
  "playing": false,
  "playedAt": "2016-12-21T22:58:55.203Z"
}, {
  "title": "Blue",
  "duration": 161.71,
  "audioUrl": "/assets/music/blue",
  "playing": false,
  "playedAt": "2016-12-21T22:58:55.203Z"
}, {
  "title": "Blue",
  "duration": 161.71,
  "audioUrl": "/assets/music/blue",
  "playing": false,
  "playedAt": "2016-12-21T22:58:55.203Z"
}, {
  "title": "Green",
  "duration": 161.71,
  "audioUrl": "/assets/music/blue",
  "playing": false,
  "playedAt": "2016-12-21T22:58:55.203Z"
}];

The Function:

function getPlayCount(arrayOfSongs) {
  let songObj = {};
  let SongSet = new Set();
  arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
  for (let songTitle of SongSet.values()) {
    songObj[songTitle] = {
      playCount: 0
    };
    arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle].playCount++ : false)
  }
  return songObj;
}

console.log(getPlayCount(arrayOfSongs));

Which isn't exactly what you wanted formatting wise, but if you're married to it, this will do the trick:

    function getPlayCount(arrayOfSongs) {
  let songObj = {};
  let SongSet = new Set();
  arrayOfSongs.map(obj => (SongSet.has(obj.title)) ? true : SongSet.add(obj.title));
  for (let songTitle of SongSet.values()) {
    songObj[songTitle] = 0;
    arrayOfSongs.map(obj => (obj.title === songTitle) ? songObj[songTitle]++ : false)
  }
  return songObj;
}

console.log(getPlayCount(arrayOfSongs));

https://jsfiddle.net/93e35wcq/1/

share|improve this answer
    
why are you using arrayOfSongs.map twice where arrayOfSongs.forEach would be more appropriate as you don't use .map return value? and why is your result an Object, not an Array!!! – Jaromanda X Dec 22 at 0:34

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.