Ruby 注意使用 inject 和 reduce

pathbox · 2017年08月24日 · 最后由 adamshen 回复于 2017年08月24日 · 1530 次阅读

最近在使用inject的时候遇到了一个问题。我的代码例子是:

[1,2,3,4,5,6].inject([]) do |sum, i|
  if i > 1
    sum << i
  end
end

结果报错:

NoMethodError: undefined method `<<' for nil:NilClass

一开始很奇怪,在inject([])的时候,我给sum赋值初始值为数组了,报错信息怎么会报 sum是一个 nil:NilClass呢?

打断点

def test_inject
  [1,2,3,4,5,6].inject([]) do |sum, i|
    if i > 1
      binding.pry
      sum << i
    end
  end
end

发现确实 sum是nil。想到了可能是if i > 1 这个条件判断的影响。修改一下测试代码

def test_inject_0
  [1,2,3,4,5,6].inject([]) do |sum, i|
    if i > 0
      sum << i
    end
  end
end

def test_inject_3
  [4,5,6,1,2,3].inject([]) do |sum, i|
    binding.pry
    if i > 3
      sum << i
    end
  end
end
test_inject_0  # [1, 2, 3, 4, 5, 6]

test_inject_3  
sum
# []
# [4]
# [4,5]
# [4,5,6]
# NoMethodError: undefined method `<<' for nil:NilClass

根据test_inject_3 , 当i的值不满足 if i > 3 的条件的时候,sum的值就被置为nil了。

如果你需要在inject的block中进行对操作元素的条件过滤操作,你会将sum变为nil。

所以建议不在inject的block中进行条件过滤操作,而是在inject方法之前过滤好。

同理reduce方法。

Ruby 版本: ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

共收到 4 条回复

当我没说 。

@pathbox

https://apidock.com/ruby/Enumerable/inject

参考 inject 使用文档

[1,2,3,4,5,6].inject([]) do |sum, i|  

当你对数组调用inject方法并进行迭代时,sum刚开始是为[],然后下面一句是需要注意的,迭代过程中block的返回值会赋值给sum,并作为下一次的输入,最终迭代完成后,sum的值作为返回结果。

In either case, the result becomes the new value for memo. At the end of the iteration, the final value of memo is the return value for the method.

所以,当进行如下判断时

if i > 1 
  sum <<  i
end

执行完成 if语句后,返回的值为nil,此时本次迭代的返回值nil就会赋值给sum,当下一次再进行迭代时, 就会报你遇到的以下错误

NoMethodError: undefined method `<<' for nil:NilClass

解决办法

在block迭代的时候,显示返回sum就行了

[1,2,3,4,5,6].inject([]) do |sum, i|
  if i > 1
    sum << i
  end
  sum
end

可以用each_with_object,这样可以少写一行

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册