Ruby [讨论] 用 Ruby 实现 Ruby 的 inject 方法

frankking · 2014年12月09日 · 最后由 davidhuangdw 回复于 2014年12月09日 · 3740 次阅读

Ruby 新手,自己用 Ruby 实现了 Ruby 的Enumerable#inject方法:Enumerable#my_inject 代码如下:

module Enumerable
  def my_inject *arg
    initial = method = nil 
    tmp = Array.new(self)
    # 判断参数个数及类型
    if arg.length > 2
      raise ArgumentError, "wrong number of arguments #{arg.length} for 0..2"
    elsif arg.length == 2
      initial, method = arg[0], arg[1] 
    elsif arg.first.is_a? Symbol
      method = arg.first
    elsif arg.first
      initial = arg.first
    end
    # 给accumulation赋值
    accumulation = initial ? initial : tmp.shift

    # 如果参数中有Symbol(方法)
    if method 
      tmp.each do |element|
        accumulation = accumulation.send(method, element)
      end
    else 
      tmp.each do |element|
        accumulation = yield accumulation, element
      end
    end

    accumulation
  end
end

大家有兴趣的可以一起来讨论有没有更优雅的写法? 还有,我是新手,有经验的前辈可否指点一下我的代码?(思路、命名、注释..)

代码的缩进用 2 个空格,而不是 4 个。

nice!! 感觉还需要再重构一下:

  1. 尽量不要用注释,用方法名代替注释 -- 把注释的代码段重构成方法
  2. 想办法把 if/else 去掉,比如:用幂等方法 to_a, to_proc, to_h, Array() ...

嘿嘿,我也试一试:

module Enumerable
  def my_inject(acc=nil, meth=nil, &blk)
    meth,acc = [acc,nil] unless blk || meth
    meth = blk || meth
    raise "should provide method_name or block" unless meth && meth.respond_to?(:to_proc)

    to_a.arr_inject(acc, &meth.to_proc)
  end
  def arr_inject(acc)
    acc ||= shift
    each {|v| acc = yield acc,v}
    acc
  end
end
p (1..10).my_inject(:+)
p (1..10).my_inject(&:+)
p (1..10).my_inject(10, :+)
p (1..10).my_inject(10, &:+)
p (1..10).my_inject(0)

#3 楼 @davidhuangdw 厉害!学到很多东西,谢谢你。

  1. 去掉了所有的 if/else,利用 || 和 response_to? 整合判断。
  2. 利用 to_proc,把 send 和 yield 合为一个函数

但是我觉得还有两个问题:

  1. (1..10).my_inject(:+) 时,破坏了原数组,这点和Enumerable#inject相违背。 我的理解可以将to_a.arr_inject(acc, &meth.to_proc)换成dup.arr_inject(acc, &meth.to_proc)
  2. 有个小 BUG:p (1..2).my_inject(0, :+, &:*) #=> 2 ,应该把meth = blk || meth换成meth = meth || blk吧?

#4 楼 @frankking 哈哈,确实好多没想到诶,特别是要小心破坏性操作。谢谢,共同进步!

module Enumerable
  def my_inject(acc=nil, meth=nil, &blk)
    meth,acc = [acc,nil] unless blk || meth
    raise "shouldn't provide both method_name and block" if meth && blk
    meth ||= blk
    raise "should provide method_name or block" unless meth && meth.respond_to?(:to_proc)

    acc,all = acc ? [acc, self] : [first, drop(1)]
    all.safe_inject(acc, &meth.to_proc)
  end
  def safe_inject(acc)
    each {|v| acc = yield acc,v}
    acc
  end
end

考虑情况一多,代码就矬了。。。

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