It is simple to find every number in the given Array
function isNumber(x) {
return typeof x === 'number';
}
var list = ['a', 'b', 1, 2, 3, 'c'];
console.log(list.filter(isNumber)); // [1, 2, 3]
The .filter
iterator returns every value that passes the given predicate.
What if we wanted to know what the second number is in an array? We could filter
and access element with index 1.
function nth(k, predicate, list) {
return list.filter(isNumber)[k];
}
var secondNumber = nth.bind(null, 1, isNumber);
console.log(secondNumber(list)); // 2
This approach destroys the information about the position of the second number in the original list. What if I wanted to print first item after the second number? By filtering the list first, we lost the original positions.
Counting predicates
Instead of making a new array right away, we can write a function that keeps count how many times a predicate returned true.
function nth(n, predicate) {
var k = 0;
return function () {
var result = predicate.apply(null, arguments);
if (result) {
k += 1;
}
return k === n;
};
}
Instead of using Array.prototype.filter
we can now use Array.prototype.map
because we are interested
where in the original list is the second number, not its value yet.
var list = ['a', 'b', 1, 2, 3, 'c'];
var secondNumber = nth(2, isNumber);
console.log(list.map(secondNumber));
// [ false, false, false, true, false, false ]
Function nth
mixes two aspects togehter: counting and predicate execution.
I showed an example of splitting such function into smaller focused functions. Let us rewrite nth
in terms of two simpler functions. First, the counter.
// keeps number of executions
function counter(n) {
console.assert(n > 0, 'invalid n to count to ' + n);
var k = 0;
return function () {
k += 1;
return k === n;
}
}
// example
var countTo2 = counter(2);
console.assert(!countTo2(), 'first');
console.assert(countTo2(), 'second');
console.assert(!countTo2(), 'after');
The nth
function becomes a simple composition
function nth(n, predicate) {
var counterToN = counter(n);
return function () {
var result = predicate.apply(null, arguments);
return result ? counterToN() : false;
};
}
var secondNumber = nth(2, isNumber);
var list = ['a', 'b', 1, 2, 3, 'c'];
console.log(list.map(secondNumber));
// [ false, false, false, true, false, false ]
Using counter
we can write other predicates, for example for finding items n positions after
another predicate returns true.
function after(n, predicate) {
var countToN = counter(n);
var seenPositive;
return function () {
if (seenPositive) {
return countToN();
}
seenPositive = predicate.apply(null, arguments);
return false;
};
}
A couple of examples
var list = ['a', 'b', 1, 2, 3, 'c'];
var firstAfter2ndNumber = after(1, nth(2, isNumber));
console.log(list.map(firstAfter2ndNumber));
[ false, false, false, false, true, false ]
var secondAfter2ndNumber = after(2, nth(2, isNumber));
console.log(list.map(secondAfter2ndNumber));
[ false, false, false, false, false, true ]
Note that our predicate with built-in counter does NOT reset itself. Thus it can only be used once
var list = ['a', 'b', 1, 2, 3, 'c'];
var firstAfter2ndNumber = after(1, nth(2, isNumber));
console.log(list.map(firstAfter2ndNumber));
[ false, false, false, false, true, false ]
console.log(list.map(firstAfter2ndNumber));
[ false, false, false, false, false, false ]
Printing value
Once we have boolean list, we can find the actual value. Just take the first element where the predicate returns true
1 | function toValue(list, predicate) { |
I probably will use this approach for flexible optional argument parsing.