Re: Making Change (#154)

My solution uses a queue of combinations of coins. In the basic
algorithm, in each iteration, it dequeues the first element, creates a
new combination combining it with one of each type of coin (i.e.: the
Cartesian product of the set containing just the combination and the set
of coins), and then inserts the new combination into the queue unless
the sum is too large. When it encounters a combination whose sum equals
amount, it saves that combination if it’s smaller than the previous
optimal combination or if the previous optimal combination is nil.

Of course, that algorithm would be extremely slow - worse than a brute
force search due to redundancies. However, I am employing several major
optimizations.

Firstly, there is a min-size heuristic which gives an estimate of how
small the children of a certain combination could be by dividing the
amount of change left by the largest coin less than that amount. If the
ceiling of this number is greater than or equal to the size of an
existing solution, it knows immediately that no child of this branch
could possibly be an optimal solution, and will prune it out
immediately.

Secondly, it is of course faster to evaluate more promising branches
first. Each iteration, I sort the queue my a solution-proximity
heuristic. At the moment, this heuristic is equal to the amount of
change left times the min-size heuristic, although I realized as I was
typing this that just the amount of change left might possibly be
better.

Thirdly, the algorithm as stated above would have quite a few
redundancies, so only coins whose value is less than or equal to the
newest coin in a combination are added.

Nevertheless, my solution still can get extremely slow when the number
of coins needed is a nontrivial number.

class Array
def sum
inject(0){|s,n|s+n}
end
end

def cartesian_product(first, *rest)
return first if rest == []
rest = cartesian_product(*rest)
combs = block_given? ? nil : []
first.each do |v1|
rest.each do |v2|
if block_given?
yield v1+v2
else
combs << (v1 + v2)
end
end
end
combs
end

Infinity = 1.0/0
#Calculates the minimum amount of coins needed to get the amount,
#if fractions of coins were legal.
def min_size_heuristic(amount,comb,coins)
rem = amount-comb.sum
return Infinity if rem < 0
comb.size+rem.to_f/(coins.select{|c|c<=rem&&c<=comb.max}.max) rescue
comb.size
end

#Determines the priority of which combinations of coins to search.
#Multiplies min_size_heuristic by the distance of the amount from the
current sum
def solution_proximity_heuristic(amount,comb,coins)
(amount-comb.sum)*min_size_heuristic(amount,comb,coins)
end

def make_change(amount, coins = [25, 10, 5, 1])
queue =coins.select{|c|c<=amount}.map{|c|[c]}.sort_by{|comb|
solution_proximity_heuristic(amount,comb,coins)}

smallest_change = nil
until queue.empty?
comb = queue.shift
if comb.sum == amount
smallest_change = comb if smallest_change.nil? or comb.size <
smallest_change.size
next
end
combs =
cartesian_product([comb],coins.select{|c|c<=comb.last}.map{|c|[c]})
combs.delete_if {|comb| comb.sum > amount ||
smallest_change != nil &&
min_size_heuristic(amount,comb,coins).ceil >=
smallest_change.size}
queue =
(queue+combs).sort_by{|c|solution_proximity_heuristic(amount,c,coins)}
end
smallest_change
end

  ____________________________________________________________________________________

Never miss a thing. Make Yahoo your home page.
http://www.yahoo.com/r/hs