¿Quién no se ha encontrado alguna vez escribiendo un bucle donde necesita saber cuándo está en el último elemento? ¿O cuál es el próximo elemento?
coleccion.each {|item| if estoy_en_el_ultimo? ....
La solución típica en este caso sería usar each_with_index
y dentro del bloque coleccion.size == index + 1
, pero, siendo Ruby como es, molaría algo más “objetista”
Seguramente habrán muchas implementaciones de algo así, pero me apetecía experimentar con una, así que antes de buscar le di un poco de caña al Enumerable
Partiendo de que en Ruby se puede modificar cualquier cosa (entendiendo por cosa... lo que sea), bastaría con añadir un método iterate
al módulo Enumerable
, de manera que todos los objetos que actúan como secuencias se beneficiarían del método
Por ejemplo, podríamos tener
output = '' (0..10).map { (rand * 10).to_i }.iterate {|iterator| output << "<b>" if iterator.first? || iterator.last? output << iterator.object.to_s if iterator.object % 2 == 0 output << " " << iterator.next.to_s iterator.skip_next! end output << "</b>" if iterator.first? || iterator.last? output << " <br />\n" unless iterator.last? } puts output
Que nos daría una salida como
<b>6 3</b> <br /> 6 2 <br /> 7 <br /> 3 <br /> 2 1 <br /> 8 3 <br /> <b>5</b>
O también (un poco más rebuscado)
[1, "uno", 2, "dos", "tres", 3, "cuatro", "cinco", "seis", 0].iterate {|i| puts "<div>" if i.first? i.skip_next! i.object i.object.times {|n| print i.next(n + 1) + " " } puts i.last? ? "</div>" : "<br />" }
Que nos daría
<div> uno <br /> dos tres <br /> cuatro cinco seis <br /> </div>
¿Y cómo conseguir todo eso?
module Enumerable class Iterator attr_accessor :object attr_accessor :index attr_reader :parent def initialize(parent) @parent = parent @skip_items = 0 end def last? index == parent.size - 1 end def first? index == 0 end def next(offset = 1) (offset + index >= parent.size) ? nil : parent[index + offset] end def previous(offset = 1) (index - offset < 0) ? nil : parent[index - offset] end def skip_next!(items = 1) @skip_items += items.to_i end def skip?(keep_value = false) if @skip_items > 0 @skip_items -= 1 unless keep_value true end end end def iterate(&block) iterator = Iterator.new self each_with_index {|object, index| next if iterator.skip? iterator.object = object iterator.index = index block.call iterator } end end
Después de terminar este pequeño experimento hice una búsqueda rápida a ver qué más había por ahí. Lo único que vi, sin buscar demasiado, fue un mensaje en la ruby-tak del 2002, donde hay una implementación parecida a ésta.