C'è un ciclo "do ... while" in Ruby?


453

Sto usando questo codice per consentire all'utente di inserire i nomi mentre il programma li memorizza in un array fino a quando non inseriscono una stringa vuota (devono premere Invio dopo ogni nome):

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

Questo codice sarebbe molto più bello in un do ... while loop:

people = []

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

In questo codice non devo assegnare informazioni ad alcuna stringa casuale.

Sfortunatamente questo tipo di loop non sembra esistere in Ruby. Qualcuno può suggerire un modo migliore per farlo?


1
Penso che il normale ciclo while sia più bello ed è più facile da leggere.
Magne,

1
@Jeremy Ruten c'è qualche possibilità che si sarebbe interessato a cambiare la risposta accettata alla risposta di Siwei Shen, loop do; ...; break if ...; end?
David Winiecki,

Risposte:


645

ATTENZIONE :

Il begin <code> end while <condition>è rifiutato dall'autore di Ruby Matz. Invece suggerisce di usare Kernel#loop, ad es

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

Ecco uno scambio di email nel 23 novembre 2005 in cui Matz afferma:

|> 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>

La wiki di RosettaCode ha una storia simile:

Nel novembre 2005, Yukihiro Matsumoto, il creatore di Ruby, si è pentito di questa funzione del ciclo e ha suggerito di usare il ciclo Kernel #.


18
Spot on. Questo begin end whilemetodo non sembrava giusto. Grazie per avermi dato il foraggio di cui avevo bisogno per convincere il resto della squadra.
Joshua Pinter,

2
Sembra che il ciclo begin-end-while stia effettivamente valutando la condizione prima di eseguire il loop. La differenza tra questo e un ciclo while regolare è che è garantito per essere eseguito almeno una volta. È abbastanza vicino da fare ... mentre per causare problemi.
user1992284,

4
Quindi, per quanto ho capito bene, start-end-while è "pentito" perché viola la semantica dei modificatori, vale a dire: vengono controllati prima di eseguire il blocco, ad esempio: mette k if! K.nil ?. Qui 'if' è un 'modificatore': viene controllato PRIMA, al fine di determinare se l'esecuzione dell'istruzione 'put k'. Questo non è il caso dei cicli while / until che (quando usati come modificatori di un blocco di inizio-fine !), vengono valutati DOPO la prima esecuzione. Forse questo ha causato il rimpianto, ma abbiamo qualcosa di "più forte" di un vecchio post sul forum, una specie di deprecazione ufficiale come quella che si presenta in molte altre occasioni?
AgostinoX,

2
Mi ci è voluto un sacco di tempo imbarazzante per capire perché il mio uso di questo ciclo rubino "do-while" non funzionava. Dovresti usare "meno" per imitare più da vicino un do-while in stile c, altrimenti potresti finire come me e dimenticare di invertire la condizione: p
Connor Clark,

1
@James Come per la posta collegata, ha detto che si stava "pentendo" di averla aggiunta. A volte le persone commettono errori, anche se sono progettisti di lingue.
Xiong Chiamiov,

188

Ho trovato il seguente frammento durante la lettura della fonte Tempfile#initializenella libreria principale di 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 prima vista, ho ipotizzato che il modificatore while sarebbe stato valutato prima del contenuto di begin ... end, ma non è così. Osservare:

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

Come prevedibile, il ciclo continuerà a essere eseguito mentre il modificatore è true.

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

Mentre sarei felice di non rivedere mai più questo idioma, inizio ... fine è abbastanza potente. Di seguito è riportato un linguaggio comune per memorizzare un metodo one-liner senza parametri:

def expensive
  @expensive ||= 2 + 2
end

Ecco un modo brutto, ma rapido per memorizzare qualcosa di più complesso:

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

Originariamente scritto da Jeremy Voorhis . Il contenuto è stato copiato qui perché sembra essere stato rimosso dal sito di origine. Copie si possono trovare anche nell'Archivio Web e nel Forum di Ruby Buzz . -Bill the Lizard


5
Per gentile concessione della Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.com/…
bta

56
Ecco perché quando mi collego a un sito esterno, mi assicuro sempre di copiare le informazioni pertinenti nella mia risposta qui.
davr

10
Perché dovresti aspettarti che il modificatore while venga valutato prima del contenuto di begin ... end? Questo è il modo in cui ... dovrebbero funzionare i loop. E perché dovresti "essere felice di non rivedere mai più questo idioma?" Che cosa c'è che non va? Non ho capito bene.
Bergie3000,

2
inizio ... fine sembra un blocco, allo stesso modo {...}. Niente di sbagliato.
Victor Pudeyev l'

1
-1: La risposta di Siwei Shen spiega che begin .. endè piuttosto malvisto. Usa loop do .. break if <condition>invece.
Kevin,

102

Come questo:

people = []

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

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


1
eh, picchiato ad esso. Dannazione.
Blorgbeard esce il

Questo codice non aggiungerà una stringa vuota all'array se non c'è input?
AndrewR,

1
Sebbene non si applichi qui, un problema del costrutto inizio-fine-mentre è che, a differenza di tutte le altre costruzioni di rubini, non restituisce il valore dell'ultima espressione: "inizia 1 fine mentre falso" restituisce zero (non 1, non falso)
tokland il

9
Puoi usare until info.empty?piuttosto che while not info.empty?.
Andrew Grimm,

In realtà @AndrewR è una specie di punto .. fare le cose prima di un confronto .. fare diplay ("rimanendo = # {conteggio}) mentre (conteggio> 0) ... Ottengo una visualizzazione di" rimanente = 0 ".. perfetto!
baash05

45

Cosa ne pensi di questo?

people = []

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

4
Bello, molto più elegante.
Blorgbeard esce il

23
Ma questo non è il ciclo "do ... while". :)
Alexander Prokofyev il

4
Ma fa la stessa cosa in questo caso, a meno che non mi sbagli
Blorgbeard esce il

18
@Blorgbeard, un ciclo do..while viene sempre eseguito una volta, quindi valuta se deve continuare a funzionare. Un ciclo while / until tradizionale può essere eseguito 0 volte. Non è una differenza ENORME, ma sono diversi.
Scott Swezey,

5
@Scott, è vero - intendevo solo che questo codice è equivalente agli OP, anche se non usa un do / while. Anche se in realtà, questo codice fa metà del "lavoro" del ciclo nella condizione, quindi non è nemmeno un ciclo while tradizionale - se la condizione non corrisponde, un po 'di lavoro è ancora fatto.
Blorgbeard esce il

11

Ecco l'articolo di testo completo dal link morto di hubbardr al mio blog.

Ho trovato il seguente frammento durante la lettura della fonte Tempfile#initializenella libreria principale di 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 prima vista, ho ipotizzato che il whilemodificatore sarebbe stato valutato prima del contenuto di begin...end, ma non è così. Osservare:

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

Come prevedibile, il ciclo continuerà a essere eseguito mentre il modificatore è true.

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

Mentre sarei felice di non rivedere mai più questo idioma, begin...endè abbastanza potente. Di seguito è riportato un linguaggio comune per memorizzare un metodo one-liner senza parametri:

def expensive
  @expensive ||= 2 + 2
end

Ecco un modo brutto, ma rapido per memorizzare qualcosa di più complesso:

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


6

Da quello che raccolgo, Matz non piace il costrutto

begin
    <multiple_lines_of_code>
end while <cond>

perché, la sua semantica è diversa da

<single_line_of_code> while <cond>

in quanto il primo costrutto esegue il codice prima di verificare la condizione e il secondo costrutto verifica la condizione prima di eseguire il codice (se mai). Presumo che Matz preferisca mantenere il secondo costrutto perché corrisponde a un costrutto di riga delle istruzioni if.

Non mi è mai piaciuto il secondo costrutto neanche per le dichiarazioni if. In tutti gli altri casi, il computer esegue il codice da sinistra a destra (ad es. || e &&) dall'alto verso il basso. Gli umani leggono il codice da sinistra a destra dall'alto verso il basso.

Suggerisco invece i seguenti costrutti:

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

Non so se questi suggerimenti analizzeranno il resto della lingua. Ma in ogni caso preferisco mantenere l'esecuzione da sinistra a destra e la coerenza del linguaggio.


3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end

3
sembra un po 'un goto. Il codice offusca la tua intenzione.
Gerhard,

Mi sembra fantastico, tranne che while truepuò essere sostituito con loop do.
David Winiecki,

@DavidWiniecki, infatti, while truepuò essere sostituito con loop do. Ma ho testato entrambi i costrutti con molte iterazioni all'interno del loop e ho scoperto che while trueè almeno 2 volte più veloce di loop do. Non riesco a spiegare la differenza, ma è sicuramente lì. (Scoperto durante il test dell'Avvento del Codice 2017, giorno 15.)
Jochem Schulenklopper,

2

Eccone un altro:

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

Preferisco questo dato che unlessè in primo piano e non leggo un sacco di codice (che potrebbe essere più di quanto mostrato qui) solo per trovare un "penzoloni" unlessalla fine. È un principio generale nel codice che modificatore e condizioni sono più facili da usare quando sono "in primo piano" in questo modo.
Michael Durrant,

A volte vorrei che noi programmatori dovessimo pagare in contanti per ogni confronto extra. E che il modo in cui "sembra" per noi era meno importante di come appare alla cosa che lo usa un milione di volte al giorno.
baash05,

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

2
sembra un po 'un goto. Il codice offusca le tue intenzioni e sembra molto confuso.
Gerhard,

offuscato * non offuscato.
Qedk,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.