Senshi
Senshi

Reputation: 413

Javascript sort array of objects on key

I have an array of objects (coming from a ajax/PDO call), like this:

signincounts: [{ status: 1
   count:  3
   teamid: 4
 },{
   status: 0
   count:  1
   teamid: 2
 },{
   status: 0
   count:  2
   teamid: 4
}]

These object represent the number of users signin in for a particular event:

In addition, I have the following information:

teamcount : {
    2: 5
    4: 6
}

I now want to sort this in Javascript to display the signin stats like this:

team 2: signed in/ signed out / not yet decided

team 4: signed in/ signed out / not yet decided

In this case that would be:

team 2: 0 / 1 / 4

team 4: 3 / 2 / 1

The difficulties:

I was thinking I need to sort it, but am unsure on how to approach this because of undefined zero counts and non-sequential teamids. My initial plan was to use two empty arrays (signin_count, signout_count), then loop over the ajax JSON and push the counts in there (and unshift in case it's the player team). However, how can I keep the teamid reference and undefined values in there (so indices remain "parallel")? And I'm assuming there's a cleaner & better way anyway (on the array-of-objects level itself), that'd allow me to sort it straight into the result string.

Tried searching as well, but didn't find anything for the double-sort-while-considering-specific-key/value-pairs problem.

Would be glad for some pointers.

Upvotes: 2

Views: 1841

Answers (3)

Hayden Braxton
Hayden Braxton

Reputation: 1161

I think lodash would serve you well here.

var signincounts = [{
   status: 1,
   count:  3,
   teamid: 4
 },{
   status: 0,
   count:  1,
   teamid: 2
 },{
   status: 0,
   count:  2,
   teamid: 4
}],

teamcount = {
    2: 5,
    4: 6
};

var result = _.chain(signincounts)
              .groupBy('teamid')
              .mapValues(mapTeams).value();

console.log(result);

// helper function for mapping from grouped data
function mapTeams(teamData, teamid) {
  var team = {};
  team.undecided = teamcount[teamid];

  var signed_in = _.find(teamData, {status: 1}), // gets the object that tells us how many are signed in
      signed_out = _.find(teamData, {status: 0}); // gets the object that tells us how many are signed out

  team.signed_in = (signed_in && signed_in.count) ? signed_in.count : 0; // guard against undefined
  team.signed_out = (signed_out && signed_out.count) ? signed_out.count : 0; // guard against undefined
  team.undecided -= (team.signed_in + team.signed_out);

  return team;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>

So here's what's happening. First we call _.chain(signincounts). That means that we are going to take our signincounts and pass it through a pipeline of functions that will each modify the result from the previous function. So after signaling that we are going to chain, we group all the objects in the array by their teamid. We get the following object:

{ '2': [ { status: 0, count: 1, teamid: 2 } ],
  '4':
   [ { status: 1, count: 3, teamid: 4 },
     { status: 0, count: 2, teamid: 4 } ] }

Because we're chaining, this is what's passed to mapValues. mapValues creates an new object with the same keys as the object passed in, but with different values, according to the callback function passed in. So our result will be an object that has the teamids as keys, which is what I believe you want. The helper function defines what will be mapped to each teamid. This helper function is executed for each key-value pair.

Inside the helper function, we create an object that will be our new teamid mapping. So for each teamid, we are going to return an object that looks like { signed_in: <Number>, signed_out: <Number>, undecided: <Number> }. We set undecided as the total number on the team. Then we find the object in the array from the previous result that tells us how much are attending for that team, and then we find out how many are not attending. We assign signed_in and signed_out fields accordingly. Then we deduct from our total amount how many are signed_in and signed_out to get our undecided count.

Upvotes: 1

georg
georg

Reputation: 215029

Two steps:

  • first, iterate teamcount and create an object teamid => [0, 0, count]. In other words, assume all team members undecided

  • then, iterate signincounts and do two things: add c.count to a respective slot of result[teamid] and subtract c.count from result[teamid][undecided]

This gives you the stats you want, final sorting and output are left to the reader.

Code:

data = {
    signincounts: [{
        status: 1,
        count: 3,
        teamid: 4
    }, {
        status: 0,
        count: 1,
        teamid: 2
    }, {
        status: 0,
        count: 2,
        teamid: 4
    }]
    ,
    teamcount: {
        2: 5,
        4: 6
    }
};

res = {}

Object.keys(data.teamcount).forEach(teamid => 
    res[teamid] = [0, 0, data.teamcount[teamid]]);

data.signincounts.forEach(c => {
    res[c.teamid][c.status] = c.count;
    res[c.teamid][2] -= c.count;
});

console.log(res)

Upvotes: 1

salezica
salezica

Reputation: 77089

Ok then! Let's do this in 2 phases:

Filtering phase

First, we must eliminate entries that should not be printed. You mentioned counts that equal 0:

data = data.filter(function(entry) { entry.count > 0 })

Sorting phase

Now that all the remaining entries in data are to be printed, we can sort them into the desired order. You mentioned two constraints:

  • Lower team IDs should appear first
  • A particular team ID should always appear at the top

The .sort() method of Javascript Array objects is called with a comparison function with this signature:

function compare(a, b) returns Number

The returned Number is interpreted as follows:

  • If compare returns < 0, a goes before b
  • If compare returns > 0, b goes before a
  • If compare returns 0, it doesn't matter (usually respects previous order)

So, let's write a compare function that can sort your data:

function compare(a, b) {
  // The special USER_TEAM_ID goes before any other element:
  if (a.teamId === USER_TEAM_ID) return -1

  // For any other case:
  return a.teamId - b.teamId // invert these to reverse the sort
}

Now you can call:

data.sort(compare)

Upvotes: 2

Related Questions