Posteado por: emmanueloga en: Enero 28, 2008
Ruby way of iterating over a collection is great, we don’t need to know anything about the collection. In “standard” ruby, if an object responds to “each” message, we can be sure it is a collection that will yield each element inside it.
(1..10).each do |e| puts e end
Although this way of iterating prevents us from having to index the collection, it lacks the flexibility an index can give us. For example, getting prev, next or an arbitrary value from the collection. I would say that, if possible, we should change our algorithm to better suite the ruby way of iterating. But if we absolutely need more complex iteration, there are a couple of ways of accomplishing it.For modest needs we can rely in the goodness that the ‘enumerator‘ library supply. This library extends Enumerable objects with various methods. Two of them, each_slice and each_cons can be helpful for our needs. Lets qri these methods:
# e.each_slice(n) {...}
#Iterates the given block for each slice of elements.
require 'enumerator'
(1..10).each_slice(3) {|a| p a}
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]
# e.each_cons(n) {...}
# Iterates the given block for each array of consecutive elements.
(1..10).each_cons(3) {|a| p a}
#[1, 2, 3]
#[2, 3, 4]
#[3, 4, 5]
#[4, 5, 6]
#[5, 6, 7]
#[6, 7, 8]
#[7, 8, 9]
#[8, 9, 10]
For ever finer grained iteration we can use Generators. With them we can yield arbitrary elements of a collection.
require 'generator'
def complex_iterator_for collection
prev= nil
Generator.new do |result|
collection.each_with_index do |current, idx|
nextv= collection[idx+1]
result.yield [prev, current, nextv]
prev= current
end
end
end
gen= complex_iterator_for((1..10).to_a)
while gen.next?
prev, current, nextv= gen.next
puts "# #{prev}, #{current}, #{nextv}"
end
# , 1, 2
# 1, 2, 3
# 2, 3, 4
# 3, 4, 5
# 4, 5, 6
# 5, 6, 7
# 6, 7, 8
# 7, 8, 9
# 8, 9, 10
# 9, 10,
Generators are not constrained to be used to iterate over collection, though. We could easily use them to create, for example, numerical series. Fibonacci, anyone?
require 'generator'
def fibonacci_gen till
a, b= 1, 1
Generator.new do |result|
0.upto(till) do
result.yield a
a, b = b, a + b
end
end
end
gen= fibonacci_gen(10)
while gen.next?
print "#{gen.next},"
end
# 1,1,2,3,5,8,13,21,34,55,89,
Perhaps this better explain the class name “Generator“.
Is interesting to note the analogies between Generators and Fibers (implemented on ruby 1.9). They work in a very similar way. Ruby 1.9 Fibers are not a library add-on though, but a very feature of the language, and should work much more efficiently than Generators.
This is an interesting Dave Thomas article about fibers.
Comentarios recientes