Ruby each_cons
UPDATE
Gregory Brown, who wrote the Prawn PDF library, suggests that:
“In general each_cons is useful when you need a sliding window of size n across a dataset.”
/update
Usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# Necessary >> require "enumerator" => true # Theory >> [*('a'..'g')].each_cons(2){|set| p set.join(" and ")} "a and b" "b and c" "c and d" "d and e" "e and f" "f and g" => nil # Iterates the given block for each array of consecutive <n> elements. # Practice ???????? # Documentation (1..10).each_cons(3) {|a| p a} # outputs below [1, 2, 3] [2, 3, 4] [3, 4, 5] [4, 5, 6] [5, 6, 7] [6, 7, 8] [7, 8, 9] [8, 9, 10] |
Real life
The Prawn pdf library uses each_cons like so
1 2 3 4 5 6 |
def polygon(*points) move_to points[0] (points << points[0]).each_cons(2) do |p1,p2| line_to(*p2) end end |
update
Gregory Brown says:
%Q{
I think you may have been confused by my ugly code there. I have
replaced it with:
1 2 3 4 5 6 |
def polygon(*points) move_to points[0] (points[1..-1] << points[0]).each do |point| line_to(*point) end end |
The reason why it’s not needed is because we draw the lines from point
to point.
But if we were drawing them segment by segment, it’d make sense.
1 2 3 4 5 |
>> [[1,2],[3,4],[5,6],[1,2]].each_cons(2) { |a| p a } [[1, 2], [3, 4]] [[3, 4], [5, 6]] [[5, 6], [1, 2]] |
I imagine I had refactored a line p1, p2 call down to just line_to(p2)
without fixing the each_cons()… sorry about that.
In general each_cons is useful when you need a sliding window of size
n across a dataset.
Here’s a more reasonable usage, for solving a simple tree-traversal
problem:
http://blog.majesticseacreature.com/archives/2008.10/euler_67.html
}
As far as I can google, prawn seemed to be the only usage of each_cons in the wild. Now that he’s refactored it, it appears that nothing on github will be using it.
If you’re curious, I’ve posted a question on the ruby mailing list here about what this is actually for.
At any rate, here’s the…
Implementation
1 2 3 4 5 6 7 8 9 10 |
def each_cons(n, &block) array = [] elements = self.to_a while elements.size > 0 do array << elements[0,n] if elements[0,n].size == n elements.shift end array.each { |set| yield set } nil end |
Rubinius begins by creating an empty array, then forcing the object to be an array, if it isn’t already.
Next, it iterates over each element in the enumerable object, adding a sub array of elements with the length of n.
Next, it iterates over each of the new array’s elements, passing the set to the block.
Finally, it returns nil.
January 29, 2009
