มี "ทำ ... ในขณะที่" ห่วงในทับทิม?


454

ฉันใช้รหัสนี้เพื่อให้ผู้ใช้ป้อนชื่อในขณะที่โปรแกรมเก็บไว้ในอาร์เรย์จนกว่าพวกเขาจะใส่สตริงว่าง (พวกเขาต้องกด Enter หลังจากแต่ละชื่อ):

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

รหัสนี้จะดูดีกว่ามากในการทำ ... ในขณะที่วง:

people = []

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

ในรหัสนี้ฉันไม่ต้องกำหนดข้อมูลให้กับสตริงแบบสุ่ม

น่าเสียดายที่การวนซ้ำประเภทนี้ไม่มีอยู่ใน Ruby ใครช่วยแนะนำวิธีที่ดีกว่าในการทำเช่นนี้?


1
ฉันคิดว่าปกติในขณะที่วงดูดีกว่าและง่ายต่อการอ่าน
Magne

1
@Jeremy Ruten มีโอกาสใดที่คุณจะสนใจเปลี่ยนคำตอบที่ยอมรับกับคำตอบของ Siwei Shen loop do; ...; break if ...; end?
David Winiecki

คำตอบ:


645

ข้อควรระวัง :

begin <code> end while <condition>ถูกปฏิเสธโดยผู้เขียนของรูบี้แมทซ์ เขาแนะนำให้ใช้แทนKernel#loopเช่น

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

นี่คือการแลกเปลี่ยนทางอีเมลในวันที่ 23 พ.ย. 2548 ที่รัฐ Matz:

|> 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มีเรื่องราวที่คล้ายกัน:

ในช่วงเดือนพฤศจิกายน 2548 ยูกิฮิโระมัตสึโมโต้ผู้สร้างทับทิมรู้สึกเสียใจกับคุณลักษณะนี้และแนะนำให้ใช้ Kernel # loop


18
จับได้เห็นชัดตรงเผง. begin end whileวิธีนี้ดูไม่ถูกต้อง ขอบคุณที่ให้อาหารสัตว์ฉันต้องโน้มน้าวให้คนอื่นในทีม
Joshua Pinter

2
ดูเหมือนว่าลูปเริ่มต้น - สิ้นสุดขณะที่กำลังประเมินเงื่อนไขก่อนที่จะรันลูป ความแตกต่างระหว่างสิ่งนั้นกับแบบปกติขณะที่ลูปคือมันรับประกันว่าจะทำงานอย่างน้อยหนึ่งครั้ง มันใกล้พอที่จะทำ ... ในขณะที่ทำให้เกิดปัญหา
user1992284

4
ดังนั้นเท่าที่ฉันเข้าใจดีเริ่มต้น - ขณะที่ "เสียใจ" เพราะละเมิดความหมายของโมดิฟายเออร์นั่นคือพวกเขาจะถูกตรวจสอบก่อนดำเนินการบล็อกเช่น: ใส่ k ถ้า! k.nil? ที่นี่ 'if' เป็น 'ตัวปรับแต่ง': จะถูกตรวจสอบก่อนเพื่อตรวจสอบว่าการดำเนินการคำสั่ง 'Put k' หรือไม่นั้นเป็นกรณีของ while / จนกระทั่งลูปนั้น (เมื่อใช้เป็นตัวดัดแปลงของบล็อกเริ่มต้น !) ได้รับการประเมินหลังจากการดำเนินการครั้งแรกอาจทำให้เกิดความเสียใจ แต่เรามีบางสิ่งที่ 'แข็งแกร่ง' กว่าโพสต์ในฟอรัมเก่าเรียงลำดับการคัดค้านอย่างเป็นทางการเหมือนที่เคยเป็นในครั้งอื่น ๆ ไหม?
AgostinoX

2
ฉันใช้เวลาพอสมควรในการคิดออกว่าทำไมการใช้ลูป 'do-while' ของ ruby ​​นี้จึงไม่ทำงาน คุณควรใช้ 'เว้นแต่' เพื่อเลียนแบบ c-style อย่างใกล้ชิดมากขึ้นมิฉะนั้นคุณอาจจะจบลงเหมือนฉันและลืมที่จะกลับสภาพ: p
Connor Clark

1
@ James ตามอีเมลที่เชื่อมโยงเขาบอกว่าเขา "เสียใจ" ที่เพิ่มเข้ามา บางครั้งผู้คนทำผิดพลาดแม้ว่าพวกเขาจะเป็นนักออกแบบภาษา
Xiong Chiamiov

188

ฉันพบข้อมูลต่อไปนี้ขณะอ่านแหล่งข้อมูลTempfile#initializeในไลบรารี Ruby core:

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)

เมื่อมองแวบแรกฉันคิดว่าโมเดอเรเตอร์ในขณะนั้นจะได้รับการประเมินก่อนเนื้อหาเริ่มต้น ... สิ้นสุด แต่นั่นไม่ใช่กรณี สังเกต:

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

ตามที่คุณคาดไว้การวนซ้ำจะดำเนินการต่อในขณะที่โมดิฟายเออร์เป็นจริง

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

ในขณะที่ฉันมีความสุขที่ไม่เคยเห็นสำนวนนี้อีกเริ่มต้น ... จุดจบค่อนข้างทรงพลัง ต่อไปนี้เป็นสำนวนทั่วไปในการบันทึกวิธีหนึ่งซับโดยไม่มีพารามิเตอร์:

def expensive
  @expensive ||= 2 + 2
end

นี่เป็นวิธีที่น่าเกลียด แต่อย่างรวดเร็วเพื่อบันทึกสิ่งที่ซับซ้อนมากขึ้น:

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

เขียนโดยเจเรมี Voorhis เนื้อหาถูกคัดลอกมาที่นี่เพราะดูเหมือนว่าจะถูกลบออกจากเว็บไซต์ต้นทาง สำเนานอกจากนี้ยังสามารถพบได้ในเว็บ Archiveและทับทิมของ Buzz ฟอรั่ม - เรียกเก็บเงินจิ้งจก


5
ความอนุเคราะห์จาก Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.com/…
bta

56
นี่คือเหตุผลที่เมื่อเชื่อมโยงไปยังเว็บไซต์ภายนอกฉันมักจะคัดลอกข้อมูลที่เกี่ยวข้องลงในคำตอบของฉันที่นี่
davr

10
ทำไมคุณถึงคาดว่าจะมีการประเมินโมเดอเรเตอร์ในช่วงก่อนเนื้อหาของเริ่ม ... สิ้นสุด นั่นเป็นวิธีที่ .. ในขณะที่ลูปควรทำงาน และทำไมคุณถึง "มีความสุขที่ไม่เคยเห็นสำนวนนี้อีก" มีอะไรผิดปกติกับมัน? ฉันสับสน
bergie3000

2
เริ่ม ... จุดสิ้นสุดดูเหมือนว่าบล็อกคล้ายกัน {... } ไม่มีอะไรผิดปกติกับมัน
Victor Pudeyev

1
-1: คำตอบของ Siwei Shen อธิบายว่าbegin .. endค่อนข้างขมวดคิ้ว ใช้loop do .. break if <condition>แทน
เควิน

102

แบบนี้:

people = []

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

การอ้างอิง: Ruby's Hidden do {} while () Loop


1
เฮ้แพ้มัน ประณาม.
Blorgbeard ออก

รหัสนี้จะไม่เพิ่มสตริงว่างในอาร์เรย์หรือไม่หากไม่มีอินพุต
AndrewR

1
แม้ว่ามันจะไม่ได้ใช้ที่นี่ แต่ปัญหาหนึ่งของการเริ่มต้น - จบ - ขณะที่การสร้างคือไม่เหมือนกับการก่อสร้างทับทิมอื่น ๆ ทั้งหมด แต่จะไม่คืนค่าของนิพจน์สุดท้าย: "เริ่มต้น 1 สิ้นสุดในขณะที่เป็นเท็จ" ส่งคืนศูนย์ ไม่ใช่เท็จ)
tokland

9
คุณสามารถใช้มากกว่าuntil info.empty? while not info.empty?
Andrew Grimm

จริง ๆ แล้ว @AndrewR นั่นเป็นจุดสำคัญ .. เพื่อทำสิ่งต่าง ๆ ก่อนที่จะทำการเปรียบเทียบ .. do diplay ("left = # {count}) ขณะที่ (count> 0) ... ฉันได้รับการแสดง" left = 0 ".. สมบูรณ์แบบ!
baash05

45

แล้วเรื่องนี้ล่ะ

people = []

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

4
ดีมากหรูหรามาก
Blorgbeard ออก

23
แต่นี่ไม่ใช่ "ทำ ... ขณะ" วนซ้ำ :)
Alexander Prokofyev

4
แต่มันก็ทำสิ่งเดียวกันในกรณีนี้ถ้าฉันเข้าใจผิด
Blorgbeard ออกมา

18
@Blorgbeard สิ่งที่ต้องทำ .. ขณะที่วนซ้ำจะทำงานครั้งเดียวเสมอจากนั้นประเมินเพื่อดูว่าควรจะทำงานต่อไปหรือไม่ แบบดั้งเดิม while / จนกว่า loop สามารถเรียกใช้ 0 ครั้ง มันไม่แตกต่างกันมาก แต่ก็แตกต่างกัน
Scott Swezey

5
@Scott นั่นเป็นจริง - ฉันแค่หมายความว่ารหัสนี้เทียบเท่ากับ OP ถึงแม้ว่ามันจะไม่ใช้สิ่งที่ต้องทำ / ในขณะที่ ถึงแม้ว่าจริงๆแล้วรหัสนี้จะทำงานครึ่งหนึ่งของ "ลูป" ในสภาพดังนั้นจึงไม่ใช่แบบดั้งเดิมในขณะที่วนรอบ - ถ้าเงื่อนไขไม่ตรงกันงานบางอย่างก็ยังคงทำอยู่
Blorgbeard ออก

11

นี่คือบทความข้อความเต็มจากลิงก์ตายของ hubbardr ไปยังบล็อกของฉัน

ฉันพบข้อมูลต่อไปนี้ขณะอ่านแหล่งข้อมูลTempfile#initializeในไลบรารี Ruby core:

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)

เมื่อมองแวบแรกฉันคิดว่าwhileตัวดัดแปลงจะได้รับการประเมินก่อนเนื้อหาของbegin...endแต่นั่นไม่ใช่กรณี สังเกต:

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

ตามที่คุณคาดไว้การวนซ้ำจะดำเนินการต่อในขณะที่โมดิฟายเออร์เป็นจริง

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

ในขณะที่ฉันจะมีความสุขที่ไม่เคยเห็นสำนวนนี้อีกครั้งbegin...endมีประสิทธิภาพมาก ต่อไปนี้เป็นสำนวนทั่วไปในการบันทึกวิธีหนึ่งซับโดยไม่มีพารามิเตอร์:

def expensive
  @expensive ||= 2 + 2
end

นี่เป็นวิธีที่น่าเกลียด แต่อย่างรวดเร็วเพื่อบันทึกสิ่งที่ซับซ้อนมากขึ้น:

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

10

วิธีนี้ทำงานได้อย่างถูกต้องแล้ว:

begin
    # statment
end until <condition>

แต่มันอาจถูกลบออกในอนาคตเนื่องจากbeginคำสั่งนั้นขัดกับความเป็นจริง ดู: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (Ruby's Creator) แนะนำให้ทำเช่นนี้:

loop do
    # ...
    break if <condition>
end

6

จากสิ่งที่ฉันรวบรวมมา Matz ไม่ชอบสิ่งก่อสร้าง

begin
    <multiple_lines_of_code>
end while <cond>

เพราะมันมีความหมายแตกต่างจาก

<single_line_of_code> while <cond>

ในการสร้างครั้งแรกรันรหัสก่อนที่จะตรวจสอบสภาพและการสร้างครั้งที่สองทดสอบเงื่อนไขก่อนที่จะรันรหัส (ถ้าเคย) ฉันคิดว่า Matz ชอบเก็บโครงสร้างที่สองไว้เพราะมันตรงกับคำสั่ง if บรรทัดเดียว

ฉันไม่เคยชอบโครงสร้างที่สองแม้ว่าจะเป็นข้อความสั่ง ในกรณีอื่น ๆ คอมพิวเตอร์เรียกใช้โค้ดจากซ้ายไปขวา (เช่น | | และ &&) จากบนลงล่าง มนุษย์อ่านโค้ดจากซ้ายไปขวาจากบนลงล่าง

ฉันขอแนะนำโครงสร้างต่อไปนี้แทน:

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

ฉันไม่รู้ว่าคำแนะนำเหล่านั้นจะแยกกับส่วนที่เหลือของภาษา แต่ในกรณีใด ๆ ฉันจะทำการประมวลผลจากซ้ายไปขวาและความสอดคล้องทางภาษาล่วงหน้า


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

3
ดูเหมือนว่าจะเป็น goto รหัสทำให้เข้าใจผิดเจตนาของคุณ
แกร์ฮาร์ด

ดูดีกับผมยกเว้นสามารถถูกแทนที่ด้วยwhile true loop do
David Winiecki

@DavidWiniecki แน่นอนสามารถถูกแทนที่ด้วยwhile true loop doแต่ผมทดสอบโครงสร้างทั้งสองมีจำนวนมากของการทำซ้ำภายในวงและพบว่าwhile trueอย่างน้อย 2x ได้เร็วloop doกว่า ไม่สามารถอธิบายความแตกต่างได้ แต่มีอยู่แน่นอน (ค้นพบขณะทดสอบการถือกำเนิดของรหัส 2017 วันที่ 15)
Jochem Schulenklopper

2

นี่คืออีกหนึ่ง:

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

ฉันชอบสิ่งนี้เนื่องจากunlessอยู่ตรงหน้าและฉันไม่ได้อ่านรหัส (ซึ่งอาจมากกว่าที่แสดงที่นี่) เพื่อค้นหา 'ห้อย' unlessที่ท้ายที่สุดเท่านั้น มันเป็นหลักการทั่วไปในรหัสที่โมดิฟายเออร์และเงื่อนไขจะง่ายต่อการใช้งานเมื่อพวกมัน 'อยู่ตรงหน้า' เช่นนี้
Michael Durrant

บางครั้งฉันหวังว่าเราจะต้องจ่ายเงินเป็นเงินสดสำหรับทุก ๆ การเปรียบเทียบพิเศษ และวิธีการที่ "ดู" สำหรับเรานั้นมีความสำคัญน้อยกว่ารูปลักษณ์ที่ใช้กับวันละล้านครั้ง
baash05

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

2
ดูเหมือนว่าจะเป็น goto รหัสทำให้เข้าใจผิดความตั้งใจของคุณและดูไม่น่าเชื่อถือเหมือน
แกร์ฮาร์ด

ทำให้งงงวย * ไม่ทำให้งง
qedk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.