Ruby的#count方法如何处理nil值?

时间:2014-11-22 16:51:47

标签: ruby

我正在为Odin项目(编程新手)做Ruby练习,我们的任务是重新创建Ruby的#count方法。给定一个类似的数组:

nil_list = [false, false, nil]

观察:

  • nil_list.count == 3,它的长度。
  • nil_list.count(nil)== 1,列表中存在nil的次数。
  • nil_list给定一个块的行为符合预期。

当我尝试重新创建它时,这就是我想出的:

module Enumerable
  def my_count (find = nil)
    result = 0
    for i in self
      if block_given?
        result += 1 if yield(i)
      elsif find != nil
        result += 1 if i == find
      else return self.length
      end
    end
    return result
  end
end

这里的问题是,如果我们作为参数输入nil,这实际上并不计算nils,因为这是相同的(根据我的代码),因为没有参数。

即,nil_list.my_count(nil)== 3而不是1。

在输入这个问题时,我的想法略有不同:

module Enumerable
  def my_count (find = "")
    result = 0
    for i in self
      if block_given?
        result += 1 if yield(i)
      elsif find != ""
        result += 1 if i == find
      else return self.length
      end
    end
    return result
  end
end

所以这解决了我搜索nil时遇到的问题,但是现在nil_list.count(“”)== 0而nil_list.my_count(“”)== 3.同样的问题,只是重新定位到“”我假设永远不会被使用。

此时我只是好奇:实际的计数方法如何防止这个问题发生?

2 个答案:

答案 0 :(得分:2)

您可以撰写def my_count(*args)并查看args的长度。我写道:

module Enumerable
  def my_count(*args)
    case
    when args.size > 1
      raise ArgumentError
    when args.size == 1
      value = args.first
      reduce(0) { |acc, x| value == x ? acc + 1 : acc }
    when block_given?
      reduce(0) { |acc, x| yield(x) ? acc + 1 : acc }
    else
      reduce(0) { |acc, x| acc + 1 }
    end
  end
end

答案 1 :(得分:0)

丑陋的事实是:在大多数Ruby实现中,Enumerable#count实际上并不是用Ruby编写的。在MRI,YARV和MRuby中,它用C语言编写,用JRuby和XRuby编写,用Java编写,用IronRuby和Ruby.NET编写,用C#编写,用MacRuby编写,用Objective-C编写,用MagLev编写,用Smalltalk,在Topaz中,它用RPython编写,在Cardinal中,用PIR或PASM编写,依此类推。它不仅不是用Ruby编写的,它还具有对执行引擎内部的特权访问权限,特别是它可以访问传递的参数,而这些参数是Ruby无法做到的。

这样的重载方法遍布核心库和标准库,但它们不能轻易地用Ruby编写。实施者通过用支持重载的语言(例如C#或Java)编写它们来作弊,或者他们给予他们对执行引擎内部的特权访问权。

Ruby中的标准解决方法是(ab)使用以下事实:可选参数的默认值只是普通的Ruby表达式,并且默认值表达式中的局部变量在方法体内是可见的:

def my_count(find = (find_passed = false; nil))
  if find_passed # find was passed
    # do something
  else
    # do something else
  end
end

第二种可能性是使用一些不可伪造的唯一标记作为默认值:

undefined = Object.new
define_method(:my_count) do |find = undefined|
  if undefined.equal?(find) # find was not passed
    # do something
  else
    # do something else
  end
end