2

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:

  • status = 1 (signed in, will attend)
  • status = 0 (signed out, will not attend)
  • count = x number of people signing in/out
  • teamid = y the team they belong to

In addition, I have the following information:

teamcount : {
    2: 5
    4: 6
}
  • teamid : total number of players

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:

  • teamids are unique, but not necessarily sequential
  • if the count for a status is zero, it is not part of the returned JSON
  • objects are not necessarily in order
  • there can anywhere from two to dozens of teams
  • The string output should be sorted by teamid, with the team of the viewer at the top (there's an additional variable, playerteamid, which can be used to simply check against)

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.

Community
  • 1
  • 1
Senshi
  • 353
  • 1
  • 3
  • 18
  • Did you check out: http://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value-in-javascript and/or http://stackoverflow.com/questions/979256/sorting-an-array-of-javascript-objects – JonSG Sep 26 '16 at 22:00

3 Answers3

2

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)
salezica
  • 74,081
  • 25
  • 105
  • 166
  • This sounds like a good, simple solution, but it doesn't address the issue where signincounts is sparse (has no object where count is zero). Those should be printed as zero, so zero has to be the assumed default value. – Senshi Sep 27 '16 at 06:28
  • I also just noticed: The sort/compare function doesn't always work: If the user's teamid is the last in the array, it apparently doesn't get checked as "a", so the if (a.teamid === USER_TEAM_ID) never is reached for that case. Can I catch that case using the regular sort()? – Senshi Sep 27 '16 at 07:55
1

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)
georg
  • 211,518
  • 52
  • 313
  • 390
  • I like this solution very much. I had a similar idea, but I didn't know about the necessary functions to best iterate over the arrays object.keys() in combination with foreach This sounds like something that will come in handy a lot in the future :) . – Senshi Sep 27 '16 at 06:30
1

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.

Hayden Braxton
  • 1,151
  • 9
  • 14
  • Thank you for this suggestion. While I'm not eager to use additional libraries (esp for this single case), it's the first time I hear about this particular one. And a lot of functions in there seem very powerful. Also thanks for the detailed explanation of what's going on. – Senshi Sep 27 '16 at 06:32