Có một bản do do lòng trong khi có vòng lặp trong Ruby?


452

Tôi đang sử dụng mã này để cho phép người dùng nhập tên trong khi chương trình lưu trữ chúng trong một mảng cho đến khi họ nhập một chuỗi trống (họ phải nhấn enter sau mỗi tên):

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

Mã này sẽ trông đẹp hơn nhiều trong một vòng lặp do ... while:

people = []

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

Trong mã này tôi không phải gán thông tin cho một số chuỗi ngẫu nhiên.

Thật không may, loại vòng lặp này dường như không tồn tại trong Ruby. Bất cứ ai có thể đề nghị một cách tốt hơn để làm điều này?


1
Tôi nghĩ rằng vòng lặp while bình thường trông đẹp hơn và dễ đọc hơn.
Magne

1
@Jeremy Ruten có bất kỳ cơ hội nào bạn sẽ quan tâm đến việc thay đổi câu trả lời được chấp nhận cho câu trả lời của Siwei Shen loop do; ...; break if ...; endkhông?
David Winiecki

Câu trả lời:


643

THẬN TRỌNG :

Các begin <code> end while <condition>bị từ chối bởi tác giả của Ruby Matz. Thay vào đó, ông đề nghị sử dụng Kernel#loop, ví dụ

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

Đây là một trao đổi email vào ngày 23 tháng 11 năm 2005 trong đó Matz tuyên bố:

|> 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 có một câu chuyện tương tự:

Trong tháng 11 năm 2005, Yukihiro Matsumoto, người tạo ra Ruby, đã hối hận về tính năng vòng lặp này và đề nghị sử dụng vòng lặp Kernel #.


18
Tại chỗ trên. Đây begin end whilephương pháp không có vẻ đúng. Cảm ơn đã cho tôi thức ăn gia súc tôi cần để thuyết phục phần còn lại của đội.
Joshua Pinter

2
Có vẻ như vòng lặp start-end-while thực sự đang đánh giá điều kiện trước khi chạy vòng lặp. Sự khác biệt giữa vòng lặp và vòng lặp thông thường là nó được đảm bảo chạy ít nhất một lần. Nó chỉ đủ gần để làm ... trong khi gây ra vấn đề.
user1992284

4
Vì vậy, theo như tôi đã hiểu rõ, từ đầu đến cuối là "hối tiếc" vì vi phạm ngữ nghĩa của các sửa đổi, nghĩa là: chúng được kiểm tra trước khi thực hiện khối, ví dụ: đặt k if! K.nil?. Ở đây 'if' là 'công cụ sửa đổi': nó được kiểm tra TRƯỚC, để xác định xem có thực hiện câu lệnh 'put k' hay không. Đó không phải là trường hợp trong khi / cho đến khi các vòng lặp (khi được sử dụng làm công cụ sửa đổi của khối bắt đầu !), được đánh giá SAU lần thực hiện đầu tiên. Có thể điều này gây ra sự hối tiếc, nhưng liệu chúng ta có gì đó 'mạnh mẽ' hơn một bài đăng trên diễn đàn cũ, loại bỏ sự phản đối chính thức như trong nhiều trường hợp khác không?
AgostinoX

2
Tôi đã mất một khoảng thời gian đáng xấu hổ để tìm ra lý do tại sao việc sử dụng vòng lặp 'do-while' của ruby ​​này không hoạt động. Bạn nên sử dụng 'trừ khi' để bắt chước chặt chẽ hơn một kiểu thời gian c, nếu không bạn có thể sẽ giống như tôi và quên đảo ngược điều kiện: p
Connor Clark

1
@James Theo thư được liên kết, anh ấy nói anh ấy "hối hận" khi thêm nó. Đôi khi mọi người mắc lỗi, ngay cả khi họ là nhà thiết kế ngôn ngữ.
Xiong Chiamiov

188

Tôi đã tìm thấy đoạn mã sau trong khi đọc nguồn Tempfile#initializetrong thư viện lõi 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)

Thoạt nhìn, tôi cho rằng công cụ sửa đổi while sẽ được đánh giá trước khi nội dung bắt đầu ... kết thúc, nhưng đó không phải là trường hợp. Quan sát:

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

Như bạn mong đợi, vòng lặp sẽ tiếp tục thực thi trong khi công cụ sửa đổi là đúng.

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

Mặc dù tôi sẽ rất vui khi không bao giờ gặp lại thành ngữ này nữa, bắt đầu ... kết thúc khá mạnh mẽ. Sau đây là một thành ngữ phổ biến để ghi nhớ phương thức một lớp không có tham số:

def expensive
  @expensive ||= 2 + 2
end

Đây là một cách xấu xí, nhưng nhanh chóng để ghi nhớ một cái gì đó phức tạp hơn:

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

Nguyên văn bởi Jeremy Voorhis . Nội dung đã được sao chép ở đây vì dường như nó đã bị gỡ xuống từ trang web ban đầu. Các bản sao cũng có thể được tìm thấy trong Lưu trữ web và tại Diễn đàn Ruby Buzz . -Làm con thằn lằn



56
Đây là lý do tại sao khi liên kết đến một trang web bên ngoài, tôi luôn đảm bảo sao chép thông tin liên quan vào câu trả lời của mình ở đây.
davr

10
Tại sao bạn muốn trình sửa đổi while được đánh giá trước khi nội dung bắt đầu ... kết thúc? Đó là cách làm .. trong khi các vòng lặp được cho là hoạt động. Và tại sao bạn lại "hạnh phúc khi không bao giờ gặp lại thành ngữ này nữa?" Có gì sai với nó? Tôi bối rối.
bergie3000

2
bắt đầu ... kết thúc trông giống như một khối, tương tự {...}. Không có gì sai với nó.
Victor Pudeyev

1
-1: Câu trả lời của Siwei Shen giải thích rằng begin .. endcó phần hơi nhăn mặt. Sử dụng loop do .. break if <condition>thay thế.
Kevin

102

Như thế này:

people = []

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

Tham khảo: Ruby's Hidden do {} while () Loop


1
heh, đánh cho nó chỉ trích.
Blorgbeard ra khỏi

Mã này sẽ thêm một chuỗi trống vào mảng nếu không có đầu vào?
AndrewR

1
Mặc dù nó không áp dụng ở đây, nhưng một vấn đề của cấu trúc đầu-cuối là, không giống như tất cả các cấu trúc ruby ​​khác, nó không trả về giá trị của biểu thức cuối cùng: "bắt đầu 1 kết thúc trong khi false" trả về nil (không phải 1, không sai)
tokland

9
Bạn có thể sử dụng until info.empty?chứ không phải while not info.empty?.
Andrew Grimm

Trên thực tế @AndrewR là một điểm quan trọng .. để thực hiện trước khi so sánh .. làm bằng tốt nghiệp ("còn lại = # {đếm}) trong khi (đếm> 0) ... Tôi nhận được hiển thị" còn lại = 0 ".. hoàn hảo!
baash05

45

Còn cái này thì sao?

people = []

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

4
Đẹp, thanh lịch hơn nhiều.
Blorgbeard sẽ ra vào

23
Nhưng đây không phải là vòng lặp "làm ... trong khi". :)
Alexander Prokofyev

4
Nhưng nó cũng làm điều tương tự trong trường hợp này, trừ khi tôi nhầm
Blorgbeard ra khỏi

18
@Blorgbeard, một vòng lặp do.. while luôn chạy một lần, sau đó đánh giá xem có nên tiếp tục chạy hay không. Một vòng lặp while / Until truyền thống có thể chạy 0 lần. Đó không phải là một sự khác biệt lớn, nhưng chúng khác nhau.
Scott Swezey

5
@Scott, điều đó đúng - Tôi chỉ có nghĩa là mã này tương đương với OP, mặc dù nó không sử dụng do / while. Mặc dù thực sự, mã này thực hiện một nửa "công việc" của vòng lặp trong điều kiện, do đó, nó cũng không hoàn toàn là vòng lặp while truyền thống - nếu điều kiện không khớp, một số công việc vẫn được thực hiện.
Blorgbeard ra mắt vào

11

Đây là bài báo toàn văn từ liên kết chết của hubbardr đến blog của tôi.

Tôi đã tìm thấy đoạn mã sau trong khi đọc nguồn Tempfile#initializetrong thư viện lõi 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)

Thoạt nhìn, tôi cho rằng công cụ whilesửa đổi sẽ được đánh giá trước nội dung của nó begin...end, nhưng đó không phải là trường hợp. Quan sát:

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

Như bạn mong đợi, vòng lặp sẽ tiếp tục thực thi trong khi công cụ sửa đổi là đúng.

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

Trong khi tôi sẽ rất vui khi không bao giờ gặp lại thành ngữ này nữa, begin...endthì khá mạnh mẽ. Sau đây là một thành ngữ phổ biến để ghi nhớ phương thức một lớp không có tham số:

def expensive
  @expensive ||= 2 + 2
end

Đây là một cách xấu xí, nhưng nhanh chóng để ghi nhớ một cái gì đó phức tạp hơn:

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

Từ những gì tôi thu thập được, Matz không thích công trình

begin
    <multiple_lines_of_code>
end while <cond>

bởi vì, ngữ nghĩa của nó khác với

<single_line_of_code> while <cond>

trong đó cấu trúc đầu tiên thực thi mã trước tiên trước khi kiểm tra điều kiện và cấu trúc thứ hai kiểm tra điều kiện trước trước khi thực thi mã (nếu có). Tôi lấy nó Matz thích giữ cấu trúc thứ hai vì nó khớp với cấu trúc một dòng của câu lệnh if.

Tôi không bao giờ thích cấu trúc thứ hai ngay cả khi các câu lệnh if. Trong tất cả các trường hợp khác, máy tính thực thi mã từ trái sang phải (ví dụ: | | và &&) từ trên xuống dưới. Con người đọc mã từ trái sang phải từ trên xuống dưới.

Tôi đề nghị các cấu trúc sau thay thế:

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

Tôi không biết liệu những gợi ý đó có phân tích được với phần còn lại của ngôn ngữ không. Nhưng trong mọi trường hợp, tôi đều giữ việc thực hiện từ trái sang phải cũng như sự thống nhất về ngôn ngữ.


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

3
Điều này trông hơi giống một goto. Các mã che giấu ý định của bạn.
Gerhard

Có vẻ tuyệt vời với tôi, ngoại trừ while truecó thể được thay thế bằng loop do.
David Winiecki

@DavidWiniecki, thực sự, while truecó thể được thay thế bằng loop do. Nhưng tôi đã thử nghiệm cả hai cấu trúc với rất nhiều lần lặp bên trong vòng lặp và phát hiện ra rằng nó nhanh hơn while trueít nhất gấp 2 lần loop do. Không thể giải thích sự khác biệt, nhưng nó chắc chắn là có. (Được phát hiện trong khi thử nghiệm Advent of Code 2017, ngày 15)
Joool Schulenklopper

2

Đây là một số khác:

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

Tôi thích điều này vì unlessnó ở ngay phía trước và tôi không đọc qua một loạt mã (có thể được hiển thị nhiều hơn ở đây) chỉ để tìm thấy một 'lủng lẳng' unlessở cuối. Đó là một nguyên tắc chung trong mã rằng công cụ sửa đổi và điều kiện dễ sử dụng hơn khi chúng ở phía trước như thế này.
Michael Durrant

Đôi khi tôi ước rằng các lập trình viên phải trả tiền mặt cho mỗi lần so sánh thêm. Và đó là cách nó "trông" đối với chúng ta ít quan trọng hơn vẻ bề ngoài của thứ sử dụng nó hàng triệu lần một ngày.
baash05

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

2
Điều này trông hơi giống một goto. Các mã che giấu ý định của bạn và trông rất không đáng tin.
Gerhard

obfuscate * không obfusticate.
qedk 27/05/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.