Czy w Ruby jest pętla „do… while”?

452

Używam tego kodu, aby pozwolić użytkownikowi na wprowadzanie nazw, podczas gdy program przechowuje je w tablicy, dopóki nie wprowadzi pustego ciągu (muszą nacisnąć Enter po każdej nazwie):

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

Ten kod wyglądałby znacznie ładniej w pętli do ... while:

people = []

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

W tym kodzie nie muszę przypisywać informacji do jakiegoś losowego ciągu.

Niestety ten typ pętli nie istnieje w Ruby. Czy ktoś może zaproponować lepszy sposób na zrobienie tego?

Jeremy Ruten
źródło
1
Myślę, że normalna pętla while wygląda ładniej i jest łatwiejsza do odczytania.
Magne,
1
@Jeremy Ruten czy jest jakaś szansa, że ​​byłbyś zainteresowany zmianą zaakceptowanej odpowiedzi na odpowiedź Siwei Shen loop do; ...; break if ...; end?
David Winiecki

Odpowiedzi:

643

UWAGA :

Został begin <code> end while <condition>odrzucony przez autora Ruby, Matza. Zamiast tego sugeruje użycie Kernel#loopnp

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

Oto wymiana wiadomości e-mail z 23 listopada 2005 r., W której Matz stwierdza:

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

Wiki RosettaCode ma podobną historię:

W listopadzie 2005 roku Yukihiro Matsumoto, twórca Ruby, żałował tej funkcji pętli i zaproponował użycie pętli Kernel #.

Siwei Shen 申思维
źródło
18
Spot on. Ta begin end whilemetoda wydawała się niewłaściwa. Dzięki za dostarczenie mi paszy, której potrzebowałem, aby przekonać resztę zespołu.
Joshua Pinter
2
Wygląda na to, że pętla początkowa-końcowa faktycznie ocenia warunek przed uruchomieniem pętli. Różnica między tym a zwykłą pętlą while polega na tym, że gwarantuje uruchomienie przynajmniej raz. Jest wystarczająco blisko, aby zrobić ... a jednocześnie spowodować problemy.
user1992284
4
Tak więc, o ile dobrze rozumiem, początek-koniec jest „żałowany”, ponieważ narusza semantykę modyfikatorów, to znaczy: są sprawdzane przed wykonaniem bloku, na przykład: wstawia k, jeśli! K.nil ?. Tutaj „if” jest „modyfikatorem”: jest sprawdzane PRZED, w celu ustalenia, czy wykonanie instrukcji „puts k” nie jest tak w przypadku pętli while / till (gdy są stosowane jako modyfikatory bloku początku-końca !), są oceniane PO pierwszej egzekucji. Może to spowodowało żal, ale czy mamy coś „silniejszego” niż stary post na forum, coś w rodzaju oficjalnej deprecjacji, jakiej używa się przy wielu innych okazjach?
AgostinoX
2
Zajęło mi kłopotliwie dużo czasu, aby zrozumieć, dlaczego moje użycie tej rubinowej pętli „do-while” nie działa. Powinieneś użyć „chyba”, aby ściślej naśladować tryb „c”, w przeciwnym razie możesz skończyć jak ja i zapomnieć o odwróceniu warunku: p
Connor Clark,
1
@James Jak na link do niego, powiedział, że „żałuje” dodania go. Czasami ludzie popełniają błędy, nawet jeśli są projektantami języków.
Xiong Chiamiov
188

Podczas czytania źródła Tempfile#initializew bibliotece Ruby core znalazłem następujący fragment kodu :

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)

Na pierwszy rzut oka zakładałem, że modyfikator while zostanie oceniony przed zawartością początku ... końca, ale tak nie jest. Przestrzegać:

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

Jak można się spodziewać, pętla będzie nadal działać, dopóki modyfikator będzie prawdziwy.

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

Chociaż byłbym szczęśliwy, że nigdy więcej nie zobaczę tego idiomu, początek ... koniec jest dość potężny. Poniższy typowy idiom zapamiętuje metodę jednowierszową bez parametrów:

def expensive
  @expensive ||= 2 + 2
end

Oto brzydki, ale szybki sposób na zapamiętanie czegoś bardziej złożonego:

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

Pierwotnie napisany przez Jeremy Voorhis . Treść została tutaj skopiowana, ponieważ wygląda na to, że została usunięta z witryny źródłowej. Kopie można również znaleźć w Archiwum internetowym i na forum Ruby Buzz . -Bill Jaszczurka

hubbardr
źródło
5
Dzięki uprzejmości Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.com/…
bta
56
Dlatego podczas linkowania do strony zewnętrznej zawsze staram się skopiować odpowiednie informacje do mojej odpowiedzi tutaj.
davr
10
Dlaczego miałbyś oczekiwać, że modyfikator while zostanie oceniony przed treścią początku ... końca? Tak to działa ... podczas gdy pętle powinny działać. I dlaczego miałbyś „być szczęśliwy, że nigdy więcej nie zobaczysz tego idiomu?” Co jest z tym nie tak? Jestem zmieszany.
bergie3000
2
początek ... koniec wygląda jak blok, podobnie {...}. Nie ma w tym nic złego.
Victor Pudeyev,
1
-1: Odpowiedź Siwei Shena wyjaśnia, że begin .. endjest to trochę rozczarowane. Użyj loop do .. break if <condition>zamiast tego.
Kevin
102

Lubię to:

people = []

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

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

Blorgbeard wyszedł
źródło
1
heh, pobity. cholera.
Blorgbeard wyszedł
Czy ten kod nie doda pustego ciągu do tablicy, jeśli nie ma danych wejściowych?
AndrewR
1
Chociaż nie ma tu zastosowania, jednym z problemów konstrukcji typu początek-koniec jest to, że w przeciwieństwie do wszystkich innych konstrukcji ruby, nie zwraca wartości ostatniego wyrażenia: „początek 1 końca, a fałsz” zwraca zero (nie 1, nie fałsz)
tokland
9
Możesz użyć until info.empty?zamiast while not info.empty?.
Andrew Grimm,
Właściwie @AndrewR to taki punkt .. robienie rzeczy przed porównaniem .. do diplay ("pozostały = # {liczba}) podczas gdy (liczba> 0) ... Dostaję wyświetlanie" pozostającego = 0 ".. idealne!
baash05
45

Co powiesz na to?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end
AndrewR
źródło
4
Ładne, znacznie bardziej eleganckie.
Blorgbeard jest nieobecny
23
Ale to nie jest pętla „rób ... póki”. :)
Alexander Prokofyev
4
Ale robi to samo w tym przypadku, chyba że się mylę
Blorgbeard wyszedł
18
@Blorgbeard, pętla do..while zawsze działa raz, a następnie ocenia, czy powinna nadal działać. Tradycyjna pętla while / till może działać 0 razy. To nie jest OGROMNA różnica, ale są różne.
Scott Swezey,
5
@ Scott, to prawda - miałem na myśli, że ten kod jest równoważny OP, nawet jeśli nie używa polecenia / do. Chociaż tak naprawdę ten kod wykonuje połowę „pracy” pętli w warunku, więc nie jest to również tradycyjna pętla while - jeśli warunek nie jest zgodny, część pracy jest nadal wykonywana.
Blorgbeard jest poza
11

Oto pełny artykuł z martwego linku Hubbardra do mojego bloga.

Podczas czytania źródła Tempfile#initializew bibliotece Ruby core znalazłem następujący fragment kodu :

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)

Na pierwszy rzut oka założyłem, że whilemodyfikator będzie oceniany przed zawartością begin...end, ale tak nie jest. Przestrzegać:

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

Jak można się spodziewać, pętla będzie nadal działać, dopóki modyfikator będzie prawdziwy.

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

Chociaż byłbym szczęśliwy, że nigdy więcej nie zobaczę tego idiomu, begin...endjest dość potężny. Poniższy typowy idiom zapamiętuje metodę jednowierszową bez parametrów:

def expensive
  @expensive ||= 2 + 2
end

Oto brzydki, ale szybki sposób na zapamiętanie czegoś bardziej złożonego:

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
źródło
10

Działa to teraz poprawnie:

begin
    # statment
end until <condition>

Ale może zostać usunięty w przyszłości, ponieważ beginoświadczenie jest sprzeczne z intuicją. Widzieć: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (Ruby's Creator) zalecił zrobienie tego w ten sposób:

loop do
    # ...
    break if <condition>
end
Steely Wing
źródło
6

Z tego, co zbieram, Matz nie lubi konstrukcji

begin
    <multiple_lines_of_code>
end while <cond>

ponieważ jego semantyka jest inna niż

<single_line_of_code> while <cond>

w tym, że pierwsza konstrukcja najpierw wykonuje kod przed sprawdzeniem warunku, a druga konstrukcja najpierw sprawdza warunek, zanim wykona kod (jeśli w ogóle). Rozumiem, że Matz woli zachować drugą konstrukcję, ponieważ pasuje ona do konstrukcji liniowej instrukcji if.

Nigdy nie podobał mi się drugi konstrukt, nawet jeśli instrukcje. We wszystkich innych przypadkach komputer wykonuje kod od lewej do prawej (np. || i &&) od góry do dołu. Ludzie czytają kod od lewej do prawej od góry do dołu.

Zamiast tego sugeruję następujące konstrukcje:

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

Nie wiem, czy te sugestie zostaną przeanalizowane z resztą języka. Ale w każdym razie wolę zachować wykonanie od lewej do prawej, a także spójność językową.

jds
źródło
3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end
Paul Gillard
źródło
3
to wygląda trochę jak goto. Kod zaciemnia twój zamiar.
Gerhard
Wygląda mi świetnie, tyle że while truemożna go zastąpić loop do.
David Winiecki
@DavidWiniecki rzeczywiście while truemożna zastąpić loop do. Ale przetestowałem obie konstrukcje z dużą ilością iteracji w pętli i odkryłem, że while truejest to co najmniej 2x szybsza niż loop do. Nie potrafię wyjaśnić różnicy, ale na pewno jest. (Odkryte podczas testowania Advent of Code 2017, dzień 15)
Jochem Schulenklopper
2

Oto kolejny:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end
Moray
źródło
Wolę to, ponieważ unlessjest na samym początku i nie czytam kawałków kodu (które mogą być więcej niż pokazane tutaj) tylko po to, aby znaleźć „wiszące” unlessna końcu. Jest ogólną zasadą w kodzie, że modyfikator i warunki są łatwiejsze w użyciu, gdy są „z góry” w ten sposób.
Michael Durrant
Czasami żałuję, że my, koderzy, nie musieliśmy płacić gotówką za każde dodatkowe porównanie. I to, jak to dla nas „wygląda”, było mniej ważne niż to, jak wygląda rzecz, która korzysta z niej milion razy dziennie.
baash05
-3
ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
czy to jest ok
źródło
2
to wygląda trochę jak goto. Kod zaciemnia twoją intencję i wygląda bardzo niechlujnie.
Gerhard
zaciemnij * nie zaciemnij.
qedk