How do I eliminate the not relevant outputs of my code?

Viewed 70

This is my code so far:

class Integer
  def atm
    money =  %w[e500 e200 e100 e50 e20 e10 e5]
    return '' if self == 0    
    money.each do |m|        
      value = m.split('e').last.to_i    
      next if self.to_f / value < 1.0    
      anm = self / value
      erg = ("e" + value.to_s)
      return money << {erg => anm}, (self-anm*value).atm
      end
  end
end

puts 140.atm

It gives this output:

e500 e200 e100 e50 e20 e10 e5 {"e100"=>1} e500 e200 e100 e50 e20 e10 e5 {"e20"=>2}

But I just want this:

[{ "e100" => 1 },{ "e20" => 2 }]

How can I eliminate the rest?

2 Answers

You can get the desired output by removing money << from your return statement, i.e.:

return {erg => anm}, (self-anm*value).atm

Which gives:

puts 140.atm
{"e100"=>1}
{"e20"=>2}

However, puts hides the fact that the result is actually a nested array with an empty string:

p 140.atm
[{"e100"=>1}, [{"e20"=>2}, ""]]

To fix this, you can return nil if self is 0:

return nil if self == 0

# or simply

return if self == 0

And use the splat operator * on the second element for return to "flatten" the return value:

return {erg => anm}, *(self-anm*value).atm
#                    ^

Which gives:

p 140.atm
[{"e100"=>1}, {"e20"=>2}]

Another option is to avoid the recursion and to collect the result within the loop, e.g.:

def atm
  money =  %w[e500 e200 e100 e50 e20 e10 e5]

  i = self
  money.each_with_object([]) do |m, result|
    value = m.split('e').last.to_i
    anm, i = i.divmod(value)
    result << { m => anm } if anm > 0
  end
end

I'm using divmod here to calculate the quotient and remainder (actually the modulus) at once. It works like this:

140.divmod(500) #=> [0, 140]
140.divmod(200) #=> [0, 140]
140.divmod(100) #=> [1,  40]
 40.divmod( 50) #=> [0,  40]
 40.divmod( 20) #=> [2,   0]

The result's first element determines who many times the arguments "fits" into the receiver. Its second element is the remainder which also becomes the new left-hand value.

For example:

140.divmod(100) #=> [1,  40]
# means: 140 = 100 * 1 + 40

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.

Related