Object to be returned
You may find it more convenient to return a hash rather than an array of hashes. For example,
645.atm
#=> { 500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>1 }
0.atm
#=> { 500=>0, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0 }
unless there is no way to convert the given amount to different amounts of the given denominations, in which case nil is returned:
646.atm
#=> nil
I assume that, before Integer#atm is called, it has been confirmed that the amount to be changed (self) is a non-negative integer.
Integer#atm method
We may write:
class Integer
def atm
denominations = [500, 200, 100, 50, 20, 10, 5]
change = denominations.product([0]).to_h
amount_to_change = self
denominations.each do |d|
change[d], amount_to_change = amount_to_change.divmod(d)
return change if amount_to_change.zero?
end
nil
end
end
See Array#product, Array#to_h and Integer#divmod, the latter being a very useful, yet underutilized, method.
Try it
Let's try it, but first I will add some puts statements to illustrate the intermediate calculations.
class Integer
def atm
denominations = [500, 200, 100, 50, 20, 10, 5]
change = denominations.product([0]).to_h
puts "change=#{change}"
amount_to_change = self
puts "Initial amount_to_change=#{amount_to_change}"
denominations.each do |d|
change[d], amount_to_change = amount_to_change.divmod(d)
puts "d=#{d}, change=#{change}, amount_to_change=#{amount_to_change}"
return change if amount_to_change.zero?
end
nil
end
end
645.atm
change={500=>0, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}
Initial amount_to_change=645
d=500, change={500=>1, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}, amount_to_change=145
d=200, change={500=>1, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}, amount_to_change=145
d=100, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>0, 10=>0, 5=>0}, amount_to_change=45
d=50, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>0, 10=>0, 5=>0}, amount_to_change=45
d=20, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>0}, amount_to_change=5
d=10, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>0}, amount_to_change=5
d=5, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>1}, amount_to_change=0
#=> {500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>1}
0.atm
change={500=>0, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}
Initial amount_to_change=0
#=> {500=>0, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}
646.atm
change={500=>0, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}
Initial amount_to_change=646
d=500, change={500=>1, 200=>0, 100=>0, 50=>0, 20=>0, 10=>0, 5=>0}, amount_to_change=146
...
d=5, change={500=>1, 200=>0, 100=>1, 50=>0, 20=>2, 10=>0, 5=>1}, amount_to_change=1
#=> nil
Make code more Ruby-like by using Enumerable#each_with_object
You will find that the method Enumerable#each_with_object is widely used in Ruby code. Here it would be used as follows.
class Integer
def atm
denominations = [500, 200, 100, 50, 20, 10, 5]
amount_to_change = self
denominations.each_with_object(denominations.product([0]).to_h) do |d,change|
change[d], amount_to_change = amount_to_change.divmod(d)
return change if amount_to_change.zero?
end
nil
end
end
Use a default value for hash keys
Another change you might consider is use Hash::new to define the hash to have a default value of zero:
change = Hash.new(0)
#=> {}
This merely causes change[k] to return 0 when the hash has no key k. The hash is not altered:
change[500]
#=> 0
change
#=> {}
change[500] = 1
change
#=> {500=>1}
We may now change the method atm as follows.
class Integer
def atm
denominations = [500, 200, 100, 50, 20, 10, 5]
amount_to_change = self
puts "Initial amount to change=#{amount_to_change}"
denominations.each_with_object(Hash.new(0)) do |d,change|
chg, amount_to_change = amount_to_change.divmod(d)
change[d] = chg if chg > 0
puts "d=#{d}, change=#{change}, amount_to_change=#{amount_to_change}"
return change if amount_to_change.zero?
end
nil
end
end
645.atm
Initial amount to change=645
d=500, change={500=>1}, amount_to_change=145
d=200, change={500=>1}, amount_to_change=145
d=100, change={500=>1, 100=>1}, amount_to_change=45
d=50, change={500=>1, 100=>1}, amount_to_change=45
d=20, change={500=>1, 100=>1, 20=>2}, amount_to_change=5
d=10, change={500=>1, 100=>1, 20=>2}, amount_to_change=5
d=5, change={500=>1, 100=>1, 20=>2, 5=>1}, amount_to_change=0
#=> {500=>1, 100=>1, 20=>2, 5=>1}
If we compute change = 645.atm, and need the number of units of a given denomination we simply write, for example,
change[500]
#=> 1
change[50]
#=> 0 (default value)
We must be mindful, however, that
change["cat"]
#=> 0
Avoid polluting the core class Integer
Monkey patching--adding or changing core methods--is generally frowned upon. Better to write, for example,
def atm(amount_to_change)
denominations = [500, 200, 100, 50, 20, 10, 5]
denominations.each_with_object(Hash.new(0)) do |d,change|
chg, amount_to_change = amount_to_change.divmod(d)
change[d] = chg if chg > 0
return change if amount_to_change.zero?
end
nil
end
Another option is to use Refinements.