Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Coins all combinations - strange difference between 2 algorithms

I was trying to solve a coin changing problem. Since I live in Europe let’s make it an Euro problem. We need 5 Euro. We can use 10, 20, 50 cents, 1 euro, 2 euro and 5 Euro. How many possibilities there are to get 5 Euro?

That’s the code that leif posted here.

cents = 50
denominations = [50, 20, 10, 5, 2, 1]
names = {50: "", 20: "", 10 : "", 5 : "", 2 : "", 1: ""}

def count_combs(left, i, comb, add):
    if add: comb.append(add)
    if left == 0 or (i+1) == len(denominations):
        if (i+1) == len(denominations) and left > 0:
            comb.append( (left, denominations[i]) )
            i += 1
        while i < len(denominations):
            comb.append( (0, denominations[i]) )
            i += 1
        print " ".join("%d %s" % (n,names[c]) for (n,c) in comb)
        return 1
    cur = denominations[i]
    return sum(count_combs(left-x*cur, i+1, comb[:], (x,cur)) for x in range(0, int(left/cur)+1))

print count_combs(cents, 0, [], None)

It works great, but since this code is very difficult for me (this recursion works here only with the help of magic) I used another code, that is pretty simple for me to understand.

def money(goal, money_avail, money_used):
    if sum(money_used) == goal:
        yield money_used
    elif sum(money_used) > goal:
        pass
    elif money_avail == []:
        pass
    else:
        for change in money(goal,money_avail[:], money_used+[money_avail[0]]):
            yield change
        for change in money(goal,money_avail[1:], money_used):
            yield change


results = []
for s in money(50, [1,2,5,10,20,50], []):
    results.append(s)

cn = 0
for i in results:
    cn+=1
    i = str(i)
    print cn, i

print len(results)

They both gave me the same length of answer - there are 451 possibilities.

def is_5euro(L):
    return sum(L) == 5.0

moneybag = []
for c10 in range(51):
    for c20 in range(26):
        for c50 in range(11):
            for e1 in range(6):
                for e2 in range(3):
                    for e5 in range(2):
                        denom = [c10 * 0.1, c20 * 0.2, c50 * 0.5, e1 * 1, e2 * 2, e5 * 5]
                        if is_5euro(denom):
                            moneybag.append([c10, c20, c50, e1, e2, e5])

print len(moneybag)

But this solution gives us only 446 possibilities.

So I checked what’s the difference between list results and the third (moneybag for for for) algorithm doesn’t seem to be considering as a possibility a situation when we have:

>>>
[[0, 0, 0, 0, 1, 48], [0, 0, 0, 0, 2, 46], [0, 0, 0, 0, 23, 4], [0, 0, 0, 0, 24, 2], [0, 0, 0, 1, 2, 41]]

(48 x 10) + (1 x 20) cents
(46 x 10) + (2 x 20) cents
(4 x 10) + (23 x 20) cents
(2 x 10) + (24 x 20) cents
(41 x 10) + (2 x 20) + (1 x 50) cents

This seems to be ridiculous. Do you know why it doesn't work like the 1st and 2nd algorithms?

like image 839
Tom Wojcik Avatar asked Mar 12 '23 21:03

Tom Wojcik


1 Answers

The problem is Floating Point Arithmetic: Issues and Limitations In Python. Float values are not always they seem to be. You think

(48 x 10) + (1 x 20) cents = 5.0

But originally python considers it 5.000000000000001. Which is not equal to 5.0, hence, is_5euro returns False.

You can test this by

sum([48*0.1, 1*0.2, 0*0.5, 0*1, 0*2, 0*5])

As suggested by @Tom you should use integers. You can change your denom to

denom = [c10 * 10, c20 * 20, c50 * 50, e1 * 100, e2 * 200, e5 * 500]

and your is_5euro to

def is_5euro(cents_list):
    return sum(cents_list) == 500

and it should work.

like image 155
Muhammad Tahir Avatar answered Apr 25 '23 12:04

Muhammad Tahir