Y a-t-il une boucle "do ... while" dans Ruby?


453

J'utilise ce code pour permettre à l'utilisateur d'entrer des noms pendant que le programme les stocke dans un tableau jusqu'à ce qu'ils entrent une chaîne vide (ils doivent appuyer sur Entrée après chaque nom):

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

Ce code serait beaucoup plus agréable dans une boucle do ... while:

people = []

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

Dans ce code, je n'ai pas à attribuer d'informations à une chaîne aléatoire.

Malheureusement, ce type de boucle ne semble pas exister dans Ruby. Quelqu'un peut-il suggérer une meilleure façon de procéder?


1
Je pense que la boucle while normale semble plus agréable et plus facile à lire.
Magne

1
Ruten est là @ Jeremy une chance que vous seriez intéressé à changer la réponse acceptée à la réponse de Siwei Shen, loop do; ...; break if ...; end?
David Winiecki

Réponses:


645

ATTENTION :

Le begin <code> end while <condition>est rejeté par l'auteur de Ruby, Matz. Au lieu de cela, il suggère d'utiliser Kernel#loop, par exemple

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

Voici un échange d'e-mails le 23 novembre 2005 où Matz déclare:

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

Le wiki de RosettaCode a une histoire similaire:

En novembre 2005, Yukihiro Matsumoto, le créateur de Ruby, a regretté cette fonctionnalité de boucle et a suggéré d'utiliser la boucle Kernel #.


18
Repérez. Cette begin end whileméthode ne semblait pas correcte. Merci de m'avoir donné le fourrage dont j'avais besoin pour convaincre le reste de l'équipe.
Joshua Pinter

2
Il semble que la boucle begin-end-while évalue réellement la condition avant d'exécuter la boucle. La différence entre cela et une boucle while régulière est qu'elle est garantie de fonctionner au moins une fois. C'est juste assez proche pour faire ... tout en causant des problèmes.
user1992284

4
Donc, d'après ce que j'ai bien compris, le début-fin-temps est "regretté" car il viole la sémantique des modificateurs, c'est-à-dire: ils sont vérifiés avant d'exécuter le bloc, par exemple: met k if! K.nil ?. Ici, `` if '' est un `` modificateur '': il est vérifié AVANT, afin de déterminer si l'exécution de l'instruction `` met k ''. Ce n'est pas le cas des boucles while / until qui (lorsqu'il est utilisé comme modificateurs d'un bloc début-fin) !), sont évalués APRÈS la première exécution. Peut-être cela a-t-il causé des regrets, mais avons-nous quelque chose de `` plus fort '' qu'un ancien message de forum, une sorte de dépréciation officielle comme il en a l'habitude à de nombreuses autres occasions?
AgostinoX

2
Il m'a fallu un temps embarrassant pour comprendre pourquoi mon utilisation de cette boucle ruby ​​`` do-while '' ne fonctionnait pas. Vous devez utiliser `` à moins '' pour imiter de plus près un do-while de style c, sinon vous pourriez vous retrouver comme moi et oublier d'inverser la condition: p
Connor Clark

1
@James Selon le mail lié, il a dit qu'il "regrettait" de l'avoir ajouté. Parfois, les gens font des erreurs, même s'ils sont des concepteurs de langage.
Xiong Chiamiov

188

J'ai trouvé l'extrait suivant lors de la lecture de la source Tempfile#initializedans la bibliothèque principale 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)

À première vue, j'ai supposé que le modificateur while serait évalué avant le contenu de begin ... end, mais ce n'est pas le cas. Observer:

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

Comme vous vous en doutez, la boucle continuera de s'exécuter tant que le modificateur est vrai.

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

Bien que je serais heureux de ne plus jamais revoir cet idiome, commencez ... fin est assez puissant. Voici un idiome commun pour mémoriser une méthode à une ligne sans paramètres:

def expensive
  @expensive ||= 2 + 2
end

Voici un moyen laid mais rapide de mémoriser quelque chose de plus complexe:

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

Écrit à l'origine par Jeremy Voorhis . Le contenu a été copié ici car il semble avoir été retiré du site d'origine. Des copies peuvent également être trouvées dans les archives Web et sur Ruby Buzz Forum . -Bill le lézard



56
C'est pourquoi lorsque je crée un lien vers un site externe, je m'assure toujours de copier les informations pertinentes dans ma réponse ici.
davr

10
Pourquoi vous attendriez-vous à ce que le modificateur while soit évalué avant le contenu de begin ... end? C'est ainsi que les boucles sont censées fonctionner. Et pourquoi voudriez-vous "être heureux de ne plus jamais revoir cet idiome?" Qu'est ce qui ne va pas avec ça? Je suis confus.
bergie3000

2
begin ... end ressemble à un bloc, de même {...}. Rien de mal à cela.
Victor Pudeyev

1
-1: La réponse de Siwei Shen explique que cela begin .. endest quelque peu mal vu. Utilisez loop do .. break if <condition>plutôt.
Kevin

102

Comme ça:

people = []

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

Référence: Ruby's Hidden do {} while () Loop


1
heh, battu à elle. Zut.
Blorgbeard est sorti le

Ce code ne va-t-il pas ajouter une chaîne vide au tableau s'il n'y a pas d'entrée?
AndrewR

1
Bien qu'elle ne s'applique pas ici, un problème de la construction begin-end-while est que, contrairement à toutes les autres constructions ruby, elle ne renvoie pas la valeur de la dernière expression: "begin 1 end while false" renvoie nil (not 1, pas faux)
tokland

9
Vous pouvez utiliser until info.empty?plutôt que while not info.empty?.
Andrew Grimm

En fait @AndrewR c'est en quelque sorte le point .. de faire les choses avant une comparaison .. faire un affichage ("restant = # {compte}) tandis que (compter> 0) ... J'obtiens un affichage de" restant = 0 ".. parfait!
baash05

45

Que dis-tu de ça?

people = []

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

4
Nice, beaucoup plus élégant.
Blorgbeard est sorti le

23
Mais ce n'est pas une boucle "do ... while". :)
Alexander Prokofyev

4
Mais cela fait la même chose dans ce cas, sauf erreur de
ma part

18
@Blorgbeard, une boucle do.. While s'exécute toujours une fois, puis évalue pour voir si elle doit continuer à s'exécuter. Une boucle traditionnelle while / until peut être exécutée 0 fois. Ce n'est pas une énorme différence, mais ils sont différents.
Scott Swezey

5
@Scott, c'est vrai - je voulais juste dire que ce code est équivalent aux OP, même s'il n'utilise pas de do / while. Bien que vraiment, ce code effectue la moitié du "travail" de la boucle dans la condition, ce n'est donc pas tout à fait une boucle while traditionnelle - si la condition ne correspond pas, un certain travail est toujours effectué.
Blorgbeard est sorti le

11

Voici l'article en texte intégral du lien mort de hubbardr vers mon blog.

J'ai trouvé l'extrait suivant lors de la lecture de la source Tempfile#initializedans la bibliothèque principale 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)

À première vue, j'ai supposé que le whilemodificateur serait évalué avant le contenu de begin...end, mais ce n'est pas le cas. Observer:

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

Comme vous vous en doutez, la boucle continuera de s'exécuter tant que le modificateur est vrai.

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

Bien que je serais heureux de ne plus jamais revoir cet idiome, il begin...endest assez puissant. Voici un idiome commun pour mémoriser une méthode à une ligne sans paramètres:

def expensive
  @expensive ||= 2 + 2
end

Voici un moyen laid mais rapide de mémoriser quelque chose de plus complexe:

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

D'après ce que je comprends, Matz n'aime pas la construction

begin
    <multiple_lines_of_code>
end while <cond>

parce que sa sémantique est différente de

<single_line_of_code> while <cond>

en ce que la première construction exécute le code en premier avant de vérifier la condition, et la deuxième construction teste la condition en premier avant d'exécuter le code (si jamais). Je suppose que Matz préfère conserver la deuxième construction, car elle correspond à une construction de ligne des instructions if.

Je n'ai jamais aimé la deuxième construction, même pour les instructions if. Dans tous les autres cas, l'ordinateur exécute le code de gauche à droite (par exemple || et &&) de haut en bas. Les humains lisent le code de gauche à droite de haut en bas.

Je suggère plutôt les constructions suivantes:

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

Je ne sais pas si ces suggestions seront analysées avec le reste de la langue. Mais dans tous les cas, je préfère garder l'exécution de gauche à droite ainsi que la cohérence du langage.


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

3
cela ressemble un peu à un goto. Le code confirme votre intention.
Gerhard

Ça me va bien, sauf while truepeut être remplacé par loop do.
David Winiecki

@DavidWiniecki, en effet, while truepeut être remplacé par loop do. Mais j'ai testé les deux constructions avec beaucoup d'itérations à l'intérieur de la boucle, et j'ai découvert que while truec'était au moins 2x plus rapide que loop do. Je ne peux pas expliquer la différence, mais c'est définitivement là. (Découvert lors des tests d'Advent of Code 2017, jour 15.)
Jochem Schulenklopper

2

En voici un autre:

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

Je préfère cela car le unlessest juste à l'avant et je ne lis pas à travers un tas de code (qui pourrait être plus que montré ici) seulement pour trouver un «balancement» unlessà la fin. C'est un principe général dans le code que le modificateur et les conditions sont plus faciles à utiliser quand ils sont «à l'avant» comme ça.
Michael Durrant

Je souhaite parfois que nous, les codeurs, devions payer en espèces pour chaque comparaison supplémentaire. Et que la façon dont elle nous «semblait» était moins importante que la façon dont elle ressemblait à la chose qui l'utilisait un million de fois par jour.
baash05

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

2
cela ressemble un peu à un goto. Le code obfustifie votre intention et ressemble très peu à duuby.
Gerhard

obscurcir * pas obscurcir.
qedk
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.