[ /js/lambdaii ]

More neat things you can do with Lambdas

Extra arguments in Map

Since my last article on the topic, I've learned a little bit more about the array method map.

I had only been using the first arguement being passed to the anonymous functions I was mapping. Apparently I've been missing out, as the method passes two more arguments to the procedure.

Let's throw together a trivial example:

["a","b","c","d","e","f"].map(function(x,y,z){
  console.log("%s\t:: %s\t:: %s",x,y,z);
});

If you're at all familiar with map, you should be able to tell that the first column of values we are printing will contain the individual letters within the array.

The value being passed as 'y' is the index of that variable, beginning from 0.

Finally, 'z' is the entire array, in case you want to reflect on the rest of the data without having defined it in the parent scope.

Throwing away values

You can always declare those argument names in your function and throw them away, if you don't need them all.

For example, here's a sloppy solution to the canonical 'fizzbuzz' challenge, using the index instead of the value. This technique isn't necessary for the usual version of the problem, but if someone wants to challenge you and throw in some twists, this is a great way to capture all the variables in play.

console.log(
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
  .map(function(x,y){
    return ((y%3==0)?"fizz":"")+((y%5==0)?"buzz":"")||y;
  }));

Now, what about the array values? We can use the index 'y' in expressions as a subscript of z. Keep in mind that this makes it very likely that you will exceed the bounds of your array at some point. Use the logical or operator '||' to provide a fallback value.

[0,1,2,3,4,5,6,7,8,9]
  .map(function(x,y,z){
    console.log("%s %s %s"
      ,z[y-1]||" "
      ,z[y]
      ,z[y+1]||" "
    );
  });

NIFTY!

More of the same in Filter

Filter captures the same variables

var x=["a","b","c","d","e","f"].filter(function(x,y,z){
  // print the value, context, and container of each element
  console.log("%s\t:: %s\t:: %s",x,y,z);
  // only return those elements with even indices
  return y%2==0;
});
console.log(x);

Since we capture indices and the input array with the second and third arguments using array.filter, we can write complex predicates

Capturing indices with reduce

Reduce is a little more complicated, it expects at least a binary function (unless you're going to throw the second variable away, in which case you should just map instead).

In the function below I used slightly more descriptive variable names:

  • 'a' is the first operand of a binary function
  • 'b' is the second
  • 'c' is the index (which iteration we are on)
  • 'L' is the entire array.
[0,1,2,3,4,5].reduce(function(a,b,i,L){
  console.log("%s\t\t:: %s\t:: %s\t:: %s",a,b,i,L);
});

Converting binary strings to integer values with reduce

What can we do with this? How about we convert a string of zeroes and ones to its integer representation? There's a little preamble first, as we need to turn our string into an array and reverse it.

Split produces the array, reverse does exactly what it should, and reduce operates on those values.

We need to use parseInt on a, because it will otherwise be treated like a string. You can think of 'a' as the accumulator. In this case, it doesn't need anything funny to start off, because its index is 0, meaning its value is exactly what it is already (a zero or one). From there, we use the bitwise left shift and the index to turn each value into its binary value (this is like Math.pow(b,i)). We add each successive value together, summing our bit's values as we go. Just be careful that you don't give it too large of an array, unless you're using some kind of bignum extension to javascript (I'll be posting about that soon).

console.log(
"10101"
  .split("")
  .reverse()
  .reduce(function(a,b,i){
    return parseInt(a)+(b<<i);
  })
);

As arguments with string methods

This is one of my favourite tricks. I used it a fair bit in my article about parsing CSS. The W3Schools article on String methods doesn't tell you that it's possible to pass a function as an argument.

Suppose you want to count the number of lowercase in a string:

var count=0; // instantiate a shared variable

// our source text
var S="This is a generic string of characters. It doesn't really matter what it is. Pay no attention to this string. It's just a placeholder I'm going to use to demonstrate advanced usage of a Javascript String method.";

// we can use the String.replace method to iterate over the string
// this regular expression matches lowercase characters using a character class
// the flag 'g' after the regex means 'global'.
// without it, the regex only finds the first matching instance
S.replace(/[a-z]/g,function(){ // replace doesn't modify the original string
  count++; // increment the counter
});

console.log(count);

What if we want to transform the text in some advanced way, like capitalizing the first letter of every word?

// our source text
var S="this is the text i want to capitalize.";

// our modified text
// the regex identifies whole words
var C=S.replace(/\w+/g,function(word){
  // replace removes the matched substring automatically
  // capture it with your function's first argument
  // and return its replacement
  return word.slice(0,1).toUpperCase() // capitalize the first character
    +word.slice(1); // append the rest as it is
});

// display the results
console.log(C);

String.replace gives you access to other variables in the function, following the same form as map and filter.

By now, you should see the ubiquity of String.replace as equivalent to Array.map, but for strings. Here, I use it again just for its side effects, and discard the results.

"this is a string"
  .replace(/./g,function(char,index,string){
    console.log("%s %s %s",x,y,z);
  });