Apakah ada loop “do… while” di Ruby?

453

Saya menggunakan kode ini untuk membiarkan pengguna memasukkan nama sementara program menyimpannya dalam array sampai mereka memasukkan string kosong (mereka harus menekan enter setelah setiap nama):

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

Kode ini akan terlihat jauh lebih bagus di do ... while:

people = []

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

Dalam kode ini saya tidak perlu menetapkan info ke beberapa string acak.

Sayangnya jenis loop ini tampaknya tidak ada di Ruby. Adakah yang bisa menyarankan cara yang lebih baik untuk melakukan ini?

Jeremy Ruten
sumber
1
Saya pikir loop sementara normal terlihat lebih bagus, dan lebih mudah dibaca.
Magne
1
@Jeremy Ruten, adakah kemungkinan Anda akan tertarik untuk mengubah jawaban yang diterima menjadi jawaban Siwei Shen loop do; ...; break if ...; end,?
David Winiecki

Jawaban:

644

PERHATIAN :

Itu begin <code> end while <condition>ditolak oleh penulis Ruby Matz. Sebaliknya ia menyarankan menggunakan Kernel#loop, misalnya

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

Inilah pertukaran email pada 23 November 2005 di mana Matz menyatakan:

|> 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 memiliki kisah serupa:

Selama November 2005, Yukihiro Matsumoto, pencipta Ruby, menyesali fitur loop ini dan menyarankan menggunakan loop Kernel #.

Siwei Shen 申思维
sumber
18
Spot on. Ini begin end whilemetode tampaknya tidak benar. Terima kasih telah memberi saya makanan ternak yang saya butuhkan untuk meyakinkan anggota tim lainnya.
Joshua Pinter
2
Sepertinya loop begin-end-while sebenarnya mengevaluasi kondisi sebelum menjalankan loop. Perbedaan antara itu dan loop sementara reguler adalah bahwa itu dijamin untuk berjalan setidaknya sekali. Itu cukup dekat untuk dilakukan ... sementara menyebabkan masalah.
user1992284
4
Jadi, sejauh yang saya mengerti dengan baik, permulaan-akhir "disesalkan" karena melanggar semantik pengubah, yaitu: mereka diperiksa sebelum mengeksekusi blok, misalnya: menempatkan k jika! K.nil ?. Di sini 'jika' adalah 'pengubah': itu diperiksa SEBELUM, untuk menentukan apakah mengeksekusi pernyataan 'menempatkan k'. Itu bukan kasus while / sampai loop itu (ketika digunakan sebagai pengubah blok awal-akhir-blok !), dievaluasi SETELAH eksekusi pertama. Mungkin ini yang menyebabkan penyesalan, tetapi apakah kita memiliki sesuatu yang 'lebih kuat' dari pada posting forum lama, semacam penghinaan resmi seperti dulu di banyak kesempatan lain?
AgostinoX
2
Butuh waktu yang memalukan bagi saya untuk mengetahui mengapa penggunaan loop 'do-while' ruby ​​ini tidak berfungsi. Anda harus menggunakan 'kecuali' untuk lebih dekat meniru gaya-c do-while, jika tidak, Anda mungkin berakhir seperti saya dan lupa untuk membalikkan kondisinya: p
Connor Clark
1
@ James Sesuai dengan surat yang ditautkan, dia berkata dia "menyesal" menambahkannya. Terkadang orang membuat kesalahan, bahkan jika mereka adalah perancang bahasa.
Xiong Chiamiov
188

Saya menemukan cuplikan berikut saat membaca sumber untuk Tempfile#initializedi pustaka inti 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)

Pada pandangan pertama, saya mengasumsikan pengubah sementara akan dievaluasi sebelum isi dari mulai ... akhir, tapi itu tidak terjadi. Mengamati:

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

Seperti yang Anda harapkan, loop akan terus dieksekusi ketika pengubah benar.

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

Sementara saya akan senang tidak pernah melihat idiom ini lagi, mulai ... akhir cukup kuat. Berikut ini adalah idiom umum untuk memoize metode one-liner tanpa params:

def expensive
  @expensive ||= 2 + 2
end

Berikut ini adalah cara yang jelek, tetapi cepat untuk menghafal sesuatu yang lebih rumit:

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

Awalnya ditulis oleh Jeremy Voorhis . Konten telah disalin di sini karena tampaknya telah diambil dari situs asal. Salinan juga dapat ditemukan di Web Archive dan di Ruby Buzz Forum . -Bill si Kadal

hubbardr
sumber
56
Inilah sebabnya mengapa saat menautkan ke situs eksternal, saya selalu memastikan untuk menyalin info yang relevan ke jawaban saya di sini.
davr
10
Mengapa Anda mengharapkan pengubah sementara akan dievaluasi sebelum konten awal ... akhir? Begitulah caranya. Sementara loop seharusnya bekerja. Dan mengapa Anda "senang tidak pernah melihat idiom ini lagi?" Apakah ada yang salah? Saya bingung.
bergie3000
2
begin ... end tampak seperti blok, juga {...}. Tidak ada yang salah dengan itu.
Victor Pudeyev
1
-1: Jawaban Siwei Shen menjelaskan itu begin .. endagak tidak disukai. Gunakan loop do .. break if <condition>sebagai gantinya.
Kevin
102

Seperti ini:

people = []

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

Referensi: Ruby Tersembunyi do {} while () Loop

Blorgbeard keluar
sumber
1
heh, kalahkan dia. Sial.
Blorgbeard keluar
Bukankah kode ini akan menambahkan string kosong ke array jika tidak ada input?
AndrewR
1
Meskipun tidak berlaku di sini, satu masalah konstruk begin-end-while adalah bahwa, tidak seperti semua konstruksi ruby ​​lainnya, itu tidak mengembalikan nilai dari ekspresi terakhir: "mulai 1 ujung sementara false" mengembalikan nihil (bukan 1, tidak salah)
tokland
9
Anda bisa menggunakan until info.empty?daripada while not info.empty?.
Andrew Grimm
Sebenarnya @AndrewR itu intinya .. untuk melakukan sesuatu sebelum membandingkan .. lakukan diplay ("tersisa = # {hitung}) sementara (hitung> 0) ... saya mendapatkan tampilan" tersisa = 0 ".. sempurna!
baash05
45

Bagaimana dengan ini?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end
AndrewR
sumber
4
Bagus, jauh lebih elegan.
Blorgbeard keluar
23
Tapi ini bukan loop "do ... while". :)
Alexander Prokofyev
4
Tapi itu melakukan hal yang sama dalam kasus ini, kecuali saya salah
Blorgbeard keluar
18
@ Blorgbeard, sebuah do.. while selalu berjalan sekali, kemudian mengevaluasi untuk melihat apakah itu harus terus berjalan. Perulangan while / hingga tradisional dapat berjalan 0 kali. Itu bukan perbedaan BESAR, tetapi mereka berbeda.
Scott Swezey
5
@ Esc, itu benar - saya hanya bermaksud bahwa kode ini setara dengan OP, meskipun tidak menggunakan do / while. Meskipun benar-benar, kode ini melakukan setengah dari "pekerjaan" loop dalam kondisi, jadi itu tidak cukup tradisional sementara loop baik - jika kondisinya tidak cocok, beberapa pekerjaan masih dilakukan.
Blorgbeard keluar
11

Ini artikel teks lengkap dari tautan mati hubbardr ke blog saya.

Saya menemukan cuplikan berikut saat membaca sumber untuk Tempfile#initializedi pustaka inti 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)

Pada pandangan pertama, saya berasumsi whilepengubah akan dievaluasi sebelum isinya begin...end, tapi itu tidak terjadi. Mengamati:

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

Seperti yang Anda harapkan, loop akan terus dieksekusi ketika pengubah benar.

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

Sementara saya akan senang tidak pernah melihat idiom ini lagi, begin...endcukup kuat. Berikut ini adalah idiom umum untuk memoize metode one-liner tanpa params:

def expensive
  @expensive ||= 2 + 2
end

Berikut ini adalah cara yang jelek, tetapi cepat untuk menghafal sesuatu yang lebih rumit:

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

Ini berfungsi dengan benar sekarang:

begin
    # statment
end until <condition>

Tapi, itu mungkin dihapus di masa depan, karena beginpernyataan itu berlawanan dengan intuisi. Lihat: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (Pencipta Ruby) merekomendasikan melakukannya dengan cara ini:

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

Dari apa yang saya kumpulkan, Matz tidak suka konstruk itu

begin
    <multiple_lines_of_code>
end while <cond>

karena, itu semantik berbeda dari

<single_line_of_code> while <cond>

karena konstruk pertama mengeksekusi kode terlebih dahulu sebelum memeriksa kondisi, dan konstruk kedua menguji kondisi terlebih dahulu sebelum mengeksekusi kode (jika pernah). Saya menganggapnya Matz lebih memilih untuk mempertahankan konstruk kedua karena cocok dengan konstruksi satu baris pernyataan if.

Saya tidak pernah menyukai konstruk kedua bahkan untuk pernyataan if. Dalam semua kasus lain, komputer menjalankan kode kiri-ke-kanan (mis. || dan &&) dari atas ke bawah. Manusia membaca kode dari kiri ke kanan dari atas ke bawah.

Saya menyarankan konstruksi berikut sebagai gantinya:

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

Saya tidak tahu apakah saran-saran itu akan diuraikan dengan seluruh bahasa. Tetapi bagaimanapun saya lebih suka menjaga eksekusi dari kiri ke kanan serta konsistensi bahasa.

jds
sumber
3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end
Paul Gillard
sumber
3
ini terlihat seperti goto. Kode mengaburkan niat Anda.
Gerhard
Terlihat bagus untuk saya, kecuali while truebisa diganti dengan loop do.
David Winiecki
@ Davidvidiniecki, memang, while truebisa diganti dengan loop do. Tapi saya menguji kedua konstruksi dengan banyak iterasi di dalam loop, dan menemukan bahwa while truesetidaknya 2x lebih cepat daripada loop do. Tidak dapat menjelaskan perbedaannya, tetapi pasti ada di sana. (Ditemukan saat menguji Advent of Code 2017, hari 15.)
Jochem Schulenklopper
2

Ini satu lagi:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end
Moray
sumber
Saya lebih suka ini karena unlesstepat di depan dan saya tidak membaca banyak kode (yang bisa lebih dari yang ditunjukkan di sini) hanya untuk menemukan 'menggantung' unlessdi akhir. Ini adalah prinsip umum dalam kode bahwa pengubah dan kondisi lebih mudah digunakan ketika mereka 'di depan' seperti ini.
Michael Durrant
Saya kadang-kadang berharap kita coders harus membayar tunai untuk setiap perbandingan tambahan. Dan bagaimana itu "terlihat" bagi kita adalah kurang penting daripada bagaimana itu terlihat pada hal yang menggunakannya sejuta kali sehari.
baash05
-3
ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
Apakah itu tidak apa apa
sumber
2
ini terlihat seperti goto. Kode mengaburkan niat Anda dan terlihat sangat tidak suka.
Gerhard
mengaburkan * tidak mengaburkan.
qedk