recursively implementing 'minimum number of coins' in python

前端 未结 5 1314
一整个雨季
一整个雨季 2021-01-21 02:01

This problem is same as asked in here.

Given a list of coins, their values (c1, c2, c3, ... cj, ...), and the total sum i. Find the minimum number of coins the sum

5条回答
  •  一整个雨季
    2021-01-21 02:20

    This is a great algorithms question, but to be honest I don't think your implementation is correct or it could be that I don't understand the input/output of your function, for that I apologize.

    heres a modified version of your implementation.

    def C(i, coins, cdict = None):
        if cdict == None:
            cdict = {}
        if i <= 0:
            cdict[i] = 0
            return cdict[i]
        elif i in cdict:
            return cdict[i]
        elif i in coins:
            cdict[i] = 1
            return cdict[i]
        else:
            min = 0
            for cj in coins:
                result = C(i - cj, coins)
                if result != 0:
                    if min == 0 or (result + 1) < min:
                        min = 1 + result
            cdict[i] = min
            return cdict[i]
    

    This is my attempt at solving a similar problem, but this time returning a list of coins. I initially started with a recursive algorithm, which accepts a sum and a list of coins, which may return either a list with the minimun number of coins or None if no such configuration could be found.

    def get_min_coin_configuration(sum = None, coins = None):
    if sum in coins: # if sum in coins, nothing to do but return.
        return [sum]
    elif max(coins) > sum: # if the largest coin is greater then the sum, there's nothing we can do.
        return None
    else: # check for each coin, keep track of the minimun configuration, then return it.
        min_length = None
        min_configuration = None
        for coin in coins:
            results = get_min_coin_configuration(sum = sum - coin, coins = coins)
            if results != None:
                if min_length == None or (1 + len(results)) < len(min_configuration):
                    min_configuration = [coin] + results
                    min_length = len(min_configuration)
        return min_configuration
    

    ok now lets see if we can improve it, by using dynamic programming (I just call it caching).

    def get_min_coin_configuration(sum = None, coins = None, cache = None):
    if cache == None: # this is quite crucial if its in the definition its presistent ...
        cache = {}
    if sum in cache:
        return cache[sum]
    elif sum in coins: # if sum in coins, nothing to do but return.
        cache[sum] = [sum]
        return cache[sum]
    elif max(coins) > sum: # if the largest coin is greater then the sum, there's nothing we can do.
        cache[sum] = None
        return cache[sum]
    else: # check for each coin, keep track of the minimun configuration, then return it.
        min_length = None
        min_configuration = None
        for coin in coins:
            results = get_min_coin_configuration(sum = sum - coin, coins = coins, cache = cache)
            if results != None:
                if min_length == None or (1 + len(results)) < len(min_configuration):
                    min_configuration = [coin] + results
                    min_length = len(min_configuration)
        cache[sum] = min_configuration
        return cache[sum]
    

    now lets run some tests.

    assert all([ get_min_coin_configuration(**test[0]) == test[1] for test in
    [({'sum':25,  'coins':[1, 5, 10]}, [5, 10, 10]),
     ({'sum':153, 'coins':[1, 5, 10, 50]}, [1, 1, 1, 50, 50, 50]),
     ({'sum':100, 'coins':[1, 5, 10, 25]}, [25, 25, 25, 25]),
     ({'sum':123, 'coins':[5, 10, 25]}, None),
     ({'sum':100, 'coins':[1,5,25,100]}, [100])] ])
    

    granted this tests aren't robust enough, you can also do this.

    import random
    random_sum = random.randint(10**3, 10**4)
    result = get_min_coin_configuration(sum = random_sum, coins = random.sample(range(10**3), 200))
    assert sum(result) == random_sum
    

    its possible that the no such combination of coins equals our random_sum but I believe its rather unlikely ...

    Im sure there are better implementation out there, I tried to emphasize readability more than performance. good luck.

    Updated the previous code had a minor bug its suppose to check for min coin not the max, re-wrote the algorithm with pep8 compliance and returns [] when no combination could be found instead of None.

    def get_min_coin_configuration(total_sum, coins, cache=None):  # shadowing python built-ins is frowned upon.
        # assert(all(c > 0 for c in coins)) Assuming all coins are > 0
        if cache is None:  # initialize cache.
            cache = {}
        if total_sum in cache:  # check cache, for previously discovered solution.
            return cache[total_sum]
        elif total_sum in coins:  # check if total_sum is one of the coins.
            cache[total_sum] = [total_sum]
            return [total_sum]
        elif min(coins) > total_sum:  # check feasibility, if min(coins) > total_sum
            cache[total_sum] = []     # no combination of coins will yield solution as per our assumption (all +).
            return []
        else:
            min_configuration = []  # default solution if none found.
            for coin in coins:  # iterate over all coins, check which one will yield the smallest combination.
                results = get_min_coin_configuration(total_sum - coin, coins, cache=cache)  # recursively search.
                if results and (not min_configuration or (1 + len(results)) < len(min_configuration)):  # check if better.
                    min_configuration = [coin] + results
            cache[total_sum] = min_configuration  # save this solution, for future calculations.
        return cache[total_sum]
    
    
    
    assert all([ get_min_coin_configuration(**test[0]) == test[1] for test in
                 [({'total_sum':25,  'coins':[1, 5, 10]}, [5, 10, 10]),
                  ({'total_sum':153, 'coins':[1, 5, 10, 50]}, [1, 1, 1, 50, 50, 50]),
                  ({'total_sum':100, 'coins':[1, 5, 10, 25]}, [25, 25, 25, 25]),
                  ({'total_sum':123, 'coins':[5, 10, 25]}, []),
                  ({'total_sum':100, 'coins':[1,5,25,100]}, [100])] ])
    

提交回复
热议问题