6

我正在上 The Odin Project 的课程,现在我必须为自己编写一个新#count方法(使用另一个名称),其行为类似于 Enumerable 模块中的普通方法。

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

计数→整数
计数(项目)→整数
count { |obj| 块 } → int

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

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

文档页面有以下示例:

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

3 回答 3

6

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

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
    sum {|i| counter.call(i) ? 1 : 0 }
  end
end

arr = [1,2,3,4,5]
p arr.my_count # => 5
p arr.my_count(2) # => 1
p arr.my_count(&:even?) # => 2
p arr.my_count(2, 3) # => ArgumentError: wrong number of arguments (given 2, expected 1)

在 repl.it 上查看:https ://repl.it/@jrunning/YellowishPricklyPenguin-1

于 2017-02-08T17:44:08.753 回答
2

是的,可以通过使参数可选(块始终是可选的)并检查是否传递了位置参数或块参数来做到这一点。

不过,这有点混乱。大多数 Ruby 实现都解决了这个问题,通过实现有问题的方法来实现对实现的私有内部的特权访问,这使得检查参数是否被传递变得更加容易。例如,JRuby 和 IronRuby 都可以根据参数的数量和类型将多个重载的 Java / CLI 方法绑定到单个Ruby 方法,这使得将这三种“模式”实现count为同一方法的三个简单重载成为可能. count这是来自 IronRuby的示例,这是count来自 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作为参数传递的人。

为了比较,这里是countRubinius 的实现方式:

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

在我(ab)使用可选参数的默认参数是具有副作用(例如设置变量)的任意 Ruby 表达式的情况下,Rubinius 使用了undefined由 Rubinius 运行时提供的特殊对象,并且equal?只针对其自身。

于 2017-02-08T18:57:16.733 回答
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

我还发现这两个链接很有帮助: 带有可选参数的方法http://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/(这提醒我,即使方法未定义为参数,也可以产生块,例如&堵塞)。我看到 Jorg 在第一个链接的讨论中也发表了评论。

于 2017-02-09T12:30:29.280 回答