Ruby中是否有“ do…while”循环?


452

我正在使用此代码让用户输入名称,而程序将它们存储在数组中,直到他们输入一个空字符串(他们必须在每个名称后按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

这段代码在do ... while循环中看起来会更好:

people = []

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

在此代码中,我不必将信息分配给一些随机字符串。

不幸的是,Ruby中似乎不存在这种类型的循环。有人可以建议一种更好的方法吗?


1
我认为普通的while循环看起来更好,并且更易于阅读。
Magne 2013年

1
@Jeremy Ruten您是否有可能希望将已接受的答案更改为沉思薇的答案,loop do; ...; break if ...; end
David Winiecki 2014年

Answers:


643

注意

begin <code> end while <condition>由Ruby的作者Matz的拒绝。相反,他建议使用Kernel#loop,例如

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

这是2005年11月23日的电子邮件交换,其中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具有类似的故事:

在2005年11月,Ruby的创建者Matsuki Yukihiro Matsumoto对这个循环功能感到遗憾,并建议使用Kernel#loop。


18
发现。这种begin end while方法似乎不正确。感谢您给了我说服团队其他成员所需的饲料。
约书亚·品特

2
看起来begin-end-while循环实际上是在运行循环之前评估条件。那和常规的while循环之间的区别在于,它保证至少运行一次。它足够近了,足以引起问题。
user1992284 2013年

4
因此,据我所知,begin-end-while是“遗憾的”,因为它违反了修饰符的语义,即:它们在执行块之前被检查,例如:如果!k.nil ?,则放置k。这里的'if'是一个'修饰符':在执行之前先检查一下,以确定是否执行'puts k'语句。 !),在第一次执行后被评估。也许这引起了遗憾,但是我们是否比旧的论坛帖子“更强大”,像在许多其他场合一样被正式弃用?
AgostinoX

2
我花了一个令人费解的时间才能弄清楚为什么我无法使用此红宝石“ do-while”循环。您应该使用“除非”来更紧密地模仿C风格的做事,否则您可能会像我一样最终忘了反转条件:p
Connor Clark

1
@James根据链接邮件,他说他在“后悔”添加它。有时,即使是语言设计师,人们也会犯错。
熊加米奥夫

188

在阅读Tempfile#initializeRuby核心库的源代码时,我找到了以下代码段:

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

如您所料,在修饰符为true时,循环将继续执行。

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

最初由Jeremy Voorhis撰写。内容已被复制到此处,因为它似乎已从原始站点中删除。副本也可以在Web存档Ruby Buzz论坛中找到。-比尔蜥蜴



56
这就是为什么链接到外部站点时,我始终确保将相关信息复制到我的答案中。
davr 2010年

10
您为什么期望while修饰符在begin ... end的内容之前被评估?这就是do..while循环应该起作用的方式。为何您会“很高兴不再见到这种习语?” 它出什么问题了?我糊涂了。
bergie3000

2
开始...结束看起来像一个块,类似{...}。没错。
维克托·普德耶夫

1
-1:沉思玮的回答解释了这begin .. end一点。使用loop do .. break if <condition>代替。
凯文(Kevin)

102

像这样:

people = []

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

参考:Ruby的Hidden do {} while()循环


1
嘿,殴打它。该死的。
Blorgbeard在

如果没有输入,此代码不会在数组中添加一个空字符串吗?
AndrewR

1
尽管在这里不适用,但是begin-end-while构造的一个问题是,与所有其他ruby构造不同,它不返回最后一个表达式的值:“ begin 1 end while false”返回nil(不是1, (不是虚假)
tokland 2010年

9
您可以使用until info.empty?而不是while not info.empty?
安德鲁·格林

实际上,@ AndrewR就是这样。.在比较之前做一些事情..做diplay(“ remaining =#{count})while(count> 0)...我得到的显示是” remaining = 0“。完美!
baash05 2012年

45

这个怎么样?

people = []

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

4
很好,更优雅。
Blorgbeard将于

23
但这不是“ do ... while”循环。:)
Alexander Prokofyev

4
但是在这种情况下,它的作用相同,除非我弄错了
Blorgbeard将于

18
@Blorgbeard,一个do..while循环始终运行一次,然后进行评估以查看是否应继续运行。传统的while / until循环可以运行0次。这不是巨大的差异,但是它们是不同的。
Scott Swezey,2010年

5
@Scott,没错-我只是说这段代码等同于OP的代码,即使它不使用do / while。尽管实际上,此代码在条件中完成了循环“工作”的一半,所以它也不是传统的while循环-如果条件不匹配,则仍会完成一些工作。
Blorgbeard在

11

这是hubbardr到我的博客的无效链接的全文。

在阅读Tempfile#initializeRuby核心库的源代码时,我找到了以下代码段:

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

如您所料,在修饰符为true时,循环将继续执行。

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


6

根据我的收集,Matz不喜欢这种结构

begin
    <multiple_lines_of_code>
end while <cond>

因为,它的语义不同于

<single_line_of_code> while <cond>

因为第一种构造在检查条件之前先执行代码,第二种构造在执行代码之前先测试条件(如果有)。我认为Matz倾向于保留第二个构造,因为它与if语句的一行构造匹配。

即使对于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。该代码混淆了您的意图。
Gerhard

对我来说看起来不错,但while true可以替换为loop do
David Winiecki 2014年

的确,@ DavidWiniecki while true可以替换为loop do。但是我在循环中进行了多次迭代测试了这两种构造,发现速度while true至少比 2 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 2012年

有时我希望我们的编码员每次额外的比较都要付出现金。对于我们每天使用一百万次的事物,它对我们的“外观”并不重要。
baash05 2012年

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

2
这看起来有点像goto。该代码混淆了您的意图,并且看起来很不整洁。
Gerhard

混淆*不混淆。
qedk 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.