2008-09-25 24 views
392

Estoy usando este código para permitir que el usuario introduzca en los nombres, mientras que el programa de los almacena en una matriz hasta que entran en una cadena vacía (que debe presionar ENTRAR después de cada nombre):¿Hay un ciclo "do ... while" en Ruby?

people = [] 
info = 'a' # must fill variable with something, otherwise loop won't execute 

while not info.empty? 
    info = gets.chomp 
    people += [Person.new(info)] if not info.empty? 
end 

Este código se vería mucho mejor en una do ... while loop:

people = [] 

do 
    info = gets.chomp 
    people += [Person.new(info)] if not info.empty? 
while not info.empty? 

En este código no tengo que asignar información a una cadena aleatoria.

Desafortunadamente, este tipo de bucle no parece existir en Ruby. ¿Alguien puede sugerir una mejor manera de hacer esto?

+1

Creo que el ciclo while normal se ve mejor y es más fácil de leer. – Magne

+1

@Jeremy Ruten ¿hay alguna posibilidad de que le interese cambiar la respuesta aceptada a la respuesta de Siwei Shen, 'loop do; ...; romper si ...; fin'? –

Respuesta

525

PRECAUCIÓN:

El begin <code> end while <condition> es rechazado por el autor de Ruby, Matz. En cambio, sugiere usar Kernel#loop, p.

loop do 
    # some code here 
    break if <condition> 
end 

Para más detalles, consultar: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745 (or via Wayback), y el wiki: http://rosettacode.org/wiki/Loops/Do-while#Ruby

+16

Spot on. Este método 'begin end while' no parece correcto. Gracias por darme el forraje que necesitaba para convencer al resto del equipo. –

+2

Parece que el ciclo begin-end-while está evaluando la condición antes de ejecutar el ciclo. La diferencia entre eso y un ciclo while regular es que se garantiza que se ejecutará al menos una vez. Está lo suficientemente cerca como para hacer ... mientras causar problemas. – user1992284

+3

Entonces, por lo que he entendido bien, begin-end-while se "lamenta" porque viola la semántica de los modificadores, es decir: se comprueban antes de ejecutar el bloque, por ejemplo: puts k if! K.nil? . Aquí 'si' es un 'modificador': se comprueba ANTES, para determinar si se ejecuta la instrucción 'puts k'. Ese no es el caso de while/until loops (cuando se usan como modificadores de un bloque begin-end-block !), se evalúan DESPUÉS de la primera ejecución. Tal vez esto causó lamentación, pero ¿tenemos algo 'más fuerte' que una publicación en el foro anterior, una especie de desaprobación oficial como suele serlo en muchas otras ocasiones? – AgostinoX

184

Escrito originalmente por Jeremy Voorhis. El contenido se ha copiado aquí porque parece haberse eliminado del sitio de origen. Las copias también se pueden encontrar en el Web Archive y en el Ruby Buzz Forum. -Bill el Lizard

he encontrado el siguiente fragmento, mientras que la lectura de la fuente de Tempfile#initialize en la biblioteca núcleo Ruby:

begin 
    tmpname = File.join(tmpdir, make_tmpname(basename, n)) 
    lock = tmpname + '.lock' 
    n += 1 
end while @@cleanlist.include?(tmpname) or 
    File.exist?(lock) or File.exist?(tmpname) 

A primera vista, asumí el modificador mientras se evaluó antes de que los contenidos de comienzo ... final, pero ese no es el caso. Observe:

>> begin 
?> puts "do {} while()" 
>> end while false 
do {} while() 
=> nil 

Como era de esperar, el ciclo continuará ejecutándose mientras el modificador sea verdadero.

>> n = 3 
=> 3 
>> begin 
?> puts n 
>> n -= 1 
>> end while n > 0 
3 
2 
1 
=> nil 

Mientras yo estaría feliz de ver nunca este idioma nuevo, begin ... end es bastante potente. El siguiente es un idioma común a memoize un método de una sola línea sin params:

def expensive 
    @expensive ||= 2 + 2 
end 

Aquí es un feo, pero forma rápida de memoize algo más complejo:

def expensive 
    @expensive ||= 
    begin 
     n = 99 
     buf = "" 
     begin 
     buf << "#{n} bottles of beer on the wall\n" 
     # ... 
     n -= 1 
     end while n > 0 
     buf << "no more bottles of beer" 
    end 
end 
+5

Cortesía de Wayback Machine: http://web.archive.org/web/20080206125158/http://archive.jvoorhis.com/articles/2007/06/13/ruby-hidden-do-while-loop – bta

+52

This Es por eso que cuando me enlace a un sitio externo, siempre me aseguro de copiar la información relevante en mi respuesta aquí. – davr

+9

¿Por qué esperar que el modificador while se evalúe antes de que el contenido de begin ... end? Esa es la forma en que se hacen ... mientras se supone que los bucles funcionan. ¿Y por qué "estarías feliz de nunca volver a ver este idioma?" ¿Qué pasa con eso? Estoy confundido. – bergie3000

97

De esta manera:

people = [] 

begin 
    info = gets.chomp 
    people += [Person.new(info)] if not info.empty? 
end while not info.empty? 

Referencia: Ruby's Hidden do {} while() Loop

+0

heh, golpeado. Maldita sea. – Blorgbeard

+0

¿No agregará este código una cadena vacía a la matriz si no hay entrada? – AndrewR

+1

Aunque no se aplica aquí, un problema de la construcción begin-end-while es que, a diferencia de todas las otras construcciones de ruby, no devuelve el valor de la última expresión: "begin 1 end while false" devuelve nil (not 1 , no falso) – tokland

40

¿Qué tal esto?

people = [] 

until (info = gets.chomp).empty? 
    people += [Person.new(info)] 
end 
+3

Agradable, mucho más elegante. – Blorgbeard

+20

Pero esto no es "do ... while" loop. :) –

+3

Pero hace lo mismo en este caso, a menos que esté equivocado – Blorgbeard

-3
ppl = [] 
while (input=gets.chomp) 
if !input.empty? 
    ppl << input 
else 
p ppl; puts "Goodbye"; break 
end 
end 
+2

esto se parece un poco a un goto. El código muestra tu intención y se ve muy poco rudimentario. – Gerhard

+0

ofuscar * no ofender. – therewillbecoffee

11

Aquí está el texto completo del artículo del vínculo roto de hubbardr a mi blog.

He encontrado el siguiente fragmento, mientras que la lectura de la fuente de Tempfile#initialize en la biblioteca núcleo Ruby:

begin 
    tmpname = File.join(tmpdir, make_tmpname(basename, n)) 
    lock = tmpname + '.lock' 
    n += 1 
end while @@cleanlist.include?(tmpname) or 
    File.exist?(lock) or File.exist?(tmpname) 

A primera vista, asumí el modificador while sería evaluado antes de que los contenidos de begin...end, pero que no es el caso.Observe:

>> begin 
?> puts "do {} while()" 
>> end while false 
do {} while() 
=> nil 

Como era de esperar, el ciclo continuará ejecutándose mientras el modificador sea verdadero.

>> n = 3 
=> 3 
>> begin 
?> puts n 
>> n -= 1 
>> end while n > 0 
3 
2 
1 
=> nil 

Mientras yo estaría feliz de ver nunca este idioma nuevo, begin...end es bastante potente. El siguiente es un idioma común a memoize un método de una sola línea sin params:

def expensive 
    @expensive ||= 2 + 2 
end 

Aquí es un feo, pero forma rápida de memoize algo más complejo:

def expensive 
    @expensive ||= 
    begin 
     n = 99 
     buf = "" 
     begin 
     buf << "#{n} bottles of beer on the wall\n" 
     # ... 
     n -= 1 
     end while n > 0 
     buf << "no more bottles of beer" 
    end 
end 
3
a = 1 
while true 
    puts a 
    a += 1 
    break if a > 10 
end 
+3

esto se parece un poco a un goto. El código ofusca tu intención. – Gerhard

+0

Me parece genial, excepto que 'while true' se puede reemplazar por' loop do'. –

+0

@DavidWiniecki, de hecho, 'while true' se puede reemplazar por' loop do'. Pero probé ambas construcciones con muchas iteraciones dentro del ciclo, y descubrí que 'while true' es al menos 2x _faster_ que' loop do'. No puedo explicar la diferencia, pero definitivamente está ahí. (Descubierto mientras probaba Advent of Code 2017, día 15.) –

1

Aquí hay otro uno:

people = [] 
1.times do 
    info = gets.chomp 
    unless info.empty? 
    people += [Person.new(info)] 
    redo 
    end 
end 
+0

Prefiero esto ya que el 'a menos 'está justo al frente y no leo un montón de código (que podría ser más que lo que se muestra aquí) solo para encontrar un' colgando 'a menos que' al final. Es un principio general en el código que el modificador y las condiciones son más fáciles de usar cuando están 'por adelantado' de esta manera. –

+0

A veces me gustaría que los codificadores tuvieran que pagar en efectivo por cada comparación extra. Y que la forma en que "se ve" para nosotros era menos importante que la forma en que se ve la cosa que la usa un millón de veces al día. – baash05

5

Por lo que sé, Matz no le gusta el constructo

begin 
    <multiple_lines_of_code> 
end while <cond> 

porque, es la semántica es diferente que

<single_line_of_code> while <cond> 

en que la primera construcción ejecuta el código primero antes de verificar la c ondition, y la segunda construcción prueba primero la condición antes de ejecutar el código (si es que lo hace). Supongo que Matz prefiere mantener el segundo constructo porque coincide con un constructo de línea de enunciados if.

Nunca me ha gustado la segunda construcción incluso para las declaraciones if. En todos los demás casos, la computadora ejecuta el código de izquierda a derecha (p. Ej. || y & &) de arriba a abajo. Los humanos leen el código de izquierda a derecha arriba a abajo.

que sugieren las siguientes construcciones en su lugar:

if <cond> then <one_line_code>  # matches case-when-then statement 

while <cond> then <one_line_code> 

<one_line_code> while <cond> 

begin <multiple_line_code> end while <cond> # or something similar but left-to-right 

No sé si esas sugerencias analizará con el resto de la lengua. Pero en cualquier caso Prefiero mantener la ejecución de izquierda a derecha, así como la consistencia del lenguaje.