Tuesday, September 25, 2007

Javascript composite comparator

Since it's available on every modern web browser, Javascript captures the title as the world's most deployed programming language. But while its availability is almost universal, its reputation is less than stellar. Douglas Crockford wrote a great piece on Javascript being the world's most misunderstood programming language. He lists several reasons such as design errors, bad early implementations, and even naming the language Javascript which implied it being like Java and it being no more than a simple scripting language.

I'll admit that my initial opinion was quite down as well. I used it strictly as a C clone and cringed anytime I had to debug a script. But now that I have had more exposure to and experience with the language features such as closures and first class functions, I've definitely reversed my opinion. It's so useful to perform client side processing with such a full fledged language at my disposal.

This post wouldn't do Javascript justice if it didn't have some accompanying code, so I'll revisit my Java generics and varargs post and reimplement the composite comparator using Javascript.

To recap, the composite comparator (aka cascading comparator or chained comparator) is a comparator made up of several other comparators. My Java 1.5 implementation uses the varargs language feature to yield a simple to use api that accepts a variable amount of comparators.

Since javascript is dynamically typed and supports variable length arguments, the implementation looks pretty similar.

This is our player object:

function player(name, hr, rbi, sb) {
    this.name=name;
    this.hr=parseInt(hr);
    this.rbi=parseInt(rbi);
    this.sb=parseInt(sb);
}

Let's create some players and populate a players array:
var a = new player('Arod',50,150,1);
var b = new player('Reyes',50,150,2);
var c = new player('Bonds',50,150,3);

var players = new Array(a,b,c);

Next, we need to create some comparators:
function sortRbi(a, b) {
   return a.rbi - b.rbi;
}

function sortHr(a,b) {
   return a.hr-b.hr;
}

function sortSb(a,b) {
   return a.sb-b.sb;
}

Here's the composite comparator:
function compositeSort() {
  var args = Array.prototype.slice.call(arguments);
     return function(a,b) {    
     for (var i=0;i<args.length;i++) {
        var result = args[i](a,b);
        if (result!=0) {
           return result;
        }
     }
     return 0;        
    }
}

Here's a negate comparator:
function negate(func) {
    return function(a,b) {
        var i=func(a,b);
        if (i==0)
          return i;
        else
          return -i;
    }
}


This comparator will sort by hr ascending, rbi ascending, and sb descending.
var comp = compositeSort(sortHr,sortRbi,negate(sortSb));

And now we pass the newly created comparator to the built in sort method in arrays.
players.sort(comp)

No comments: