sintaxis rails iterators for every ruby iteration range

ruby - for - rails iterators



¿Hay alguna razón por la que no podamos iterar en "rango inverso" en ruby? (11)

Traté de iterar hacia atrás con el uso de un Rango y each :

(4..0).each do |i| puts i end ==> 4..0

La iteración a través de 0..4 escribe los números. Por otro lado, Range r = 4..0 parece estar bien, r.first == 4 , r.last == 0 .

Me parece extraño que la construcción anterior no produzca el resultado esperado. ¿Cuál es el motivo para eso? ¿Cuáles son las situaciones cuando este comportamiento es razonable?


¿Qué tal (0..1).reverse_each que itera el rango hacia atrás?


Añado otra posibilidad de cómo realizar la iteración sobre el rango inverso. No lo uso, pero es una posibilidad. Es un poco arriesgado para los objetos de núcleo de rubí mono parche.

class Range def each(&block) direction = (first<=last ? 1 : -1) i = first not_reached_the_end = if first<=last lambda {|i| i<=last} else lambda {|i| i>=last} end while not_reached_the_end.call(i) yield i i += direction end end end


Al iterar sobre un rango en Ruby con each llama al método succ en el primer objeto del rango.

$ 4.succ => 5

Y 5 está fuera del rango.

Puede simular la iteración inversa con este truco:

(-4..0).each { |n| puts n.abs }

John señaló que esto no funcionaría si abarca 0. Esto:

>> (-2..2).each { |n| puts -n } 2 1 0 -1 -2 => -2..2

No puedo decir que realmente me gusta ninguno de ellos porque oscurecen el intento.


Como bta dijo, la razón es que Range#each envía succ a su comienzo, luego al resultado de esa llamada succ , y así sucesivamente hasta que el resultado sea mayor que el valor final. No puede obtener de 4 a 0 llamando a succ , y de hecho ya comienza más que al final.


De acuerdo con el libro "Programming Ruby", el objeto Range almacena los dos puntos finales del rango y usa el miembro .succ para generar los valores intermedios. Según el tipo de tipo de datos que esté utilizando en su rango, siempre puede crear una subclase de Integer y redefinir el miembro .succ para que actúe como un iterador inverso (probablemente también desee volver a definir .next también).

También puede lograr los resultados que está buscando sin usar un Rango. Prueba esto:

4.step(0, -1) do |i| puts i end

Esto pasará de 4 a 0 en pasos de -1. Sin embargo, no sé si esto funcionará para nada, excepto los argumentos enteros.


El OP escribió

Me parece extraño que la construcción anterior no produzca el resultado esperado. ¿Cuál es el motivo para eso? ¿Cuáles son las situaciones cuando este comportamiento es razonable?

no ''¿Se puede hacer?'' pero para responder a la pregunta que no se formuló antes de llegar a la pregunta que realmente se hizo:

$ irb 2.1.5 :001 > (0..4) => 0..4 2.1.5 :002 > (0..4).each { |i| puts i } 0 1 2 3 4 => 0..4 2.1.5 :003 > (4..0).each { |i| puts i } => 4..0 2.1.5 :007 > (0..4).reverse_each { |i| puts i } 4 3 2 1 0 => 0..4 2.1.5 :009 > 4.downto(0).each { |i| puts i } 4 3 2 1 0 => 4

Como reverse_each se dice que construye una matriz completa, downto claramente va a ser más eficiente. El hecho de que un diseñador de lenguaje incluso podría considerar la implementación de cosas así se relaciona con la respuesta a la pregunta real tal como se le preguntó.

Para responder la pregunta como realmente se le preguntó ...

La razón es porque Ruby es un lenguaje infinitamente sorprendente. Algunas sorpresas son agradables, pero hay un montón de comportamientos que se rompen por completo. Incluso si algunos de los siguientes ejemplos son corregidos por lanzamientos más nuevos, hay muchos otros, y siguen siendo acusaciones sobre la mentalidad del diseño original:

nil.to_s .to_s .inspect

resultados en "" pero

nil.to_s # .to_s # Don''t want this one for now .inspect

resultados en

syntax error, unexpected ''.'', expecting end-of-input .inspect ^

Probablemente esperarías que << y push sean lo mismo para anexar matrices, pero

a = [] a << *[:A, :B] # is illegal but a.push *[:A, :B] # isn''t.

Probablemente esperas que ''grep'' se comporte como su equivalente de línea de comandos de Unix, pero hace === matching not = ~, a pesar de su nombre.

$ echo foo | grep . foo $ ruby -le ''p ["foo"].grep(".")'' []

Varios métodos son alias inesperados para cada uno, por lo que debe aprender varios nombres para la misma cosa, por ejemplo, find y detect , incluso si le gusta la mayoría de los desarrolladores y solo utiliza uno u otro. Lo mismo ocurre con el size , el count y la length , a excepción de las clases que definen cada uno de manera diferente, o no definen uno o dos en absoluto.

A menos que alguien haya implementado otra cosa, como el método central, el tap ha sido redefinido en varias bibliotecas de automatización para presionar algo en la pantalla. Buena suerte para descubrir qué está pasando, especialmente si algún módulo requerido por algún otro módulo ha monkeado otro módulo para hacer algo no documentado.

El objeto variable de entorno, ENV no admite ''fusionar'', por lo que debe escribir

ENV.to_h.merge(''a'': ''1'')

Como beneficio adicional, incluso puede redefinir las constantes de usted o de otra persona si cambia de opinión sobre lo que deberían ser.


Esto funcionó para mi caso de uso perezosa

(-999999..0).lazy.map{|x| -x}.first(3) #=> [999999, 999998, 999997]


Incluso puede usar un ciclo for :

for n in 4.downto(0) do print n end

que imprime:

4 3 2 1 0


Otra forma es (1..10).to_a.reverse


Un rango es solo eso: algo definido por su inicio y su final, no por su contenido. "Iterar" en un rango en realidad no tiene sentido en un caso general. Considere, por ejemplo, cómo "iteraría" sobre el rango producido por dos fechas. ¿Podrías iterar por día? ¿Por mes? ¿por año? ¿por semana? No está bien definido. OMI, el hecho de que está permitido para los rangos de avance debe verse como un método de conveniencia solamente.

Si desea iterar hacia atrás en un rango como ese, siempre puede usar downto :

$ r = 10..6 => 10..6 $ (r.first).downto(r.last).each { |i| puts i } 10 9 8 7 6

Aquí hay algunos pensamientos más de otros sobre por qué es difícil permitir la iteración y tratar consistentemente con rangos inversos.


si la lista no es tan grande. creo que [*0..4].reverse.each { |i| puts i } [*0..4].reverse.each { |i| puts i } es la manera más simple.