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;
    });

Fancy For Loops – Part 1

Using libraries like underscore (or Lo-Dash) for traversing and transforming arrays or objects can be a great time saver (even though, it seems like they did it wrong). However, when helping developers with anything new, I have found the less “black box” libraries you throw at someone, the better they are able to learn what’s really going on. Performance and style discussions aside, I’d rather see a beginner JavaScript developer write tons of for or while loops before finding out what library X, Y, or Z helps them do. Not using a library is also a great learning experience about writing your own algorithms, or using polyfills because IE8 doesn’t support map() or filter().

Most of the array prototype methods can be implemented with a simple for or while loop, generally with better performance, but possibly not as elegantly or with the same level of reusability. This post is not intended to be a guide on when to use and when not to use these methods (or the underscore/Lo-Dash equivalents), but rather help understand the concepts.

Disclaimer: I am by no means a JavaScript performance or functional programming expert, but I play one at work.

Array.prototype.forEach() or “A for loop, but all functional and stuff”

Consider the following example that loops over each item in an array and logs the value to the debug console.

var values = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < values.length; i++){
    console.log('value at index ' + i + ': ' + values[i]);
}

This is pretty much the “Hello World” of for loops in JavaScript. We can write the same thing using the array prototype method .forEach(). According to the MDN, arrays have a forEach() method that accepts a callback function as the first argument, and thisArg as an optional second. We’ll ignore thisArg for this particular post.

As an aside, if you aren’t using the MDN while writing web apps, you are doing it wrong (or you have it all memorized, in which case you should be working for NASA or maybe as one of those waiters that never writes anything down to be all impressive and stuff).

 

The callback function will be executed once per array element, passing in the array element as the first argument, the index of the element as the second, and the array itself as the third. Using the forEach() method, we can produce the same output as the above code this way:

var values = [1,2,3,4,5,6,7,8,9,10];
values.forEach(function(value, idx, arr){
    console.log('value at index ' + idx + ': ' + value);
});

Array.prototype.map() or “Make all Items in an Array into Something Else”

Consider the following example that uses a for loop to produce an array of upper case letters from an array of lower case letters (without modifying the original array or its contents).

var letters = ['a','b','c','d','e','f'];
var upperLetters = new Array(letters.length);

for (var i = 0; i < items.length; i++){
    upperLetters.push(items[i].toUpperCase());
}

// upperLetters now contains:
// ['A','B','C','D','E','F'];

We can write the same thing using map(). According to the MDN, the map() method on JavaScript arrays accepts two arguments, callback, and optionally thisArg. The first argument, callback will be executed over each item in the array. The return value of map() will be an array containing all the return values from callback. Underscore map does the same thing, but, it works reliably in browsers that do not implement native JavaScript map. The following code produces the same output as the above code.

var letters = ['a','b','c','d','e','f'];

var upperLetters = letters.map(function(value){
    return value.toUpperCase();
});

// upperLetters now contains:
// ['A','B','C','D','E','F'];

So why would you use the second example over the first, especially considering it is slower? The answer, of course is, “it depends”.  Using map() with a named function or function variable can be really useful for writing more concise code. Consider the following example code that creates copies of three arrays, while removing leading and trailing whitespace from the array elements:

var arr1 = [' bob', 'sally  ', '  tod', ' phil  '];
var arr2 = [' teresa ', ' julie  ', '  sandy  ', ' ron  '];
var arr3 = [' jason', ' jill  ', ' jane  ', ' sam  '];

var arr1trimmed = arr1.map(String.prototype.trim);
// ['bob', 'sally', 'tod', 'phil'] var arr2trimmed = arr2.map(String.prototype.trim);
// ['teresa', 'julie', 'sandy', 'ron'] var arr3trimmed = arr3.map(String.prototype.trim);
// ['jason', 'jill', 'jane', 'sam'];

This code reuses the trim() function, rather than passing an anonymous function into forEach() like we did earlier.  It should be noted that this simplistic trim() function example will throw an exception if any of the values in the array are undefined, null, or not a string.

Hopefully this helps un-black-box things a bit. In Part 2, we’ll look at Array.prototype.filter(), Array.prototype.reduce(), and look more at what the underscore and Lo-Dash libraries provide relating to for-loops.