At a movie theater a few weeks back, I saw a fun fact on the screen that stated: Did you know that there are 293 ways to make a dollar using US coins? I wondered how a programmer-type like myself would calculate that, and devoted a rainy morning to write some ruby code. Turns out the solution was harder than I thought. My original version was very brute force, was really slow, and dedicated to the US coin set and single dollar amount.
After many refinements, I came up with the Changer module below. This module’s classes lets you set up a currency system and calculate and list the coin permutations that sum to the specified amount. The Currency class not only allows you to work with US coins, but with some monetary research, develop versions for Russian, Swedish and Euro currencies. Using some optimizations, I was able to increase the speed at coming up with all the coin permutations by a factor of 100 over the original version. Calculating permutations for large dollar totals can take a very long time. (try: ruby usa.rb – 10000)
Another enhancement was allowing a subset of coins to be specified on the command line, and also using a total dollar amount other than $1.00.
So, if you’ve ever wondered how many permutations of pennies, dimes and quarters can make $9.75, then this ruby code was written for you.
module Changer
class Currency
attr_reader :tender, :name
def initialize(name)
@name = name
@denoms = []
end
def add_tender(name, face_value, plural=nil)
@denoms << Tender.new(name, face_value, plural)
@denoms.sort! {|e1,e2| e2.value <=> e1.value}
end
def make_change(face_value)
@tender = @denoms.select {|d| d.value <= face_value}
unless @tender.any? {|d|
(face_value/d.value)*d.value == face_value}
puts "#{face_value} cannot be changed with those"
exit
end
@tray = [0]*@tender.length
@regmax = @tender.length-1
@cntr = 0
permute(face_value, 0)
end
private
def permute(sub_tot, rind)
div = sub_tot/@tender[rind].value
div.downto(0) do |v|
@tray[rind]=v
nsub_tot = sub_tot - @tender[rind].total(v)
if nsub_tot == 0
res="#{@cntr+=1}: "
@tray.each_with_index do |q,i|
res << @tender[i].to_s(q); break if i == rind
end
puts res
break if rind == @regmax
next
end
permute(nsub_tot, rind+1) if rind < @regmax
end
end
end
class Tender
attr_reader :name, :value
def initialize(name, face_value, plural=nil)
@name = name
@value = face_value
@plural = plural || @name + 's'
end
def to_s(quantity)
quantity>0 ? "#{quantity} #{quantity == 1 ?
@name : @plural} " : ''
end
def total(quantity)
return @value * quantity
end
end
end
A US Currency driver for the above changer.rb module.
require 'changer'
DENOM = [
['penny', 1, 'pennies'],
['nickel', 5, nil],
['one-dollar', 100, nil],
['quarter', 25, nil],
['dime', 10, nil],
['half-dollar', 50, nil],
['five-dollar', 500, nil],
['ten-dollar', 1000, nil],
['20-dollar', 2000, nil],
['100-dollar', 10000, nil]
]
#
# Usage:
# make change for a dollar
# ruby usa.rb - 100
#
# make change for $12.75 using only pennies, nickels
# and quarters
# ruby usa.rb pnq 1275
#
# note: use first char of DENOM coin name to specify
# coins
#
s=ARGV[0]
if s.nil? || s == '-'
selections = DENOM
else
selections = []
s.scan(/./).each do |c|
e = DENOM.detect {|n,v| n[/^./] == c}
raise "invalid denomination of coin" if e.nil?
selections << e
end
end
m = Changer::Currency.new('usa')
selections.each { |name, val, plur|
m.add_tender(name, val, plur)
}
m.make_change((ARGV[1] || "100").to_i)