Neuron Workout Solutions #2
It is high time for this second installment of Neuron Workout Solutionsâ„¢, a series that answers the questions and challenges at the end of many chapters in the book. Today we’ll address the challenges closing chapter 5, which focuses on the wonderful Enumerable mixin.
Which is faster when iterating over an array, a standard integer-based for or each()? Are there circumstances where one is preferable over the other?
The native integer-based for loop will be slightly faster if it doesn’t delegate processing to a function. If it does, it becomes the equivalent of a each call: on regular arrays, this is exactly what each does, so the only penalty it carries over an inline-processing for loop is the function call cost at each turn.
Because it is so close performance-wise, you’ll probably want to use each most of the time for concision reasons, especially if all you’re doing is applying a function to each element in the array, which is a frequent case in scripts of any significant size or complexity.
If you must check that all elements in an Enumerable meet a condition that can be expressed both positively and negatively, is all() the best choice?
This is a trick question. If the test can be formulated both ways (as a positive assertion or negative assertion) with no significant performance difference between the two variants, both all() and any() will work well, considering they both short-circuit on the first element that changes their result.
So all(someTruePredicate) and any(someFalsePredicate) are equivalent from a performance standpoint, inasmuch as their respective predicates have similar performance. You should favor one over the other only when the predicate you’d use is far simpler or faster than its opposite variant.
So, we’ve got items.max(function(i) { return i.size(); }). Does it return the item with the biggest size or the biggest size itself?
This often comes as a surprise, but min and max, when given a derivative function as argument, will work exclusively on these derivatives, so they’ll return the lowest (resp. highest) derivative value. For instance:
$w('hello world this is nice').min(function(s) { return s.length; }) // => 2, not 'is'
If you’re trying to get the min/max value based on a derivative, you can resort to a custom use of, say, inject. Here’s a minimum-oriented example:
$w('hello world this is nice').inject('', function(result, word, index) { if (index == 0) return word; return result.length <= word.length ? result : word; }) // => 'is'
Use inject() to write a function that computes the product of all values of an array. Add your new function to the Array prototype.
That’s easy enough, a classic injection example:
function product(enum) { return $A(enum).inject(1, function(acc, item) { return acc * item; }); } product($R(1, 5)) // => 120
Notice how we actually make our function take anything array-convertible by wrapping the argument with a $A() call. This lets us demonstrate with an ObjectRange instance here, for example.
Now to add this to all arrays. We can either rewrite the function that way:
Array.prototype.product = function() { return this.inject(1, function(acc, item) { return acc * item; }); }
Or, if we had the function already written (as in the former example), we could re-use it like this:
Array.prototype.product = function() { return product(this); }
And if we actually know what Prototype has to offer, we’d recognize this pattern as a methodization (passing the current object as first argument) and just go:
Array.prototype.product = product.methodize(); [1, 2, 3, 4, 5].product() // => 120
Cool, isn’t it?
Do it again in a more concise way by leveraging curry() as well.
Well, instead of going:
Array.prototype.product = function() { return this.inject(1, function(acc, item) { return acc * item; }); }
We could say:
Array.prototype.product = Array.prototype.inject.curry(1, function(acc, item) { return acc * item; });
Wh00t!
Aside from strings with regular expressions and DOM elements with CSS selectors, what situations can you imagine where it would make sense to provide a match() method to become grep-compatible?
Hmmm, before offering suggestions for that one, I’d really like to see some in the comments
Ignoring the slight structural difference of the resulting elements, what’s faster: a.zip(b).zip(c) or a.zip(b, c)? Why?
The second, more concise version is faster, because it only loops once. The second, chain-calling-based version will loop once for every zip call, so it will take twice as long.
And that’s a wrap!
I’ll look forward to any questions, comments or wild guesses you may have. And stay tuned for the third installment!
3 Comments so far
Leave a reply



Great post, Christophe.
The last question reminds me of something I’ve been thinking about for quite some time.
Consider an example of getting all visible ancestors of an element:
$(someElement).ancestors().findAll(Element.visible);
This is an elegant snippet, but you could probably see a performance issue here.
There are exactly 2 times more iterations made than there could be (”while” in ancestors and “each” in findAll). In a recent patch (http://dev.rubyonrails.org/ticket/11143), I proposed to let certain methods (ancestors in this case) accept an iterator/fiter function. Ancestors “walking” could easily filter visible elements within one loop.
There are many more methods which could enforce such technique - leading to a more efficient code (though possibly in exchange of readability)
Best, kangax
Hey Kangax,
Yes, I saw the patch. However, how do you differentiate between detect/find semantics and select/findAll semantics? Is this based on the result arity of the method (e.g.
ancestorsand other set-gathering methods would goselect, whileupand its irk would godetect)?Well, it’s just another condition to check against (i.e. iterator function to return truthy value) but eliminating extra loop.
#ancestorsshould still return an array, but only of those elements that satisfy iterator function. So,element.ancestors(Element.visible);should recursively walk parent nodes but collect only those that are “visible”.
Regarding #up and family - selector (which they accept) seems to be enough most of the time. The only annoyance is that while it’s possible to get FIRST element by selector, there’s no way to get ALL elements by selector unless using something like #grep : )
Consider this: