Introduction to the Reactive Extensions for JavaScript – Aggregation Part 2

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 follow onto our last post which started talking about the Aggregates library of the Reactive Extensions for JavaScript

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.  We’ll start with the Contains function, picking things up where we left off last time.

Contains

The first method we’ll cover is the Contains function, which determines whether a given observable sequence contains the item, and optionally with a comparer which allows us to compare values for equality.

Rx.Observable.Contains = function(
    value,     // Value to locate in the sequence
    comparer); // Optional comparer to compare values

If you fail to include a comparer, the default comparer used looks like the following:

var defaultComparer = function(a, b) { 
    return a === b 
};

In this example, I’ll have an observable collection of employees and I wish to check whether Bob is an employee.  In this case, I’ll provide a comparer which checks the name property on our object for equality.

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

var employees = [{ name : "Bob", age : 30 },
                 { name : "Billy", age : 32 }]

employees.toObservable()
    .Contains({ name : "Bob" }, 
        function(a, b) { return a.name === b.name; })
    .Subscribe(function(contains) {
        $("#result").html("Contains Bob?: " + contains);
    });

This of course produces that it can in fact find an employee named Bob in the collection.  Moving on to the next function, Count.

Count

The count function in the Reactive Extensions for JavaScript simply returns the number of items in our observable collection.  The signature of this method is as follows:

Rx.Observable.Count = function();

As an example, we can take an array of numbers, get the distinct values, count them and display the results.

var factors = [ 2, 2, 3, 5, 5 ];

factors
    .toObservable()
    .DistinctUntilChanged()
    .Count()
    .Subscribe(function(count) {
        $("#result").html("Distinct factors: "+count);
    });

This then gives us the result of 3 distinct factors as shown below.

Distinct factors: 3

Next up, we’ll talk about the Sum function.

Sum

This rather self-explanatory method simply computes the sum of all items in the observable collection.  As JavaScript is a dynamic language, this method supports anything that supports the + operator, typically integers and doubles, although oddly enough it does work for strings (which is not recommended).  The signature of the function is as follows:

proto.Sum = function();

In this example, we’ll simply take some floating point numbers and sum them together.

var numbers = [ 25.78, 5.72, 1.5, 3.2 ];

numbers
    .toObservable()
    .Sum()
    .Subscribe(function(summed) {
        $("#result").html(summed);
    });

And the result of our action then puts the 36.2 into the result div element.

36.2

Next, we’ll tackle the Average function.

Average

Just as you’d expect, we have the Average function which does as you expect, averages numbers.  This function takes no arguments and simply returns the average.

Rx.Observable.Average = function();

We can take our above example and instead of summing the values, this time we’ll average them.

var numbers = [ 25.78, 5.72, 1.5, 3.2 ];

numbers
    .toObservable()
    .Average()
    .Subscribe(function(average) {
        $("#result").html(average);
    });

Which then yields the result of:

9.05

Moving on, let’s talk about the Final method.

Final

The aptly named Final method gives us the ability, much like the Last method in LINQ to Objects, to retrieve the final value from the observable sequence.  The signature of this method is as follows:

Rx.Observable.Final = function();

Moving away from our same numbers example we had from above, instead we can turn our attention to Twitter and more in particular, FourSquare users.  In this example, I’ll search Twitter for 4sq in their tweet, take the results array and split each one into its own observable sequence and then get the last one and display the text.

var term = "4sq";
var url = "http://search.twitter.com/search.json";

$.getJSONAsObservable(url, { rpp : 100, q : term })
    .SelectMany(function(d) { 
        return d.data.results.toObservable(); })
    .Final()
    .Subscribe(function(final) {
        $("#result").html(final.text);
    });

Let’s move on to our last two functions, Min and Max.

Min

The next function, which should also look familiar to LINQ to objects users is the Min function.  This function, which optionally takes a key selector and min comparer, computes the minimum value in the sequence.

Rx.Observable.Min = function(
    keySelector, // Optional key selector 
    comparer);   // Optional min comparer

If a comparer is not provided, the default comparer uses the standard less than operator.

comparer = function(current, key) { 
    return key < current; 
};

To put this one to the test, let’s search Twitter again, this time, let’s look for the minimum user ID in this batch of tweets.  We’ll use the JSON jQuery integration with the Reactive Extensions for JavaScript, extract the results and then return the minimum by the from_user_id property.

$(document).ready(function() {

var term = "4sq";
var url = "http://search.twitter.com/search.json";

$.getJSONAsObservable(url, { rpp : 100, q : term })
    .SelectMany(function(d) { 
        return d.data.results.toObservable(); })
    .Min(function(result) { 
        return result.from_user_id; })
    .Subscribe(function(min) {
        $("#result").html(min.from_user_id+" "+min.text);
    });

Finally, we come to the Max function.

Max

The Max function is the inverse of the Min function and allows us to calculate the maximum value in our observable sequence.  Optionally, we can give a key selector and a max comparer.

proto.Max = function(
    keySelector, // Optional key selector
    comparer);   // Optional max comparer

If a max comparer is not provided, the default should work just fine.

comparer = function(current, key) { 
    return key > current; 
};

In our last example, let’s look for the maximum Twitter user ID that someone is responding to.  Much like above we’ll use the jQuery integration with the Reactive Extensions for JavaScript to get the JSON data, we’ll then filter by whether there is a person the user is responding to, and then get that max value of that user ID.

var term = "4sq";
var url = "http://search.twitter.com/search.json";

$.getJSONAsObservable(url, { rpp : 100, q : term })
    .SelectMany(function(d) { return d.data.results.toObservable(); })
    .Where(function(result) { return result.to_user_id != null; })
    .Max(function(result) { return result.from_user_id; })
    .Subscribe(
        function(min) {
            $("#result").html(min.from_user_id+" "+min.text); });

And there you have it, the complete set of operators handled in the aggregates library for the Reactive Extensions for JavaScript.

Conclusion

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