rubycountenumerablemethod-declaration

Can a Ruby method accept either a block OR an argument?


I'm doing the lessons on The Odin Project and now I have to write myself a new #count method (with another name) that behaves like the normal one from the Enumerable module.

The documentation on count says the following (http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count):

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

Returns the number of items in enum through enumeration. If an argument is given, the number of items in enum that are equal to item are counted. If a block is given, it counts the number of elements yielding a true value.

I think I can write all of these as separate methods, but I was mostly wondering if one method definition can combine the last two uses of count - with item and with the block. Naturally, I'm wondering if all three can be combined in one definition, but I'm mostly interested in the last two. So far I can't seem to find a possible answer.

The documentation page has these examples:

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

Solution

  • Sure it's possible. All you have to do is check if an argument is given and also check if a block is given.

    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
    

    Or:

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

    The latter is useful because it converts the block to a Proc (named block) that you can then reuse, as below.

    You could implement your own count method like this:

    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)
    

    See it on repl.it: https://repl.it/@jrunning/YellowishPricklyPenguin-1