Mapping arrays

Most built in methods of the Array Abstract Prototype can be re-created by looping through an array. In this blog we will construct a brand new array using different patterns. We will look at a for each loop, for of, forEach, map and @@iterable (generators).

Naive solution: Basic For Each Loop

Every JavaScripter knows how to a do a for each loop, but here goes for completeness.

var i_int = 0,
    ARR = ["red", "green", "yellow", "hotpink"],
    new_arr = [];

for(i_int = 0; i_int < ARR.length; i_int += 1){
  new_arr[i_int] = ARR[i_int];
};

//  new_arr is now ["red", "green", "yellow", "hotpink"]
easy peasy
build Dev tools time build Play around with this in dev tools MAPPING_ARRAYS.runBasicForEachLoop(1);

bug_report Try passing in the number 2 and note the key values of the array and the length. This is the infamous "sparse array". Try passing the number 6 and study the difference.

Built in method solution: Basic For Each Loop

We have the same thing as a built in method. Array.prototype.forEach.

var ARR = ["red", "green", "yellow", "hotpink"],
  new_arr = [];

var will_be_undefined = ARR.forEach(function(arr_item, i_int){
    new_arr[i_int] = "bright" + " " + arr_item;
    return "too much coffee"; //note this has no effect
});

//  new_arr is now ["bright red", "bright green", "bright yellow", "bright hotpink"]
We don't want to use inline anonymous functions right?

This makes baby Jesus cry. So we need to refactor.
var ARR = ["red", "green", "yellow", "hotpink"],
  new_arr = [];

function makeABrighterHappierArray(arr_item, i_int){
    new_arr[i_int] = "bright" + " " + ARR[i_int];
    return "too much coffee"; //note this has no effect
}

var will_be_undefined_yet_again = ARR.forEach(makeABrighterHappierArray);

//  new_arr is now ["bright red", "bright green", "bright yellow", "bright hotpink"]

I find myself privately naming my iteration functions in such away that to avoid direct invocation. They need to explain what they do and what they are. E.g _iteratationFunctionForMakingABrightArrayFromADullArray (check the source). Then I don't accidentally call this directly expecting it to build an array for me.

build Dev tools time build Now try MAPPING_ARRAYS.runBuiltInMethodForEachLoop();

Also try copying those snippets in there too.

bug_report Notice the built in method does not @return the last item that was added like in number 1 if you were to go back to debug and check MAPPING_ARRAYS.runBasicForEachLoop(1).

Mapping properly

There is Array.prototype.Map which allows you to get a return value this time! WOOT! That return value is a newly constructed array built from consecutive return values from the iteration function.

Here is a Map function writen in such away that won't making baby Jesus cry. As it is a map function and not a forEach that returns the array, I choose to name them with a mappingFunction prefix.

var ARR = ["red", "green", "yellow", "hotpink"],
  new_arr = [];

function _mappingFunctionThatMakesAMuchBrighterAndHappierArray(arr_item, i_int){
    // we don't do this new_arr[i_int] = "really bright" + " " + ARR[i_int];
    // we just return the string to be pushed onto the newly constructed array
    return "really bright" + " " + ARR[i_int];
}

new_arr = ARR.map(_mappingFunctionThatMakesAMuchBrighterAndHappierArray);

//  new_arr is now ["really bright red", "really bright green", "really bright yellow", "really bright hotpink"]

Generators

Being able to yeild inside a loop process is very helpful. Insert imagination here!

With generators we have fined grained control during a map / iteration?
build Dev tools time build See it working first. Run MAPPING_ARRAYS.runUsingIterator(); Then wait a few seconds.

announcement Did you notice we are essentially controlling the pace of the loop with a timeout?

Alhought this code below is a lot more complex, you do get a lot of power here. 1) Create a custom generator. 2) Spawn a cursor and keep invoking next() until it is done! 3) Do async stuff in your loop. Ajax etc, you can even adjust ARR to get new items processed*. Think streams!


var ARR = ["red", "green", "yellow", "hotpink"],
    cursor = null,
    new_arr = [],
    iterator_next_item = null,
    __exhaustCursor = function(){
        iterator_next_item = cursor.next();
        if(iterator_next_item.done === true){
            console.info("exhausted the cursor, nothing left to work on");
            return;
        }
        new_arr.push(iterator_next_item.value);
        console.log(new_arr);
        
        __exhaustCursor();
        //  try replace ^^ with setTimeout(__exhaustCursor, 3*1000);
        
    },
    __generatorFunction = function*(){
        var i_int = 0;
        while(i_int < this.length){
            yield this[i_int++];
        }
    };
ARR[Symbol.iterator] = __generatorFunction;
cursor = ARR[Symbol.iterator]();
__exhaustCursor();

announcement Try adjusting the loop with a timeout instead of just __exhaustCursor() i.e setTimeout(__exhaustCursor, 3*1000);

Pretty cool huh! Not sure there are many 'real world' uses ...but its still cool that you can do this. Now you can essentially 'sleep' in JavaScript!

Even if you never use generators (you probably won't btw) at least get to grips with Map, it is very handy.

*FINAL TIP: Never adjust the non-tail of an array while processing it. Note the CONSTANT ARR I use.

The bast way to count ants is to take a picture of them first and then count them in the picture. Always SNAPSHOT your arrays to constants and build (map / transform) them into new ones.

My comments engine is coming...one day. For now, drop me an email if you have stuff to get off your chest