Old Emmanuel Oga's Weblog (new one is at www.emmanueloga.com)

Non-standard iterations over a collection: Getting prev and next element and sliding a window

Posted in Uncategorized by emmanueloga on 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.

Tagged with:

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: