Gibt es in Ruby eine "do ... while" -Schleife?

452

Ich verwende diesen Code, damit der Benutzer Namen eingeben kann, während das Programm sie in einem Array speichert, bis er eine leere Zeichenfolge eingibt (er muss nach jedem Namen die Eingabetaste drücken):

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

Dieser Code würde in einer do ... while-Schleife viel besser aussehen:

people = []

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

In diesem Code muss ich keiner zufälligen Zeichenfolge Informationen zuweisen.

Leider scheint diese Art von Schleife in Ruby nicht zu existieren. Kann jemand einen besseren Weg vorschlagen, dies zu tun?

Jeremy Ruten
quelle
1
Ich denke, die normale while-Schleife sieht besser aus und ist leichter zu lesen.
Magne
1
@Jeremy Ruten Gibt es eine Chance, dass Sie daran interessiert sind, die akzeptierte Antwort auf die Antwort von Siwei Shen zu ändern loop do; ...; break if ...; end?
David Winiecki

Antworten:

643

VORSICHT :

Das begin <code> end while <condition>wird von Rubys Autor Matz abgelehnt. Stattdessen schlägt er vor Kernel#loop, z

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

Hier ist ein E-Mail-Austausch am 23. November 2005, in dem Matz Folgendes feststellt:

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

RosettaCode Wiki hat eine ähnliche Geschichte:

Im November 2005 bedauerte Yukihiro Matsumoto, der Erfinder von Ruby, diese Schleifenfunktion und schlug vor, die Kernel # -Schleife zu verwenden.

Siwei Shen 申思维
quelle
18
Genau richtig. Diese begin end whileMethode schien nicht richtig zu sein. Danke, dass du mir das Futter gegeben hast, das ich brauchte, um den Rest des Teams zu überzeugen.
Joshua Pinter
2
Es sieht so aus, als würde die Anfang-Ende-während-Schleife die Bedingung tatsächlich auswerten, bevor die Schleife ausgeführt wird. Der Unterschied zwischen dieser und einer regulären while-Schleife besteht darin, dass sie garantiert mindestens einmal ausgeführt wird. Es ist gerade nah genug, um Probleme zu verursachen.
user1992284
4
Soweit ich gut verstanden habe, wird begin-end-while "bedauert", weil es gegen die Semantik von Modifikatoren verstößt, dh: Sie werden vor dem Ausführen des Blocks überprüft, zum Beispiel: setzt k if! K.nil?. Hier ist 'if' ein 'Modifikator': Es wird VORHER überprüft, um festzustellen, ob die 'put k'-Anweisung ausgeführt wird. Dies ist nicht der Fall bei while / till-Schleifen (wenn sie als Modifikatoren eines Anfang-Ende-Blocks verwendet werden) !), werden NACH der ersten Hinrichtung ausgewertet. Vielleicht hat dies das Bedauern verursacht, aber haben wir etwas "Stärkeres" als einen alten Forumsbeitrag, eine Art offizielle Abwertung, wie sie früher bei vielen anderen Gelegenheiten war?
AgostinoX
2
Ich brauchte eine peinliche Zeit, um herauszufinden, warum meine Verwendung dieser rubinroten "Do-While" -Schleife nicht funktionierte. Sie sollten "es sei denn" verwenden, um eine Pause im C-Stil genauer nachzuahmen. Andernfalls könnten Sie wie ich enden und vergessen, die Bedingung umzukehren: p
Connor Clark
1
@ James Laut der verlinkten Mail sagte er, er "bereue" das Hinzufügen. Manchmal machen Menschen Fehler, auch wenn sie Sprachdesigner sind.
Xiong Chiamiov
188

Beim Lesen der Quelle Tempfile#initializein der Ruby- Kernbibliothek habe ich den folgenden Ausschnitt gefunden :

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)

Auf den ersten Blick ging ich davon aus, dass der Modifikator while vor dem Inhalt von begin ... end ausgewertet wird, aber das ist nicht der Fall. Beobachten:

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

Wie zu erwarten, wird die Schleife weiterhin ausgeführt, solange der Modifikator wahr ist.

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

Ich würde mich freuen, diese Redewendung nie wieder zu sehen, aber Anfang ... Ende ist ziemlich mächtig. Das Folgende ist eine gebräuchliche Redewendung, um eine Einzeilermethode ohne Parameter auswendig zu lernen:

def expensive
  @expensive ||= 2 + 2
end

Hier ist eine hässliche, aber schnelle Möglichkeit, sich etwas Komplexeres zu merken:

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

Ursprünglich geschrieben von Jeremy Voorhis . Der Inhalt wurde hier kopiert, da er anscheinend von der ursprünglichen Website entfernt wurde. Kopien finden Sie auch im Webarchiv und im Ruby Buzz Forum . - Rechnung die Eidechse

Hubbardr
quelle
5
Mit freundlicher Genehmigung der Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.com/…
bta
56
Aus diesem Grund stelle ich beim Verknüpfen mit einer externen Website immer sicher, dass die relevanten Informationen in meine Antwort hier kopiert werden.
Davr
10
Warum sollte der while-Modifikator vor dem Inhalt von begin ... end ausgewertet werden? Das ist der Weg ... während Schleifen funktionieren sollen. Und warum würden Sie "glücklich sein, diese Redewendung nie wieder zu sehen?" Was stimmt damit nicht? Ich bin verwirrt.
bergie3000
2
begin ... end sieht aus wie ein Block, ähnlich {...}. Daran ist nichts auszusetzen.
Victor Pudeyev
1
-1: Siwei Shens Antwort erklärt, dass dies begin .. endetwas verpönt ist. Verwenden Sie loop do .. break if <condition>stattdessen.
Kevin
102

So was:

people = []

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

Referenz: Ruby's Hidden do {} while () Schleife

Blorgbeard ist raus
quelle
1
heh, geschlagen. Verdammt.
Blorgbeard ist
Fügt dieser Code dem Array keine leere Zeichenfolge hinzu, wenn keine Eingabe erfolgt?
AndrewR
1
Obwohl dies hier nicht zutrifft, besteht ein Problem des Konstrukts begin-end-while darin, dass es im Gegensatz zu allen anderen Ruby-Konstruktionen nicht den Wert des letzten Ausdrucks zurückgibt: "begin 1 end while false" gibt nil (nicht 1, nicht falsch)
tokland
9
Sie können until info.empty?eher als verwenden while not info.empty?.
Andrew Grimm
Eigentlich @AndrewR, das ist eine Art Punkt .. Dinge vor einem Vergleich zu tun .. do diplay ("verbleibend = # {count}) während (count> 0) ... ich bekomme eine Anzeige von" verbleibend = 0 ".. perfekt!
baash05
45

Wie wäre es damit?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end
AndrewR
quelle
4
Schön, viel eleganter.
Blorgbeard ist
23
Dies ist jedoch keine "do ... while" -Schleife. :)
Alexander Prokofyev
4
Aber in diesem Fall macht es dasselbe, es sei denn, ich irre mich
Blorgbeard ist
18
@Blorgbeard, eine do..while-Schleife wird immer einmal ausgeführt und dann ausgewertet, um festzustellen, ob sie weiter ausgeführt werden soll. Eine herkömmliche while / till-Schleife kann 0 Mal ausgeführt werden. Es ist kein RIESIGER Unterschied, aber sie sind unterschiedlich.
Scott Swezey
5
@Scott, das stimmt - ich habe nur gemeint, dass dieser Code den OPs entspricht, obwohl er kein do / while verwendet. Obwohl dieser Code wirklich die Hälfte der "Arbeit" der Schleife in der Bedingung erledigt, ist es auch keine traditionelle while-Schleife - wenn die Bedingung nicht übereinstimmt, ist noch etwas Arbeit erledigt.
Blorgbeard ist am
11

Hier ist der Volltextartikel von Hubbardrs totem Link zu meinem Blog.

Beim Lesen der Quelle Tempfile#initializein der Ruby- Kernbibliothek habe ich den folgenden Ausschnitt gefunden :

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)

Auf den ersten Blick habe ich angenommen, dass der whileModifikator vor dem Inhalt von ausgewertet wird begin...end, aber das ist nicht der Fall. Beobachten:

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

Wie zu erwarten, wird die Schleife weiterhin ausgeführt, solange der Modifikator wahr ist.

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

Ich würde mich freuen, diese Redewendung nie wieder zu sehen, begin...endist aber ziemlich mächtig. Das Folgende ist eine gebräuchliche Redewendung, um eine Einzeilermethode ohne Parameter auswendig zu lernen:

def expensive
  @expensive ||= 2 + 2
end

Hier ist eine hässliche, aber schnelle Möglichkeit, sich etwas Komplexeres zu merken:

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

Das funktioniert jetzt richtig:

begin
    # statment
end until <condition>

Es kann jedoch in Zukunft entfernt werden, da die beginAussage nicht intuitiv ist. Siehe: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (Ruby's Creator) empfahl dies folgendermaßen:

loop do
    # ...
    break if <condition>
end
Steely Wing
quelle
6

Soweit ich weiß, mag Matz das Konstrukt nicht

begin
    <multiple_lines_of_code>
end while <cond>

weil seine Semantik anders ist als

<single_line_of_code> while <cond>

, dass das erste Konstrukt den Code zuerst ausführt, bevor die Bedingung überprüft wird, und das zweite Konstrukt die Bedingung zuerst testet, bevor es den Code ausführt (falls jemals). Ich nehme an, Matz zieht es vor, das zweite Konstrukt beizubehalten, da es mit einem Zeilenkonstrukt von if-Anweisungen übereinstimmt.

Ich habe das zweite Konstrukt selbst für if-Anweisungen nie gemocht. In allen anderen Fällen führt der Computer den Code von links nach rechts (z. B. || und &&) von oben nach unten aus. Menschen lesen den Code von links nach rechts von oben nach unten.

Ich schlage stattdessen die folgenden Konstrukte vor:

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

Ich weiß nicht, ob diese Vorschläge mit dem Rest der Sprache übereinstimmen. Aber auf jeden Fall bevorzuge ich die Ausführung von links nach rechts sowie die Sprachkonsistenz.

jds
quelle
3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end
Paul Gillard
quelle
3
das sieht ein bisschen aus wie ein goto. Der Code verfälscht Ihre Absicht.
Gerhard
Sieht toll aus für mich, while truekann aber durch ersetzt werden loop do.
David Winiecki
@ DavidWiniecki kann in der Tat while truedurch ersetzt werden loop do. Aber ich habe beide Konstrukte mit vielen Iterationen innerhalb der Schleife getestet und festgestellt, dass dies while truemindestens 2x schneller ist als loop do. Kann den Unterschied nicht erklären, aber es ist definitiv da. (Entdeckt beim Testen von Advent of Code 2017, Tag 15.)
Jochem Schulenklopper
2

Hier ist ein anderes:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end
Moray
quelle
Ich bevorzuge dies, da das ganz unlessvorne ist und ich nicht eine Menge Code durchlese (der mehr sein könnte als hier gezeigt), nur um unlessam Ende ein "Baumeln" zu finden . Es ist ein allgemeines Prinzip im Code, dass Modifikatoren und Bedingungen einfacher zu verwenden sind, wenn sie so "vorne" sind.
Michael Durrant
Ich wünschte manchmal, wir Programmierer müssten für jeden zusätzlichen Vergleich bar bezahlen. Und das, wie es für uns "aussieht", war weniger wichtig als das, wie es für das Ding aussieht, das es millionenfach am Tag benutzt.
Baash05
-3
ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
ist das in Ordnung
quelle
2
das sieht ein bisschen aus wie ein goto. Der Code verfälscht Ihre Absicht und sieht sehr unruby aus.
Gerhard
verschleiern * nicht verschleiern.
Qedk