Posts Tagged ‘prob31’

Problem 31 – Coins

October 19, 2009

In problem 31, we are given a list of coin values and a target value, and asked how many ways we can make that target value with coins of the given denominations. I think this is probably a pretty standard programming exercise, and I’m embarassed it took me as long as it did. Anyway, here’s what I came up with, nothing too special:

import sys, time

def worker(target, coins):
    """ assume coins are sorted decreasing """
    if target == 0:
        return 1 # there's only one way to not have money?

    # get rid of coins bigger than the target
    while(len(coins) and target < coins[0]):
        coins = coins[1:]

    # some easy cases
    if not len(coins): # all coins are > target
        return 0
    if len(coins) == 1:
        return (target % coins[0] == 0) and 1 or 0

    # there are >=2 types of coins, all <= target
    return sum([worker(target - v, coins[1:])
                for v in xrange(0, target + 1, coins[0])])

def solve(target, coins):
    t = time.time()
    ret = worker(target, coins)
    t = time.time() - t
    return [ret, t]

if __name__ == "__main__":
    print solve(int(sys.argv[1]), map(int,sys.argv[2:]))

I also had fun trying this another way. Instead of breaking down the target amount and doing things recursively, I thought about having a huge amount of coins around. The example problem had a target of 200, with denominations 1, 2, 5, 10, 20, 50, 100, 200. Suppose I had 200/1=200 of the 1s, 200/2 = 100 of the 2s, 200/5 of the 5s, etc. I could then take any number of my 1s, along with any number of my 2s, etc, see how much I have, and see if the total is 200. So I’ve got a product space [0,200/1]\times [0,200/2]\times [0,200/5]\times\cdots\times [0,200/200] of possible combinations, and I want to pick out all those that have a value of 200. This presents a fun application of the map/reduce functions, which I can’t seem to get enough of.

Suppose I start with the list of coins available, [1,2,5,10,20,50,100,200], and my target value in mind. I want to replace each item in this list, say the value c, with the list of values I could make using only $c$-valued coins (and only enough to get to 200). I’m going to do something a little silly, and store each value as it’s own list (this’ll make the next step easier, I think). So I first do

target = 200
coins = [1, 2, 5, 10, 20, 50, 100, 200]
var = map(lambda c: [ [v] for v in xrange(0,target+1,c)], coins)

Next, I want to make the cartesian product of all of these list of lists. I’ll build up my product, working my way through var. That is, I think about L_1\times L_2\times \cdots \times L_k as (((L_1\times L_2)\times L_3)\times \cdots )\times L_k, where each L_i here is a list. This is the sort of thing reduce is for. Keeping in mind that all of my lists are lists of one-element lists (so “+” below is really “concatenate”), I can build the cartesian product via

var = reduce(lambda l,r: [ x+y for x in l for y in r ], var)

Now each element of var is a list with as many values as there are coins, e.g. [7, 6, 0, 40] corresponds to 7 in 1s, 6 in 2s, 0 in 5s, 40 in 10s (of course, in the running example, I need more values in the list, but I hope the point comes across). To find the combined value of the coins in each list, I just need the sum, so I do

var = map(sum, var)

Finally, I only want the values where the sum is my target value, so I filter. We’re asked for the number of ways to make the target value, so I take the length:

var = filter(lambda t: t == target, var)
print len(var)

This is not particularly efficient. In the stated problem, the product of my lists has 1.28 billion elements (significantly larger than the actual answer, which is less than 100,000). That’s rather a lot of space to be taking up. I tried running this code on a target value of 100, and gave up on it after about 20 minutes. For contrast, the first solution above, the recursive solution, takes about half a second on my computer. Still, I like this exercise of getting the cartesian product of a bunch of lists.