Reputation: 21
I'm doing Ruby exercises for the Odin Project (programming newcomer), and we're tasked with recreating Ruby's #count method. Given an array like:
nil_list = [false, false, nil]
Observations:
When I try to recreate it, here's what I come up with:
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
The problem here is that this doesn't actually count nils if we enter nil in as an argument, since this is the same (according to my code) as there not being an argument.
ie, nil_list.my_count(nil) == 3 instead of 1.
While typing this question I had a slightly different idea:
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
So this fixes the problem I was having with searches for nil, but now nil_list.count("") == 0 whereas nil_list.my_count("") == 3. Same issue, just relocated to "" which I assume doesn't ever get used.
At this point I'm just curious: how does the actual count method prevent this issue from happening?
Upvotes: 2
Views: 2055
Reputation: 369468
The ugly truth is: in most Ruby implementations, Enumerable#count
isn't actually written in Ruby. In MRI, YARV and MRuby, it's written in C, in JRuby and XRuby, it's written in Java, in IronRuby and Ruby.NET, it's written in C#, in MacRuby, it's written in Objective-C, in MagLev, it's written in Smalltalk, in Topaz, it's written in RPython, in Cardinal, it's written in PIR or PASM, and so on. And it not only is not written in Ruby, it's also got privileged access to the internals of the execution engine, in particular, it can access the arguments that were passed, which you cannot do from Ruby.
Such overloaded methods appear all over the core library and standard library, but they can't easily be written in Ruby. The implementers cheat by either writing them in languages that do support overloading (e.g. C# or Java), or they give them privileged access to the internals of the execution engine.
The standard workaround in Ruby is to (ab)use the fact that the default value of an optional parameter is just a normal Ruby expression and that local variables in a default value expression are visible inside the method body:
def my_count(find = (find_passed = false; nil))
if find_passed # find was passed
# do something
else
# do something else
end
end
A second possibility is to use some unforgeable unique token as the default value:
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
Upvotes: 1
Reputation: 67870
You can write def my_count(*args)
and check then length of args
. I'd write:
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
Upvotes: 2