Ruby Training and Ruby on Rails Training exclusively

Making Change

This is a solution to Ruby Quiz #154. The quiz goal is to write a method that takes an amount and an array of coins and returning a minimal list of coins that totals the desired amount.

# A solution to RubyQuiz #154.

# Given an amount and a set of coins, the make_change method finds the
# shortest combination of coins that equals the amount, if it's
# possible.

# See http://www.rubyquiz.com/quiz154.html for details.

# The latest version of this solution can also be found at
# http://learnruby.com/examples/ruby-quiz-154.shtml .

# Basically it does a breadth-first search by using a search tree,
# where the depth of a node in the tree equates to how many coins are
# used.  The search tree is pruned by not building off of nodes with a
# total that exceeds the desired total or nodes that come to a total
# that has already been found by a previous node.  Further, the search
# tree is minimized by avoiding coin combinations that could be
# permutations of others.  This is achieved by only adding coins that
# appear at the same position or later from the coinage as we descend
# the tree.  For example, if the coinage is [25, 10, 5, 1], then
# descending the tree, all 25 coins will appear before all 10 coins,
# and so forth.

# Note: the coinage does not have to be in any particular order (e.g.,
# sorted), but the list of coins returned will have the coins in the
# same order as in the given coinage.

Node = Struct.new("Node", :parent, :coin, :total, :starting_coin)

def make_change(amount, coinage = [25, 10, 5, 1])
  root = Node.new(nil, nil, 0, 0)  # root of tree with no coins
  found_totals = { 0 => root }     # track totals found to prune search
  queue = [root]                   # leaves to process breadth-first

  # process items from queue in a tree breadth-first order until
  # nothing left to process or right total is found
  catch(:found) do
    until queue.empty?
      node = queue.shift
      node.starting_coin.upto(coinage.size - 1) do |index|
        coin = coinage[index]
        new_total = node.total + coin
        next if new_total > amount || found_totals[new_total]  # prune
        new_node = Node.new(node, coin, new_total, index)
        found_totals[new_total] = new_node
        throw :found if new_total == amount
        queue << new_node

  return nil if found_totals[amount].nil?  # no solution found

  # walk up tree and build array of coins used
  result = []
  cursor = found_totals[amount]
  until cursor.coin.nil?
    result << cursor.coin
    cursor = cursor.parent
  result.reverse!  # reverse so coins in same order as coinage provided