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.