如何创建inject方法的自定义实现?

时间:2016-10-10 11:22:45

标签: ruby

起初看起来很容易。这是我在几分钟内写的第一个代码块:

module Enumerable

  def my_inject(memo=false)
    memo = memo ? memo : self[0]
    self.my_each do |item|
      memo = yield(memo, item)
    end
    return memo
  end

end

当使用初始值调用inject方法时,它按预期工作。但我无法找到一种方法让它正常工作没有任何初始值。

例如:

puts [1,2,3,4].my_inject(0) { |memo, i| memo+i} #=> 10
puts [1,2,3,4].my_inject { |memo, i| memo+i} #=> 11

这就是原始Ruby注入方法输出的内容:

puts [1,2,3,4].inject { |memo, i| memo+i} #=> 10

Ruby文档说

  

如果没有明确指定备忘录的初始值,那么   第一个集合元素用作备忘录的初始值。

那么如何为初始值设置正确的数据类型?我们是否应该为Ruby中的每种数据类型设置条件并为每种数据类型设置默认值?

3 个答案:

答案 0 :(得分:2)

如果您调试Ruby自己的实现,您将看到它在没有给出默认备忘录的情况下从第二个元素开始迭代:

> [1,2,3].inject { |m, i| puts "M: #{m} | I: #{i}"; break }
M: 1 | I: 2

因此,您的实现应如下所示:

def my_inject(*args)
  init = args.size > 0
  memo = init ? args[0] : self[0]

  self.drop(init ? 0 : 1).my_each do |item|
    memo = yield(memo, item)
  end

  return memo
end

puts [1,2,3,4].my_inject(0) { |memo, i| memo+i} #=> 10
puts [1,2,3,4].my_inject { |memo, i| memo+i}    #=> 10

init = args.size > 0的原因是您必须考虑到有人可能想要[...].my_inject(false) { ... }。因此,您无法检查是否memo == false确定是否需要跳过第一个元素,您必须检查给定的实际参数计数。

答案 1 :(得分:0)

  

那么如何为初始值设置正确的数据类型?我们是否应该为Ruby中的每种数据类型设置条件并为每种数据类型设置默认值?

不,默认值是集合的第一个元素。

Ruby允许你定义自己的类,所以无论如何都不可能为每种可能的类型写一个案例,其中有无数种。

答案 2 :(得分:0)

出于好奇 - 尾递归的解决方案:

class Array
  def my_inject(memo = nil, enum = self.each)
    return enum_for(:my_inject) unless block_given?

    memo = enum.next if memo.nil?
    my_inject(yield(memo, enum.next), enum, &Proc.new)
  rescue StopIteration
    memo
  end 
end

[1,2,3].my_inject(3) { |memo, v| memo += v } 
#⇒ 9
[1,2,3].my_inject &:*
#⇒ 6