¿Hay un bucle "do ... while" en Ruby?

453

Estoy usando este código para permitir que el usuario ingrese nombres mientras el programa los almacena en una matriz hasta que ingresen una cadena vacía (deben presionar enter después de cada nombre):

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 un bucle do ... while:

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 alguna cadena aleatoria.

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

Jeremy Ruten
fuente
1
Creo que el bucle while normal se ve mejor y es más fácil de leer.
Magne
1
@Jeremy Ruten, ¿hay alguna posibilidad de que te interese cambiar la respuesta aceptada a la respuesta de Siwei Shen loop do; ...; break if ...; end?
David Winiecki

Respuestas:

645

PRECAUCION :

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

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

Aquí hay un intercambio de correo electrónico el 23 de noviembre de 2005 donde Matz dice:

|> Don't use it please.  I'm regretting this feature, and I'd like to
|> remove it in the future if it's possible.
|
|I'm surprised.  What do you regret about it?

Because it's hard for users to tell

  begin <code> end while <cond>

works differently from

  <code> while <cond>

El wiki de RosettaCode tiene una historia similar:

Durante noviembre de 2005, Yukihiro Matsumoto, el creador de Ruby, se arrepintió de esta función de bucle y sugirió usar Kernel # loop.

Siwei Shen 申思维
fuente
18
Correcto. Este begin end whilemétodo no parecía correcto. Gracias por darme el forraje que necesitaba para convencer al resto del equipo.
Joshua Pinter el
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 normal es que está garantizado que se ejecute al menos una vez. Es lo suficientemente cerca para hacer ... mientras causa problemas.
user1992284
44
Entonces, por lo que he entendido bien, begin-end-while se "lamenta" porque viola la semántica de los modificadores, es decir: se verifican antes de ejecutar el bloque, por ejemplo: pone k si! K.nil ?. Aquí 'if' es un 'modificador': se verifica ANTES, para determinar si la ejecución de la instrucción 'pone k'. Ese no es el caso de los bucles while / while que (cuando se usan como modificadores de un bloque de inicio-fin-final) !), se evalúan DESPUÉS de la primera ejecución. Quizás esto haya causado el arrepentimiento, pero ¿tenemos algo 'más fuerte' que una publicación anterior en el foro, una especie de desaprobación oficial como solía ser en muchas otras ocasiones?
AgostinoX
2
Me tomó una cantidad de tiempo vergonzosa descubrir por qué mi uso de este bucle 'do-while' de rubí no estaba funcionando. Debería usar 'a menos' para imitar más de cerca un tiempo de estilo c, de lo contrario puede terminar como yo y olvidar invertir la condición: p
Connor Clark
1
@ James Según el correo vinculado, dijo que estaba "lamentando" agregarlo. A veces las personas cometen errores, incluso si son diseñadores de idiomas.
Xiong Chiamiov
188

Encontré el siguiente fragmento mientras leía la fuente Tempfile#initializeen la biblioteca principal de 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, supuse que el modificador while se evaluaría antes de que el contenido de begin ... end, pero ese no es el caso. Observar:

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

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

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

Si bien sería feliz de no volver a ver este idioma, comenzar ... el final es bastante poderoso. El siguiente es un idioma común para memorizar un método de una línea sin parámetros:

def expensive
  @expensive ||= 2 + 2
end

Aquí hay una forma fea pero rápida de memorizar 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

Originalmente escrito por Jeremy Voorhis . El contenido se ha copiado aquí porque parece haber sido retirado del sitio de origen. También se pueden encontrar copias en el Archivo Web y en el Foro Ruby Buzz . -Bill the Lizard

Hubbardr
fuente
56
Es por eso que cuando hago un enlace a un sitio externo, siempre me aseguro de copiar la información relevante en mi respuesta aquí.
davr
10
¿Por qué esperarías que el modificador while sea evaluado antes de que el contenido de begin ... end? Esa es la forma de hacerlo ... mientras se supone que los bucles funcionan. ¿Y por qué "estarías feliz de no volver a ver este idioma?" ¿Qué tiene de malo? Estoy confundido.
bergie3000
2
comenzar ... final parece un bloque, de manera similar {...}. No tiene nada de malo.
Victor Pudeyev
1
-1: la respuesta de Siwei Shen explica que begin .. endestá algo mal visto. Usar en su loop do .. break if <condition>lugar.
Kevin
102

Me gusta esto:

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

Blorgbeard está fuera
fuente
1
je, golpeado a eso. Maldición.
Blorgbeard sale el
¿Este código no agregará 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 demás construcciones ruby, no devuelve el valor de la última expresión: "begin 1 end while false" devuelve nil (no 1, no es falso)
tokland el
99
Puedes usar en until info.empty?lugar de while not info.empty?.
Andrew Grimm
En realidad @AndrewR ese es el punto ... hacer cosas antes de una comparación ... hacer diplay ("restantes = # {conteo}) mientras que (conteo> 0) ... obtengo una visualización de" restantes = 0 "... perfecto!
baash05
45

¿Qué tal esto?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end
AndrewR
fuente
44
Bonito, mucho más elegante.
Blorgbeard sale el
23
Pero esto no es "hacer ... mientras" bucle. :)
Alexander Prokofyev
44
Pero hace lo mismo en este caso, a menos que me equivoque
Blorgbeard saldrá el
18
@Blorgbeard, un bucle do.. while siempre se ejecuta una vez, luego se evalúa para ver si debe continuar ejecutándose. Un ciclo while / until tradicional puede ejecutarse 0 veces. No es una GRAN diferencia, pero son diferentes.
Scott Swezey
55
@Scott, eso es cierto, solo quise decir que este código es equivalente a los OP, a pesar de que no usa do / while. Aunque realmente, este código hace la mitad del "trabajo" del bucle en la condición, por lo que tampoco es un bucle while tradicional; si la condición no coincide, todavía queda algo de trabajo.
Blorgbeard sale el
11

Aquí está el artículo de texto completo del enlace muerto de hubbardr a mi blog.

Encontré el siguiente fragmento mientras leía la fuente Tempfile#initializeen la biblioteca principal de 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, supuse que el whilemodificador se evaluaría antes que el contenido de begin...end, pero ese no es el caso. Observar:

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

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

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

Si bien estaría feliz de no volver a ver este idioma, begin...endes bastante poderoso. El siguiente es un idioma común para memorizar un método de una línea sin parámetros:

def expensive
  @expensive ||= 2 + 2
end

Aquí hay una forma fea pero rápida de memorizar 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
jvoorhis
fuente
10

Esto funciona correctamente ahora:

begin
    # statment
end until <condition>

Pero, puede eliminarse en el futuro, porque la begindeclaración es contradictoria. Ver: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (el creador de Ruby) recomendó hacerlo de esta manera:

loop do
    # ...
    break if <condition>
end
Ala de acero
fuente
6

Por lo que deduzco, a Matz no le gusta la construcción

begin
    <multiple_lines_of_code>
end while <cond>

porque su semántica es diferente a

<single_line_of_code> while <cond>

en que la primera construcción ejecuta el código primero antes de verificar la condición, y la segunda construcción prueba la condición primero antes de ejecutar el código (si alguna vez). Supongo que Matz prefiere mantener la segunda construcción porque coincide con una construcción de línea de sentencias if.

Nunca me gustó 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 de arriba a abajo.

Sugiero 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 se analizarán con el resto del lenguaje. Pero en cualquier caso prefiero mantener la ejecución de izquierda a derecha, así como la coherencia del lenguaje.

jds
fuente
3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end
Paul Gillard
fuente
3
esto se parece un poco a un goto. El código ofusca tu intención.
Gerhard
Me parece genial, excepto que while truepuede ser reemplazado por loop do.
David Winiecki
@DavidWiniecki, de hecho, while truepuede ser reemplazado con loop do. Pero probé ambas construcciones con muchas iteraciones dentro del bucle y descubrí que while truees al menos 2 veces más rápido que loop do. No puedo explicar la diferencia, pero definitivamente está ahí. (Descubierto al probar Advent of Code 2017, día 15.)
Jochem Schulenklopper
2

Aqui hay otro más:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end
Moray
fuente
Prefiero esto ya que unlessestá justo al principio y no leo un montón de código (que podría ser más de lo que se muestra aquí) solo para encontrar un 'colgante' unlessal 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' como este.
Michael Durrant
A veces desearía que los codificadores tuvieran que pagar en efectivo por cada comparación adicional. Y que "cómo se ve" para nosotros era menos importante que cómo se ve para lo que lo usa un millón de veces al día.
baash05
-3
ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
is_that_okay
fuente
2
esto se parece un poco a un goto. El código ofusca tu intención y se ve muy desordenado.
Gerhard el
ofuscar * no ofuscar.
qedk