Introduction to the Reactive Extensions for JavaScript – Aggregation Part 1

So far we’ve come a long way in this series on the Reactive Extensions for JavaScript, starting with some of the basics, and then going deeper with composition with such things as the Bing Maps and Twitter Mashup, Drag and Drop, asynchronous programming among others.  In the previous post, we talked about separating our concerns between the base functionality, DOM events and third-party library integration.  This time, let’s look at another separation which comes in the form of aggregates.

Before we get started, let’s get caught up to where we are today:

Aggregation Operators

In the initial release of the Reactive Extensions for JavaScript, you may have noticed there was a lack aggregation operators.  By aggregation operators I mean those which require us to iterate over the collection with some sort of accumulated value to produce a final answer.  The Reactive Extensions for JavaScript have included a number of operators which we’ll go through one by one including:

  • Aggregate
  • Any
  • All
  • Contains
  • Count
  • Sum
  • Average
  • Final
  • Min/MinBy
  • Max/MaxBy

To use these, simply add a reference to the Rx.aggregates.js library like the following:

<script src="rx.aggregates.js" type="text/javascript"></script>

Now to cover some of the functions.  Let’s start off with the Aggregate function.

Aggregate

The aggregate function gives us the ability to apply an accumulator function over our observable sequence and the seed acts as the initial accumulator value and then yields to us a single value.  For those coming from the functional programming world, one could think of this as a left fold.  For a quick refresher on what a fold is, take a look at Bart de Smet’s post on the matter.

This function should be familiar to you who already use LINQ and the semantics are exactly the same.  Except, there is one large difference in that all of these aggregation functions don’t return just a value as they do in LINQ to objects, but instead an IObservable<someObject>, so in order for us to get at the value, we must subscribe to it.

Below is the signature of the Aggregate function.

Aggregate = function(
    seed,             // Initial value
    accumulator);     // Function to be invoked on each element

We can then use this function, for example, to count the number of vowels in a given piece of text.  We have an initial seed of an object with properties for each vowel, a e i o u.  Then we have an accumulator function when then compares our letters and increments the accumulator properly by incrementing the right property should there be a match on a given vowel.

Array.prototype.toObservable = function() {
    return Rx.Observable.FromArray(this);
}

function countVowel(state, letter) {
    var l = letter.toLowerCase();
    if(l === "a") return $.extend(state, { a : state.a + 1 });
    if(l === "e") return $.extend(state, { e : state.e + 1 });
    if(l === "i") return $.extend(state, { i : state.i + 1 });
    if(l === "o") return $.extend(state, { o : state.o + 1 });
    if(l === "u") return $.extend(state, { u : state.u + 1 });

    return state;
}

var initial = {
    a : 0,
    e : 0,
    i : 0,
    o : 0,
    u : 0 };

var sentence = "Are you ready to hack some JavaScript";

sentence.split("").toObservable()
    .Aggregate(initial, countVowel)
    .Subscribe(function(counted) {
        $("#result").html("Number of E's: " + counted.e);
    });

The result of our labor gives us that it found three E’s in the sentence.

Number of E's: 3

We also have another version of the Aggregate function which takes no seed at all and instead just an accumulator. 

Aggregate = function(
    accumulator);      // Function to be invoked on each element

In this example, we’ll take a sentence and reverse it by splitting it by whitespace and then inside of our accumulator function, we put the word in front of the rest of the sentence as we go along.

var sentence = "Are you ready to hack some JavaScript";

sentence.split(" ").toObservable()
    .Aggregate1(function(acc, word) {
        return word + " " + acc; })
    .Subscribe(function(reversed) {
        $("#result").html(reversed);
    });

This should then render the following:

JavaScript some hack to ready you Are 

Now that we’re familiar with Aggregate, let’s move on to determining whether we satisfy a given condition

Any

The next LINQ operator that is in the Aggregates library is the Any function.  This function has two overloads, the first determines whether there are any items in the collection.  This function takes no arguments and looks like the following.

Any = function();

We can then use this to then determine if we have any items in our collection.

var items = Rx.Observable.FromArray([1,2,3]);

items.Any().Subscribe(function(any) {
    alert(any); // alerts true
});

Just as well, we could use the overload of the Any operator to determine whether any item in the observable sequence match a given predicate.  The predicate function takes in the item from list and then returns a boolean to determine whether there is a match. 

Any = function(
    predicate); // predicate to determine if any satisfies

In this example, we’ll determine whether any of the strings inside of an array have the letters “ie” in them.

var words = Rx.Observable.FromArray(
    [ "belief", "height", "yield", "cashier" ]);

words
    .Any(function(word) { return word.indexOf("ie") != -1; })
    .Subscribe(function(any) {
        alert(any); // yields true
    });

All

The last one we’re going to cover today is the All function.  This function determines whether all elements of an observable sequence satisfy a given predicate. 

All = function(
    predicate); // predicate to determine if all satisfy

To show you quickly how it works, I’ll take the example from above and rewrite it to use the All function instead of the Any and this time because not all the words have the “ie” letter combination, I should have false returned to me when I subscribe.

var words = Rx.Observable.FromArray(
    [ "belief", "height", "yield", "cashier" ]);

words
    .All(function(word) { return word.indexOf("ie") != -1; })
    .Subscribe(function(any) {
        alert(any); // yields false
    });
  

Conclusion

I think that’s enough for now, and we have a bit to cover including:

  • Contains
  • Count
  • Sum
  • Average
  • Final
  • Max
  • Min

As you can see, with the Aggregates library addition of the Reactive Extensions for JavaScript, we have yet another tool in our belt for handling our data.  That’s just one of the many things we can do with it that I’ll hopefully cover more in the near future.  So, download it, and give the team feedback!

What can I say?  I love JavaScript and very much looking forward to the upcoming JSConf 2010 here in Washington, DC where the Reactive Extensions for JavaScript will be shown in its full glory with Jeffrey Van Gogh (who you can now follow on Twitter).  For too many times, we’ve looked for the abstractions over the natural language of the web (HTML, CSS and JavaScript) and created monstrosities instead of embracing the web for what it is.  With libraries such as jQuery and indeed the Reactive Extensions for JavaScript gives us better tools for dealing with the troubled child that is DOM manipulation and especially events.

No Comments