简体   繁体   中英

make_change algorithm: best way to count out exact change

I just started learning Python and practicing using material from a CS course from Berkeley. This problem is from that class. If someone could either show me how to find the solution or help fix my program, I would be very grateful.

The function I'm having trouble with is make_change; it takes a dictionary with each key being the value of the currency and the values being the amount of that currency you have. The function takes this dictionary and a value and returns the shortest list possible that can be constructed from the given dictionary that lists from lowest to highest the units of currency that add up to exactly the inputted amount. Look at the doc strings for more concrete examples.

When I run doc tests on this function, the actual results of the doc tests give are the correct numbers in the correct order for most of these tests, but many have lists within lists, and the doc tests for making change for 25 gives back None after enough recursive cycles.

Could someone explain to me why this program is giving me back None and how to get rid of the lists within the lists? I would be grateful for any help.

def make_change(amount, coins):
    """Return a list of coins that sum to amount, preferring the smallest
    coins available and placing the smallest coins first in the returned 
    list.  The coins argument is a dictionary with keys that are positive 
    integer denominations and values that are positive integer coin counts.

    >>> make_change(2, {2: 1})
    [2]
    >>> make_change(2, {1: 2, 2: 1})
    [1, 1]
    >>> make_change(4, {1: 2, 2: 1})
    [1, 1, 2]
    >>> make_change(4, {2: 1}) == None
    True
    >>> coins = {2: 2, 3: 2, 4: 3, 5: 1}
    >>> make_change(4, coins)
    [2, 2]
    >>> make_change(8, coins)
    [2, 2, 4]
    >>> make_change(25, coins)
    [2, 3, 3, 4, 4, 4, 5]
    >>> coins[8] = 1
    >>> make_change(25, coins)
    [2, 2, 4, 4, 5, 8]
    """
    if len(coins) == 0:
        return None
    smallest = min(coins)
    rest = remove_one(coins, smallest)
    lst = []
    if smallest == amount:
        return [smallest]
    elif amount - smallest >= smallest:
        amount -= smallest
        lst.extend([smallest] + [make_change(amount, rest)])
        return lst
    elif amount - smallest < smallest:
        return [make_change(amount, rest)]


def remove_one(coins, coin):
    """Remove one coin from a dictionary of coins. Return a new dictionary,
    leaving the original dictionary coins unchanged.

    >>> coins = {2: 5, 3: 2, 6: 1}
    >>> remove_one(coins, 2) == {2: 4, 3: 2, 6: 1}
    True
    >>> remove_one(coins, 6) == {2: 5, 3: 2}
    True
    >>> coins == {2: 5, 3: 2, 6: 1} # Unchanged
    True
    """
    copy = dict(coins)
    count = copy.pop(coin) - 1
    if count:
        copy[coin] = count
    return copy

The make_change function returns either a list or None . That implies two things: (a) you don't need to wrap it in another list; and (b) you need to check for None before concatenating its return value to other lists. If we apply those points to a piece of your code, we get something like this:

change = make_change(amount, rest)
if change is None:
    return None
else:
    return [smallest] + change

The bigger problem (why the function returns None for 25) is caused by your algorithm: it is greedy (consuming smallest coins no matter what) and thus will fail if it marches down an impossible path. You need to add some backtracking logic.

The most straight-forward way to do this is to use recursion. Suppose you have to make change for 100 using coins from the list [14, 7, 3, 1]. If the shortest list uses the 14 coin, it will have length one more than the shortest way to change 86 using the same coins. If it doesn't use the 14 coin, then it will simply be the shortest way to change 100 using coins from [7, 3, 1]. Recursion will take care of the backtracking for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM