Ошибка рекурсии для калькулятора замены монет - не удается отладить - PullRequest
0 голосов
/ 11 октября 2019

Я делаю калькулятор изменений - функция, которая принимает стоимость предмета и сумму денег, переданную кассиру, перед возвратом количества монет каждого типа для оптимального возврата минимального количества монет.

Я сделал это довольно простым способом, используя функции деления по полу и по модулю, но мне также было любопытно расширить мое понимание рекурсии и определения функций внутри функций, выполнив следующий метод (операторы print для отладки):

def change_calc_2(cost, given): # input in full £xx.xx
    coin_vals = [200, 100, 50, 20, 10, 5, 2, 1]
    coins = [0] * len(coin_vals)

    cost = cost * 100 # pence
    given = given * 100
    left = given - cost

    def _calc(left):

        for i, c in enumerate(coin_vals):
            print("for loop level:", i)
            print("Amount left at start of kernel:", left)
            print("Coin val in question:", c)

            if left == 0:
                return coins # early exit routine

            elif c > left:
                continue

            else:
                coins[i] += 1
                print("coin added:", c)
                left -= c
                print("Amount left at end of kernel:", left)
                _calc(left)

        return coins

    #_calc(left)
    return _calc(left)       
    #return coins

print(change_calc_2(17, 20))

Запуск change_calc_2 (x, 20) для x = 18 и x = 19, работает нормально. Результаты [1 0 0 0 0 0 0 0] (одна монета £ 2), и [0 1 0 0 0 0 0 0] (одна монета £ 1).

Однако, когда япопробуйте x = 17 и ожидайте [1 1 0 0 0 0 0 0], я получаю [1 2 0 0 0 0 0 0].

Отладка печати дает следующее:

for loop level: 0
Amount left at start of kernel: 300
Coin val in question: 200
coin added: 200
Amount left at end of kernel: 100   ##### Completion of one round of for loop, as expected
for loop level: 0
Amount left at start of kernel: 100
Coin val in question: 200          ##### Rejects the 200p coin as too big for the amount left
for loop level: 1
Amount left at start of kernel: 100
Coin val in question: 100
coin added: 100                   #### Adds the last coin expected, so 100p + 200p in total
Amount left at end of kernel: 0 
for loop level: 0               
Amount left at start of kernel: 0 ############ <----- Running as expected until here
Coin val in question: 200         ************** <--- Should break just after this point?
for loop level: 2
Amount left at start of kernel: 0
Coin val in question: 50
for loop level: 1
Amount left at start of kernel: 100  ########### <--- Wtf? How has it added on money?
Coin val in question: 100
coin added: 100
Amount left at end of kernel: 0
for loop level: 0
Amount left at start of kernel: 0
Coin val in question: 200
for loop level: 2
Amount left at start of kernel: 0
Coin val in question: 50
[1 2 0 0 0 0 0 0]

Я ожидаю, что в отмеченных точках в журналах выше программа оставит = 0 (что и делает), а затем нажмете, если осталось == 0: верните монеты, а затем выйдите из экземпляра _calc (). Мои мысли о том, как он возвращается в родительский экземпляр _calc (), родитель тоже попадет влево == 0 и вернется к _calc () над ним и т. Д.

Очевидно, что мое мышление неверно- любая помощь с благодарностью! У меня такое ощущение, что это из-за моего неправильного понимания функции возврата и / или неправильного понимания "локальности" и "глобальности" переменных

Ответы [ 3 ]

0 голосов
/ 11 октября 2019
def change_calc_2(cost, given): # input in full £xx.xx
    coin_vals = [200, 100, 50, 20, 10, 5, 2, 1]
    coins = [0] * len(coin_vals)

    cost = cost * 100 # pence
    given = given * 100
    left = given - cost

    def _calc(left):

        for i, c in enumerate(coin_vals):
            print("for loop level:", i)
            print("Amount left at start of kernel:", left)
            print("Coin val in question:", c)

            if left == 0:
                return coins # early exit routine

            elif c > left:
                continue

            else:
                coins[i] += 1
                print("coin added:", c)
                left -= c
                print("Amount left at end of kernel:", left)
                return _calc(left)   #### <----- This was the bug

        return coins

    #_calc(left)
    return _calc(left)       
    #return coins

print(change_calc_2(17, 20))

Вы didn't put the return заявление.
Итак, ваш loop continued for the further iterations после возвращения из дочернего рекурсивного вызова.

0 голосов
/ 11 октября 2019

Сначала я скажу, как вы можете исправить свой код выше, затем я дам вам немного лучший способ кодировать это, если это то, что вы ищете.

Какчтобы исправить это

Ваша ошибка появляется, потому что ваш рекурсивный вызов _calc отредактирует coins, но не left. Это связано с тем, что в функции _calc left был заменен локальной переменной.

Я добавил несколько динамических отступов «быстро и грязно», чтобы показать, как это работает:

def change_calc_2(cost, given): # input in full £xx.xx
    coin_vals = [200, 100, 50, 20, 10, 5, 2, 1]
    coins = [0] * len(coin_vals)

    cost = cost * 100 # pence
    given = given * 100
    left = given - cost

    def _calc(left,recur_level):
        print("{}recursion level:".format(' '*4*recur_level), recur_level)
        for i, c in enumerate(coin_vals):
            print("    {}for loop level:".format(' '*4*recur_level), i)
            print("    {}Amount left at start of kernel:".format(' '*4*recur_level), left)
            print("    {}Coin val in question:".format(' '*4*recur_level), c)

            if left == 0:
                print("{}EXIT recursion level:".format(' '*4*recur_level), recur_level)
                return coins # early exit routine

            elif c > left:
                continue

            else:
                coins[i] += 1
                print("    {}coin added:".format(' '*4*recur_level), c)
                left -= c
                print("    {}Amount left at end of kernel:".format(' '*4*recur_level), left)
                _calc(left,recur_level+1)

        return coins

    #_calc(left)
    return _calc(left,0)       
    #return coins

Если мы быстро запустим это:

>>> change_calc_2(17,20)
recursion level: 0
    for loop level: 0
    Amount left at start of kernel: 300
    Coin val in question: 200
    coin added: 200
    Amount left at end of kernel: 100      #<----- These values are the same
    recursion level: 1
        for loop level: 0
        Amount left at start of kernel: 100
        Coin val in question: 200
        for loop level: 1
        Amount left at start of kernel: 100
        Coin val in question: 100
        coin added: 100
        Amount left at end of kernel: 0
        recursion level: 2
            for loop level: 0
            Amount left at start of kernel: 0
            Coin val in question: 200
        EXIT recursion level: 2
        for loop level: 2
        Amount left at start of kernel: 0
        Coin val in question: 50
    EXIT recursion level: 1
    for loop level: 1
    Amount left at start of kernel: 100   #<----- These values are the same
    Coin val in question: 100
    coin added: 100
    Amount left at end of kernel: 0
    recursion level: 1
        for loop level: 0
        Amount left at start of kernel: 0
        Coin val in question: 200
    EXIT recursion level: 1
    for loop level: 2
    Amount left at start of kernel: 0
    Coin val in question: 50
EXIT recursion level: 0

Ваша ошибка не позволяет корректно обновить left. Если вы хотите использовать рекурсию, убедитесь, что вы просто правильно вложили свои вызовы:

def change_calc_rec(cost, given): # input in full £xx.xx
    coin_vals = [200, 100, 50, 20, 10, 5, 2, 1]
    coins = [0] * len(coin_vals)

    cost = cost * 100 # pence
    given = given * 100
    left = given - cost

    def _calc(left,coin_index):
        i = coin_index
        c = coin_vals[i]
        print("{}coin_index:".format(' '*4*i), i)
        while True:
            if left == 0:
                print("{}EXIT recursion level:".format(' '*4*i), i)
                return coins # early exit routine
            elif c > left:
                break
            else:
                coins[i] += 1
                print("    {}coin added:".format(' '*4*i), c)
                left -= c
                print("    {}Amount left at end of kernel:".format(' '*4*i), left)

        if coin_index < len(coin_vals):
            return _calc(left,coin_index+1)
        else:
            return coins

    #_calc(left)
    return _calc(left,0)       
    #return coins

Какой ваш уровень рекурсии должен совпадать с индексом монет. Раньше вы доходили до индекса, где ваша монета была больше требуемого изменения, затем запускали новый цикл с индексом 0. Выше это должно исправить.

Без рекурсии

Для этой проблемы рекурсия вообще не нужна. Вы можете кодировать его более безопасным способом, используя цикл while:

def change_calc(cost, given): # input in full £xx.xx
coin_vals = [200, 100, 50, 20, 10, 5, 2, 1]
coins = [0] * len(coin_vals)

cost = cost * 100 # pence
given = given * 100
left = given - cost

for i, c in enumerate(coin_vals):
    while True:
        if left == 0:
            return coins # early exit routine
        elif c > left:
            break
        else:
            coins[i] += 1
            left -= c
raise ValueError('Bad given or cost!')

Это будет продолжать цикл while до тех пор, пока c > left, , затем не перейдет на меньшие монеты. Возвращается, когда left == 0. Возникает ошибка ValueError, если вы даете неверное значение стоимости или стоимости, например, left == 0 никогда не происходит.

Наслаждайтесь!

0 голосов
/ 11 октября 2019

вы используете цикл for внутри рекурсивной функции, и одно и то же условие оценивается дважды, т. Е.

Этот код

        coins[i] += 1
        print("coin added:", c)
        left -= c
        print("Amount left at end of kernel:", left)
        _calc(left)

будет оцениваться внутри внутренней функции, а также внутривнешняя функция

с тем же значением индекса i=1 в цикле for for i, c in enumerate(coin_vals):.

По сути, вам не нужна рекурсия, или вам нужно передать индекс во внутреннюю функцию.

...