Ruby方法可以接受块或参数吗?

时间:2017-02-08 17:20:43

标签: ruby count enumerable method-declaration

我正在Odin项目中上课,现在我必须自己编写一个新的#count方法(带有其他名称),其行为类似于Enumerable模块中的正常方法。

有关计数的文档说明以下内容(http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count):

  

count→int
count(item)→int
count {| obj | block}→int

     

通过枚举返回enum中的项目数。如果   给出了参数,enum中等于item的项目数   被计算在内。如果给出了一个块,它会计算元素的数量   产生真正的价值。

我想我可以将所有这些作为单独的方法编写,但我主要想知道一个方法定义是否可以将count的最后两次使用与item和块一起使用。当然,我想知道这三个是否可以合并为一个定义,但我最后对这两个定义感兴趣。到目前为止,我似乎找不到可能的答案。

文档页面包含以下示例:

ary = [1, 2, 4, 2]
ary.count               #=> 4
ary.count(2)            #=> 2
ary.count{ |x| x%2==0 } #=> 3

3 个答案:

答案 0 :(得分:5)

当然有可能。您所要做的就是检查是否给出了参数,并检查是否给出了一个块。

def call_me(arg=nil)
  puts "arg given" unless arg.nil?
  puts "block given" if block_given?
end

call_me(1)
# => arg given
call_me { "foo" }
# => block given
call_me(1) { "foo" }
# => arg given
#    block given

或者:

def call_me(arg=nil, &block)
  puts "arg given" unless arg.nil?
  puts "block given" unless block.nil?
end

后者非常有用,因为它会将块转换为可以重复使用的Proc(名为block),如下所示。

您可以像这样实现自己的count方法:

module Enumerable
  def my_count(*args, &block)
    return size if args.empty? && block.nil?
    raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size > 1
    counter = block.nil? ? ->(i) { i == args[0] } : block
    reduce(0) {|cnt,i| counter.call(i) ? cnt + 1 : cnt }
  end
end

p [1,2,3,4,5].my_count # => 5
p [1,2,3,4,5].my_count(2) # => 1
p [1,2,3,4,5].my_count {|i| i % 2 == 0 } # => 2

在repl.it上查看:https://repl.it/FcNs

答案 1 :(得分:2)

是的,可以通过使参数可选(块总是可选的)并检查是否传递位置参数或块参数来实现。

但是,这有点乱。大多数Ruby实现通过实现具有对实现的私有内部的特权访问的相关方法来解决这个问题,这使得检查参数是否被传递变得更加容易。例如。 JRuby和IronRuby都有办法根据参数的数量和类型将多个重载的 Java / CLI方法绑定到单个 Ruby方法,从而可以实现这些三种"模式" count作为同一方法的三个简单重载。以下是count from IronRuby的示例,这是count from JRuby

但是,Ruby并不支持重载,所以你必须手动实现它,这可能有点尴尬。像这样:

module Enumerable
  def count(item = (item_not_given = true; nil))
    item_given = !item_not_given
    warn 'given block not used' if block_given? && item_given

    return count(&item.method(:==)) if item_given
    return inject(0) {|acc, el| if yield el then acc + 1 else acc end } if block_given?
    count(&:itself)
  end
end

正如你所看到的,它有点尴尬。为什么我不使用nil作为可选item参数的默认参数?好吧,因为nil是一个有效的参数,我不能区分没有参数的人和传递nil的人作为参数。

为了进行比较,以下是count is implemented in Rubinius

的方法
def count(item = undefined)
  seq = 0
  if !undefined.equal?(item)
    each do
      element = Rubinius.single_block_arg
      seq += 1 if item == element
    end
  elsif block_given?
    each { |element| seq += 1 if yield(element) }
  else
    each { seq += 1 }
  end
  seq
end

其中I(ab)使用以下事实:可选参数的默认参数是具有副作用的任意Ruby表达式,例如设置变量,Rubinius使用由Rubinius运行时提供的特殊undefined对象并且equal?只对自己而言。

答案 2 :(得分:0)

感谢您的帮助!就在我来检查是否有任何答案之前,我提出了以下解决方案。它可以肯定地改进,我会尝试缩短它,但我更喜欢先在这里发布它,因为我提出它,它可能对像我这样的其他新手有帮助。在下面的代码中,我使用的#my_each方法与普通的#each相同。

def my_count(arg=nil)
    sum = 0
    if block_given? && arg == nil
        self.my_each do |elem|
            if yield(elem)
                sum += 1
            end
        end
    elsif !block_given? && arg != nil
        self.my_each do |elem|
            if arg == elem
                sum += 1
            end
        end
    else
        self.my_each do |elem|
            sum += 1
        end
    end
    sum
end

我还发现这两个链接很有帮助: A method with an optional parameterhttp://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/(这提醒我一个方法可以产生一个块,即使它没有被定义为诸如& block之类的参数)。我看到Jorg也在第一个链接的讨论中发表了评论。