Ranking an array of objects

A fairly common thing is ranking an array of objects. A sort, followed by a map of the indexes is a simple way to do it. 

Something like this

array.sort (function(a,b) {
    return a.value - b.value;
})
.forEach (function(d,i) {
    d.rank = i;
});

However - when you have two  values the same, you dont want the rank to be sequential. for example [10,11,20,20,24]  ranks should be [0,1,2,2,4], whereas the above would give [0,1,2,3,4].

Here's a snippet in the useful library that's been generalized to produce proper rankings. 

And here's how you would use it.

function testRank () {
  Logger.log(
    cUseful.arrayRank( [{value:10},{value:20},{value:3},{value:40},{value:20}],
      function (a,b) {
        return a.value - b.value;
      },
      function (d,r,a) {
        d.rank = r; 
        d.rankRatio = (a.length-r)/a.length;
        return d;
      },
      function (d) {
        return d.rank;
      }) 
    );
}

and the result

[{"value":3,"rank":0,"rankRatio":1},{"value":10,"rank":1,"rankRatio":0.8},{"value":20,"rank":2,"rankRatio":0.6},{"value":20,"rank":2,"rankRatio":0.6},{"value":40,"rank":4,"rankRatio":0.2}]

Defaults


In some cases, the compare/getters/setters are fairly standard - so there are defaults as follows

  // default compare/getter/setters
  funcCompare = funcCompare ||   function (a,b) {
        return a.value - b.value;
      };
  funcStoreRank = funcStoreRank || function (d,r,a) {
        d.rank = r; 
        return d;
      };
  funcGetRank = funcGetRank || function (d) {
        return d.rank;
      } ;

So if your objects are in the right shape, you can just do this

    cUseful.arrayRank( [{value:10},{value:20},{value:3},{value:40},{value:20}]) 

and get this

    [{"value":3,"rank":0},{"value":10,"rank":1},{"value":20,"rank":2},{"value":20,"rank":2},{"value":40,"rank":4}]

Keeping the original order

In the examples so far, we'll get the original array back, sorted and ranked. However you may want to keep the original order and just attach ranks. 

using the default setters/getters 

    cUseful.arrayRank( [{value:10},{value:20},{value:3},{value:40},{value:20}] ,null,null,null,true) 

gives this, retaining the original order

    [{"value":10,"_xlOrder":0,"rank":1},{"value":20,"_xlOrder":1,"rank":2},{"value":3,"_xlOrder":2,"rank":0},{"value":40,"_xlOrder":3,"rank":4},{"value":20,"_xlOrder":4,"rank":2}]


Ranking primitives

We've dealt with where we have an array of objects, and the the object gets populated with the rank. Lets say you have an array of primitives, and you want to rank, creating a new array of ranks, and retaining the original order. Here's an example using the default getters/setters

    cUseful.arrayRank(      
      [10,20,3,40,20].map(function(d) {
        return {value:d};
      }),null,null,null,true)
      .map(function(d) {
        return d.rank;
      }) 

and the result - the ranks in the order of the input data
    [1,2,0,4,2]


If you have some scripts you want add to this library, let me know on my forum.

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.




See Google Apps Scripts snippets for more like this and Useful stuff library for more from this library.
For help and more information join our forumfollow the blogfollow me on twitter

Comments