3

I have written a Python solution here which solves the following problem: how can a given amount of money n be made with the least number of coins of given the denominations d?

def min_coin_change(n, d):
    mini = float("inf")
    for den in d:
        diff = n - den
        if diff == 0:
            ans = 1
        elif diff < 0:
            ans = float("inf")
        else:
            ans = 1 + min_coin_change(n - den, d)
        mini = min(mini, ans)
    return mini

While my solution works, it takes a very long time when n is larger than 50 or when the length of d is larger than 5. How can I speed up my code so it will work for considerably larger inputs? Am I missing a trick or something which would drastically speed up my code?

LazySloth13
  • 2,369
  • 8
  • 28
  • 37
  • I just added an even simpler method replacing the loop with integer division and modulo. This makes it two lines in the main coinage loop and does not require any recursion. – sabbahillel Feb 27 '14 at 13:11

4 Answers4

2

Note that you should set up your function to start with the largest coin, loop through for the maximum possible number of largest coins, then repeat with the next largest coin.

Thus den should be sorted in decreasing order

def changecoin(d, denval)
  count = 0
  while d > denval:
    d -= denval
    count += 1
  return count, d

now call the recursion with the next lowest value of denval and the new d valueand increment the count appropriately.

newcount = 0
for denval in den:
    count, d = changecoin(d, denval)
    newcount += count

Actually, looking at the code, the changecoin function can be somewhat fixed by eliminating the while so that the inline code can be written

count = 0
for denval in den
  count += d//denval
  d = d % denval

This returns the count and whatever value is left in d. If d is less than denval, the count increment would be 0 and the remaining d is unchanged. While the for loop is unbroken when d becomes 0, there are few enough entries in den that the test can be left out

  if d < 1:
    break
sabbahillel
  • 4,357
  • 1
  • 19
  • 36
  • 1
    so for 30cents (with no nickels) you would take a quarter and five pennies ? – vish Feb 26 '14 at 21:13
  • @vish Why would you specify no nickels? However, if that is what you speify, then that would be the correct answer. – sabbahillel Feb 26 '14 at 21:28
  • no, the correct answer would be 3 dimes. i was giving a counter example. that is why – vish Feb 26 '14 at 21:29
  • @vish good point. The algoritm needs to be redone then to allow for it. perhaps set up a check for that type of case – sabbahillel Feb 27 '14 at 10:44
  • @vish of course, the real world situation does have the 5 as a valid value. An example of a real world case can be Israel where the coinage is [1000, 500, 100, 50, 25, 10, 5, 1] – sabbahillel Feb 27 '14 at 13:08
  • I believe you'll find coinage in israel is 1000,500,200,100,50,10 the 5 and 1 have been retired, and the 25 never existed – vish Feb 27 '14 at 14:49
  • @vish OK thanks. That is what happens when trying to type from an old memory and not doublechecking the current references. I recently saw a math problem in which a person pays with one of each bill and gets change as one of each coin. Question, what was the price of the item? – sabbahillel Feb 27 '14 at 15:54
2

Can we assume that the coin denominations are sensible? The general case of this problem would allow for strange coin denominations like [100, 90, 1] and a simple "greedy" algorithm won't find optimal solutions (e.g. for 180 the optimal solution is two 90-cent pieces, while the simple greedy algorithm would suggest one 100-cent piece and 80 pennies).

If we can assume sensible currency (like any real currency) then I suggest you use a combination of integer division and modulus to find how many of each coin to use.

If we cannot assume sensible currency, then this problem is ideal for a dynamic programming solution. In dynamic programming, you build a table that memoizes intermediate results, which saves a lot of time compared to the simple recursive solution.

There is a nice explanation of dynamic programming in the Skiena book The Alogorithm Design Manual.

http://www.algorist.com/

Here are some links I found online:

http://www.avatar.se/molbioinfo2001/dynprog/dynamic.html

http://www.codechef.com/wiki/tutorial-dynamic-programming

steveha
  • 74,789
  • 21
  • 92
  • 117
  • is 25,10,1 sensible? not all real currencies have the feature that any coin is divisible by all smaller denominations . – vish Feb 26 '14 at 21:25
  • No, `[25, 10, 1]` is not sensible. My definition of "sensible" is "will the simple greedy algorithm work" and if you try to make optimal change for 30 cents you need to use 3 10-cent pieces, while greedy would suggest 1 25-cent piece and 5 pennies. But the real currencies I have seen tend to have a 5-cent piece which would make this "sensible" again. I think the important thing is not which coins are divisible by other coins, but whether there is a smooth continuum of smaller coins to keep making change the greedy way. – steveha Feb 26 '14 at 21:54
  • you do realize though that algorithms should be tailored to fit input and not the other way around – vish Feb 26 '14 at 22:07
1

This is because it is recursive

Read this What is memoization and how can I use it in Python? (and the top 2 answers)

"memoization" remembers things you have calculated already (eg 10 cents) so you don't recalculate them an exponential number of times.

You can copy the Mwmoize class as is, and just "decorate" your function, as explained in the second answer


FOR THE LAZY

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        return self.memo[args]

then add the decorator before the definition

@Memoize
def min_coin_change(n,d)......

the rest of the function is the same

then you call it

min_coin_change(30,(25,10,5,1))
Community
  • 1
  • 1
vish
  • 1,046
  • 9
  • 26
  • 1
    In Python 3.2 and newer, you can use `functools.lru_cache` which is a memoizing decorator. http://docs.python.org/3/library/functools#functools.lru_cache – steveha Feb 27 '14 at 20:51
1

Here is a faster version of your program. I've changed two things:

  • Implementation detail: all recursive programs can be re-written as equivalent iterative programs (in this case, using a for and a while loop). In most languages, the iterative version is faster as there is no need to maintain a stack.

  • Algorithm: I am using a greedy algorithm that starts off with the largest-value coins first, and then tries the smaller coins. This is not guaranteed to be optimal, as pointed out in other answers, but runs very fast (linear in the number of coins in the returned solution). Have a look at this page for an optimal (but slower) dynamic programming solution.

def min_coin_change2(n, d):
    current_amount = 0
    used_coins = []

    for coin_value in sorted(d, reverse=True):
        while n - current_amount >= coin_value:
            current_amount += coin_value
            used_coins.append(coin_value)

    excuse = '' if current_amount == n else ', but that is not what you asked for'
    return 'I can get to %d using %s%s' % (current_amount, used_coins, excuse)

Let's try it out:

print min_coin_change2(101, [50, 20, 10, 1])
Out[25]: 'I can get to 101 using [50, 50, 1]'

and again, when it is impossible to get the desired amount

print min_coin_change2(101, [50, 20, 10])
Out[26]: 'I can get to 100 using [50, 50], but that is not what you asked for'

and again, when the greedy algorithm finds a sub-optimal solution

print min_coin_change2(180, [100, 90, 20])
Out[2]: 'I can get to 180 using [100, 20, 20, 20, 20]'

but the optimal solution would have been [90, 90]

mbatchkarov
  • 15,487
  • 9
  • 60
  • 79