【问题标题】:Is there a "do ... while" loop in Ruby?Ruby 中有“do ... while”循环吗?
【发布时间】:2010-09-13 07:14:12
【问题描述】:

我正在使用此代码让用户输入名称,而程序将它们存储在一个数组中,直到他们输入一个空字符串(他们必须在每个名称后按 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 中似乎不存在这种类型的循环。有人可以提出更好的方法吗?

【问题讨论】:

  • 我觉得普通的while循环看起来更好看,也更容易阅读。
  • @Jeremy Ruten 您是否有兴趣将已接受的答案更改为沉思伟的答案loop do; ...; break if ...; end

标签: ruby loops


【解决方案1】:

注意

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

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

这是 2005 年 11 月 23 日的 an email exchange,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 的创建者 Yukihiro Matsumoto 对这个循环功能表示遗憾,并建议使用 Kernel#loop。

【讨论】:

  • 正确。这个begin end while 方法似乎不对。感谢您为我提供说服团队其他成员所需的素材。
  • 看起来 begin-end-while 循环实际上是在运行循环之前评估条件。这与常规的 while 循环之间的区别在于它保证至少运行一次。它只是足够接近......虽然会引起问题。
  • 所以,据我了解,begin-end-while 是“遗憾的”,因为违反了修饰符的语义,即:在执行块之前对其进行检查,例如: puts k 如果 !k.nil?。这里的“if”是一个“修饰符”:在之前检查它,以确定是否执行“puts k”语句。while/until循环不是这种情况(当用作begin-end-block的修饰符时!),在第一次处决后进行评估。也许这引起了遗憾,但我们有比旧论坛帖子“更强大”的东西吗?就像在许多其他场合一样,官方弃用了它?
  • 我花了很长时间才弄清楚为什么我使用这个 ruby​​ 'do-while' 循环不起作用。您应该使用“除非”来更接近地模仿 c 风格的 do-while,否则您可能最终会像我一样忘记反转条件:p
  • @James 根据链接的邮件,他说他“后悔”添加它。有时人们会犯错误,即使他们是语言设计师。
【解决方案2】:

我在阅读 Ruby 核心库中 Tempfile#initialize 的源代码时发现了以下 sn-p:

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

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

【讨论】:

  • 这就是为什么在链接到外部网站时,我总是确保将相关信息复制到我的答案中。
  • 为什么你希望 while 修饰符在 begin...end 的内容之前被计算?这就是 do..while 循环应该起作用的方式。为什么你会“很高兴再也见不到这个成语?”它出什么问题了?我很困惑。
  • begin...end 看起来像一个块,类似于 {...}。没有错。
  • -1:沉思伟的回答解释说,begin .. end 有点不受欢迎。请改用loop do .. break if &lt;condition&gt;
【解决方案3】:

像这样:

people = []

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

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

【讨论】:

  • 如果没有输入,这段代码不会给数组添加一个空字符串吗?
  • 虽然这里不适用,但是 begin-end-while 构造的一个问题是,与所有其他 ruby​​ 构造不同,它不返回最后一个表达式的值:“begin 1 end while false" 返回 nil(不是 1,不是 false)
  • 您可以使用until info.empty? 而不是while not info.empty?
  • 实际上@AndrewR 这就是重点.. 在比较之前做一些事情.. do diplay("remaining =#{count} ) while(count > 0)... 我得到一个显示“剩余 = 0”.. 完美!
【解决方案4】:

这个怎么样?

people = []

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

【讨论】:

  • 但这不是“do ... while”循环。 :)
  • 但在这种情况下它做同样的事情,除非我弄错了
  • @Blorgbeard,do..while 循环总是运行一次,然后评估它是否应该继续运行。传统的 while/until 循环可以运行 0 次。这不是一个巨大的差异,但它们是不同的。
  • @Scott,这是真的 - 我只是说这段代码等同于 OP,即使它不使用 do/while。虽然实际上,这段代码在条件中做了一半循环的“工作”,所以它也不是一个传统的 while 循环——如果条件不匹配,一些工作仍然完成。
  • 刚试过这个。你可以有一个表单块 begin ... end until。酷!
【解决方案5】:

这是来自 hubbardr 到我博客的死链接的全文文章。

我在阅读 Ruby 核心库中Tempfile#initialize 的源代码时发现了以下 sn-p:

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

【讨论】:

    【解决方案6】:

    现在可以正常工作了:

    begin
        # statment
    end until <condition>
    

    但是,它可能会在将来被删除,因为begin 语句是违反直觉的。见:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

    Matz(Ruby 的创建者)建议这样做:

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

    【讨论】:

      【解决方案7】:

      据我所知,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
      

      我不知道这些建议是否会与其他语言一起解析。但无论如何 我更喜欢保持从左到右的执行以及语言的一致性。

      【讨论】:

        【解决方案8】:
        a = 1
        while true
          puts a
          a += 1
          break if a > 10
        end
        

        【讨论】:

        • 这看起来有点像 goto。代码混淆了你的意图。
        • 对我来说看起来很棒,除了 while true 可以替换为 loop do
        • @DavidWiniecki,确实,while true 可以替换为 loop do。但我在循环内用大量迭代测试了这两种结构,发现while true 至少比loop do 快2 倍。无法解释差异,但肯定存在。 (在测试 Advent of Code 2017 第 15 天时发现。)
        【解决方案9】:

        这是另一个:

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

        【讨论】:

        • 我更喜欢这个,因为 unless 就在前面,我不会阅读一堆代码(可能比这里显示的更多)只是为了找到一个“悬空”unless在末尾。修饰符和条件在像这样“预先”放置时更容易使用,这是代码中的一般原则。
        • 我有时希望我们的编码人员必须为每次额外的比较支付现金。而且它对我们的“外观”不如它对每天使用它一百万次的东西的外观那么重要。
        【解决方案10】:
        ppl = []
        while (input=gets.chomp)
         if !input.empty?
          ppl << input
         else
         p ppl; puts "Goodbye"; break
         end
        end
        

        【讨论】:

        • 这看起来有点像 goto。代码混淆了你的意图,看起来很不红。
        • 混淆* 不混淆。
        猜你喜欢
        • 2014-08-16
        • 2010-09-16
        • 1970-01-01
        • 2018-05-09
        • 2015-07-03
        • 2016-02-21
        • 2011-12-26
        • 2020-11-26
        • 2015-02-16
        相关资源
        最近更新 更多