3

この問題は、ここで尋ねられたものと同じです。

コインのリスト、それらの値 (c1、c2、c3、... cj、...)、および合計 i が与えられます。合計が i であるコインの最小数を見つける (1 つのタイプのコインを必要な数だけ使用できます)、または合計が S になるようにコインを選択することはできないことを報告します。

昨日動的計画法を紹介されたばかりで、そのコードを作成しようとしました。

# Optimal substructure: C[i] = 1 + min_j(C[i-cj])
cdict = {}
def C(i, coins):

    if i <= 0:
        return 0

    if i in cdict:
        return cdict[i]
    else:
        answer = 1 + min([C(i - cj, coins) for cj in coins])
        cdict[i] = answer
        return answer

ここで、C[i] は金額 i の最適解です。プログラムで使用できるコインは {c1, c2, ... , cj, ...} です。最大再帰深度超過エラーを回避するために、再帰制限を増やしました。しかし、このプログラムは誰かだけが正しい答えを出すものであり、解決できない場合はそれを示しているわけではありません。

コードの何が問題で、どのように修正すればよいですか?

4

5 に答える 5

5

これは素晴らしいアルゴリズムの質問ですが、正直なところ、あなたの実装が正しいとは思いません。または、関数の入力/出力を理解していない可能性があります。そのため、お詫び申し上げます。

あなたの実装の修正版です。

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]

これは同様の問題を解決しようとする私の試みですが、今回はコインのリストを返します。私は最初に再帰アルゴリズムから始めました。このアルゴリズムは合計とコインのリストを受け取ります。このアルゴリズムは、最小数のコインのリストを返すか、そのような構成が見つからない場合は None を返します。

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

では、動的プログラミング (私は単にキャッシングと呼んでいます) を使用して、これを改善できるかどうか見てみましょう。

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]

いくつかのテストを実行してみましょう。

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])] ])

このテストは十分に堅牢ではありませんが、これを行うこともできます。

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

そのようなコインの組み合わせが random_sum に等しい可能性はありますが、その可能性はかなり低いと思います...

より良い実装があると確信しています。パフォーマンスよりも読みやすさを強調しようとしました。幸運を。

以前のコードにはマイナーなバグがあり、最大コインではなく最小コインをチェックすることを前提として更新さ れ、pep8 準拠でアルゴリズムを書き直し、[]代わりに組み合わせが見つからなかった場合に戻ります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])] ])
于 2012-06-11T04:40:28.017 に答える
1

これが楽しい方法です。少しハックですが、それが楽しい理由です。

    import math

    def find_change(coins, value):
        coins = sorted(coins, reverse=True)
        coin_dict = {}
        for c in coins:
            if value % c == 0:
                coin_dict[c] = value / c
                return coin_dict
            else:
                coin_dict[c] = math.trunc(value/ float(c))
                value -= (c * coin_dict[c])

    coins = [1, 5, 10, 25]
    answer = find_change(coins, 69)
    print answer
    [OUT]: {25: 2, 10: 1, 5: 1, 1: 4}

以下は、エッジケース保護付きの注釈を使用した同じソリューションです

    import math
    def find_change(coins, value):
        '''
        :param coins: List of the value of each coin [25, 10, 5, 1]
        :param value: the value you want to find the change for ie; 69 cents
        :return: a change dictionary where the key is the coin, and the value is how many times it is used in finding the minimum change
        '''
        change_dict = {}  # CREATE OUR CHANGE DICT, THIS IS A DICT OF THE COINS WE ARE RETURNING, A COIN PURSE
        coins = sorted(coins, reverse=True)  # SORT COINS SO WE START LOOP WITH BIGGEST COIN VALUE
        for c in coins:
            for d in coins:     # THIS LOOP WAS ADDED BY A SMART STUDENT:  IE IN THE CASE OF IF THERE IS A 7cent COIN AND YOU ARE LOOKING FOR CHANGE FOR 14 CENTS, WITHOUT THIS D FOR LOOP IT WILL RETURN 10: 1, 1: 4
                if (d != 1) & (value % d == 0):
                    change_dict[d] = value / d
                    return change_dict
            if value % c == 0:      # IF THE VALUE DIVIDED BY THE COIN HAS NO REMAINDER,  # ie, if there is no remainder, all the neccessary change has been given  # PLACE THE NUMBER OF TIMES THAT COIN IS USED IN THE change_dict  # YOU ARE FINISHED NOW RETURN THE change_dict
                change_dict[c] = value / c
                return change_dict
            else:
                change_dict[c] = math.trunc(value/ float(c))        # PLACE THAT NUMBER INTO OUR coin_dict  # DIVIDE THE VALUE BY THE COIN, THEN GET JUST THE WHOLE NUMBER  # IE 69 / 25.0 = 2.76  # math.trunc(2.76) == 2    # AND THAT IS HOW MANY TIMES IT WILL EVENLY GO INTO THE VALUE,
                amount = (c * change_dict[c])  # NOW TAKE THE NUMBER OF COINS YOU HAVE IN YOUR  UPDATE THE VALUE BY SUBTRACTING THE c * TIME NUMBER OF TIMES IT WAS USED    # AMOUNT IS HOW MUCH CHANGE HAS BEEN PUT INTO THE CHANGE DICT ON THIS LOOP  # FOR THE CASE OF 69, YOU GIVE 2 25CENT COINS, SO 2 * 25 = 50, 19 = 69 - 50
                value = value - amount              # NOW, UPDATE YOUR VALUE, SO THE NEXT TIME IT GOES INTO THIS LOOP, IT WILL BE LOOKING FOR THE MIN CHANGE FOR 19 CENTS...

    coins = [1, 5, 10, 25]
    answer = find_change(coins, 69)
    print answer
    [OUT]: {25: 2, 10: 1, 5: 1, 1: 4}
    edge_case_coins = [1, 7, 10, 25]
    edge_case_answer = find_change(coins, 14)
    print edge_case_answer
    [OUT]: {7: 2}
于 2014-10-02T18:01:59.573 に答える
1

i < 0コメントが言うように、次のように選択されないように、十分に大きな値を返す必要がありますmin

cdict = {}
def C(i, coins):

    if i == 0:
        return 0

   if i < 0:
        return 1e100    # Return infinity in ideally

    if i in cdict:
        return cdict[i]
    else:
        answer = 1 + min([C(i - cj, coins) for cj in coins])
        cdict[i] = answer
    return answer

関数が 1e100 を返すときはいつでも、解が不可能であることを意味します。

例えば:

$ python2 coins.py 13555 1 5 9
1507  coins
$ python2 coins.py 139 1 5 9
19  coins
$ python2 coins.py 139  5 9
19  coins
$ python2 coins.py 13977  5 9
1553  coins
$ python2 coins.py 13977   9
1553  coins
$ python2 coins.py 139772   9
1e+100  coins

使用法:

python2 coins.py <amount> <coin1> <coin2> ...
于 2012-06-11T04:37:16.130 に答える
0

これは、変更を行うためのアルゴリズムの再帰的で非常に非効率的な実装です。ここVで、 はコインとC目標金額のリストです。

def min_change(V, C):
    def min_coins(i, aC):
        if aC == 0:
            return 0
        elif i == -1 or aC < 0:
            return float('inf')
        else:
            return min(min_coins(i-1, aC), 1 + min_coins(i, aC-V[i]))
    return min_coins(len(V)-1, C)

これは、同じアルゴリズムの動的計画法のバージョンです。

def min_change(V, C):
    m, n = len(V)+1, C+1
    table = [[0] * n for x in xrange(m)]
    for j in xrange(1, n):
        table[0][j] = float('inf')
    for i in xrange(1, m):
        for j in xrange(1, n):
            aC = table[i][j - V[i-1]] if j - V[i-1] >= 0 else float('inf')
            table[i][j] = min(table[i-1][j], 1 + aC)
    return table[m-1][n-1]
于 2012-06-11T04:43:39.070 に答える