【问题标题】:Idiomatic Ruby - Execute a function until it returns a nil, collecting its values into a list惯用的 Ruby - 执行一个函数直到它返回一个 nil,将它的值收集到一个列表中
【发布时间】:2011-10-16 17:42:15
【问题描述】:

我从这篇文章中窃取了我的头衔:Executes a function until it returns a nil, collecting its values into a list

这个问题是关于 Lisp 的,坦率地说,我想不通。然而,我认为他的问题——翻译成 Ruby——正是我自己的问题:

在 [Ruby] 中创建条件循环的最佳方法是什么?该循环执行一个函数,直到它返回 NIL,然后将返回的值收集到一个列表中?

我目前的笨拙方法是这样的:

def foo
   ret = Array.new
   x = func() # parenthesis for clarity (I'm not a native Ruby coder...)
   until x.nil?
     ret << x
     x = func() 
   end
   ret
end

这段代码 sn-p 会做我想做的事……但我知道有一种更简洁、更惯用的 Ruby 方法……对吗?

【问题讨论】:

  • 如果您能提供一个使用这种方法的真实案例,我会很感兴趣。
  • @Phrogz 这对我来说听起来像是一个可迭代的生成器,它是一种常见的设计模式,尤其是在函数式程序中。

标签: ruby list while-loop return-value null


【解决方案1】:

有趣的是,没有人建议 Enumerator 和它的 take_while 方法,对我来说似乎很合适:

# example function that sometimes returns nil
def func
  r = rand(5)
  r == 0 ? nil : r
end

# wrap function call into lazy enumerator
enum = Enumerator.new{|y|
  loop {
    y << func()
  }
}

# take from it until we bump into a nil
arr = enum.take_while{|elem|
  !elem.nil?
}

p arr
#=>[3, 3, 2, 4, 1, 1]

【讨论】:

  • +1,非常好的解决方案,Enumerator 未充分用于此类事情。
  • 想过类似的方法,但不喜欢最终结果,因为写完1分钟就已经让我自己困惑了。
  • 因为这段代码根本不像 Ruby。有效的代码!= 漂亮的代码。
  • @ream88 -- 我不同意。 Ruby 具有函数式特性,因此对典型函数式问题的函数式方法似乎是正确的。我只会在这里和那里使用 do-end 块而不是括号;)
  • 代替enum.take_while { |elem| !elem.nil? }elem.take_while(&amp;:itself)
【解决方案2】:

我猜这看起来更像 Ruby:

def foo
  r = nil; [].tap { |a| a << r until (r = yield).nil? }
end

【讨论】:

  • 我喜欢这个,比我的干净。
  • 如果您在方法参数中声明 x=nil,它将是一个单行 :)
  • 请注意,这些答案并不完全符合要求,要求 nil 仅作为停止条件(而不是 false)。
  • 它很漂亮,但是因为它错误地处理了 false 它不满足要求。
  • until (result = yield).nil? 修复应该很容易,不是吗?
【解决方案3】:

这应该可以解决问题:

def foo
  arr = []
  while true
    x = yield
    break if x.nil?
    arr << x
  end
  arr
end

用法:

foo { doStuff }
foo &bar

【讨论】:

  • 我认为在 Ruby 中使用 loop{ ... } 而不是 while true ... 更为惯用。
【解决方案4】:

我个人会这样写,这样你就可以传递一个调用函数或做任何你想做的事情的块:

def gather
  [].tap do |collection|
    result=true; i=0
    until result.nil?
        unless (result=yield(i)).nil?
        collection << result
      end
      i += 1
      end
  end
end


# Silly test
$letters = *('a'..'z')
$current = -1
def next_char(max)
  $letters[$current+=1] if $current<max
end
some = gather{ next_char(10) }
p some
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]

some = gather{ |i| $letters[i] if i<=10 }
p some
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"]

请注意,您可以这样编写方法:

def gather
  [].tap do |result|
    x = true
    result << x unless (x=yield).nil? until x.nil?
  end
end

……但我个人认为这不是很可读。

【讨论】:

    【解决方案5】:

    我认为你一开始就很接近了。

    def gather
      ret = []
      while x = yield
        ret << x
      end
      ret
    end
    

    这里唯一的技巧是理解赋值“x = yield”返回x的值,所以“while x = yield”循环而x不是nil/false。不要与“while x == yield”混淆......

    【讨论】:

      猜你喜欢
      • 2011-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-15
      相关资源
      最近更新 更多