在ruby中循环,将块中的值设置为nil

时间:2015-02-24 19:19:17

标签: ruby loops

我有以下内容(' d'方法就像' puts' - 使用log_buddy):

response_array.count.times do |i|
      d {i}
      if i == 0
        return_response = response_array[i][:value]['value']
        d {return_response}
      else
        d {return_response; response_array[i][:value]['value']}

        return_response = return_response + "X-LINE-BREAK." + 
                          response_array[i][:value]['value'] 

        d {return_response}
      end

    end

输出如下:

response_array.count = 2
i = 0
return_response = "this is the return response"
i = 1
return_response = nil

问题:循环第二次i = 1时,return_response的值为nil。我无法思考为什么。

1 个答案:

答案 0 :(得分:2)

TL; DR块在每次迭代时重置其范围。您可以在进入循环之前执行return_response = nil,它应该可以解决您的问题,因为它不会在每次迭代时重置。

这是一个非常有趣的问题,因为输出有些误导。我做了一些更多的研究,因为我不喜欢我的答案,这就是我想出的。 (仅供参考,我在Ruby 2.1.1上运行了所有这些)

可变存在

在Ruby中,解析器确定单个作用域中的变量存在。有一个really good explanation here,但举个简单的例子:

if false
  fnord = 42
end
fnord # => nil

与此相反(您必须重新加载irb或重置范围)

fnord # => NameError: undefined local variable or method `fnord' for main:Object
if false
  fnord = 42
end

这是一个有趣的行为,我强烈建议您阅读上面链接的堆栈溢出答案以获取更多详细信息。

阻止范围

Ruby中的块在每次迭代时重置其本地范围。

5.times do
  # This "if false" is here because otherwise the scope reset would cause x 
  # to be a NameError, as above. If you delete the following 3 lines it
  # will raise that error when running "p x".
  if false
    x = 10 
  end
  p x
  x = 20
end

永远不会保留x = 20 - 你连续五次打印nil,因为x已被解析(所以你不会得到上面的NameError)但它没有&# 39;被分配。您可以在块作用域之外定义x以保留该值(或者甚至让解析器在块作用域之外解析x,而不实际进行赋值!)

x = nil
5.times do
  p x
  x = 20
end

这将导致nil,然后是20,20,20,20。在这种情况下x的范围超出了块范围(并且块可以访问它们周围范围内的局部变量)。即使您使用if false机制来声明&#​​34;声明" x没有分配它,它仍然可以工作。

因此,对于您的情况,问题不在于return_response的范围限定为if语句的if/else部分,它不是't}所有第二次分配(但它确实被解析,因此它的值为nil,而不是引发NameError)。让我们稍微简化一下你的情况:

(0..1).each do |i|
  if i == 0
    x = 10
    p x
  else
    p x
  end
end

这打印10,然后是nil,这反映了你遇到的问题。你无法通过"声明"来解决问题。 if语句之前的变量,但仍然在块作用域内(或使用block-local variables,这将做同样的事情):

(0..1).each do |i|
  x ||= nil # note: this is not something you'd probably do in Ruby

  if i == 0
    x = 10
    p x
  else
    p x
  end
end

这仍然打印10,然后是零。您必须将变量移动到块范围之外,以使其不会每次都重置。

x = nil # outside the block scope
(0..1).each do |i| 
  if i == 0
    x = 10
    p x
  else
    p x
  end
end

这将打印10,10。

从您的代码看起来,您希望在迭代时从每一行构建return_response。您可以在进入循环之前执行return_response = nil,它应该可以解决您的问题,因为它不会在每次迭代时重置。更类似Ruby的方法是在进入循环之前分配return_response = []并附加每条记录(在这种情况下你不需要检查i == 0)。如果你不需要中间日志记录,你可能会放弃这个

return_response = response_array.map { |response_line| response_line[:value]['value'] }.join("X-LINE-BREAK.")

即。将响应数组中的每个项目映射到其[:value]['value'],然后将所有这些项目连接到每个项目之间的"X-LINE-BREAK."字符串。

PS:这两个答案还提供了一些关于块作用域,变量和循环的其他特性的信息,尽管它们不适用于您的特定情况:https://stackoverflow.com/a/1654665/4280232https://stackoverflow.com/a/21296598/4280232。< / p>

我在这里写的上一个答案是不正确的,所以我更换了它。如果这是错误的方法,请告诉我。