Justin R. Buchanan

on Software Development, Systems Administration, Networking, and Random other Stuff

Fancy For Loops – Part 2

So this post is somewhat more abbreviated than originally intended, because I finished it out 2 years after it was started. Between the last time I posted something on my blog, I’ve had another kid, and and built a house, so my blog got neglected. This post was sitting in my drafts since 2014. It’s not my finest work or anything, but I figured I might as well get it posted.

Array.prototype.filter() or “Give me all the items where some condition is truthy”

Consider the following example that uses a for loop to produce an array of numbers that are all evenly divisible by 2 from an input list of numbers (without modifying the original array or its contents).

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = [];
for (var i = 0; i < numbers.length; i++){
    if (numbers[i] % 2 === 0){
        evenNumbers.push(numbers[i]);
    }
}

// evenNumbers now contains:
// [2, 4, 6, 8, 10]

Again, since the above code is using a for loop, it is faster than the array prototype methods that accept callbacks, but may not be as concise as what it would look like if you used the filter method, as shown below:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var evenNumbers = numbers.filter(function(num){
    return num % 2 === 0;
});

The .filter() method again accepts two parameters, a callback function, and a thisArg. The callback function is executed once for each item in the array. The return value of .filter() is a new array containing all the items in the original array where executing callback() returned a truthy value.

When combined with map, the .filter() method vs. the for-loop can really shine when it comes to writing concise code. Consider the example below where we need to filter a set of people to only those whose age is greater than or equal to 18, and produce an array of just their age.

people.filter(function(x) { x.age >= 18 }).map(function(x) { return x.age });

Great, now we have an array of ages of people 18 or over. But what if we want to reduce that set to a single value, such as the maximum or average age?

Array.Prototype.reduce or “Reduce a set to a single value”

So what does reduce do exactly and how can it help me? Reduce takes a set of values, and reduces it to a single value. This can useful in all kinds of ways, but the simple example is the easiest to start with. Taking a list of numbers, we could compute the sum of those numbers like this:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var sum = 0;
for (var i = 0; i < numbers.length; i++){
    sum += numbers[i];
}

Using reduce, we could write the above code this way:

var sum = numbers.reduce(function(accum, cur) {
    return accum + cur;
}, 0);

The reduce method accepts two parameters, the first being a callback function, just like map & filter. The arguments to the callback function are as follows:

  • accumulator: the current value of the accumulator
  • current value: the value at the current index of the source array.
  • current value index: the index of the current array element
  • array: the array on which reduce() was called.

The second parameter to reduce is the initial value of the accumulator. That is, the value that will be passed as the accumulator (first parameter) on the first invocation of the callback function. In the above example, I specify 0 as the initial value, because I want the sum.

Using this knowledge we can take our list of ages of people we produced above, and incorporate reduce to compute the average age in our list.

var averageAge = people
    .filter(function(x) { x.age >= 18 })
    .map(function(x) { return x.age });
    .reduce(function(acc, cur, index, arr) { 
        var retval = acc + cur;
        // if we are looking at the last value, return the avg instead of the sum
        if (index === arr.length-1) {
            return retval / arr.length;    
        }
        return retval;
    });

While the above code is filled with potential performance issues, the clarity it provides probably out weights the performance overhead in most cases. It is worth noting, that beyond the performance hit of using the callback functions above, there is a second performance issue here that may not be entirely obvious. Namely, we are iterating over an array three times in the above code, instead of what could be once. Libraries like Underscore.js and Lodash can mitigate this using chaining, which *can* reduce many algorithms like the above into a single loop that performs better.

It’s worth noting that since this post was originally drafted, arrow functions are widely available in the native browser, Babel or Typescript, and would probably be a better way to write some of the above examples. Using arrow functions, the last code sample would look like this:

var averageAge = people
    .filter(x => x.age >= 18)
    .map(x => x.age);
    .reduce((acc, cur, index, arr) => { 
        var retval = acc + cur;
        // if we are looking at the last value, return the avg instead of the sum
        if (index === arr.length-1) {
            return retval / arr.length;    
        }
        return retval;
    });

Add comment

Loading