【问题标题】:Recursion Error for coin change calculator - cannot debug硬币找零计算器的递归错误 - 无法调试
【发布时间】:2020-02-08 17:54:45
【问题描述】:

我正在做一个找零计算器 - 一个函数,它会在返回每种硬币的数量之前计算物品的成本和给收银员的金额,以最佳地返回最少的硬币。

我使用地板除法和模函数以非常简单的方式做到了这一点,但我也很想通过以下方法(用于调试的打印语句)扩展我对递归和定义函数的理解:

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

为 x = 18 和 x = 19 运行 change_calc_2(x, 20) 可以正常工作。结果是 [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]

我希望,在上面日志中的标记点,程序有 left=0(它确实如此),然后点击 if left == 0: return coin ,然后退出 _calc() 的实例。我的想法是当它返回到 _calc() 的父实例时,父实例也会点击 left==0,然后返回到它上面的 _calc() 等等。

显然我的想法是错误的 - 非常感谢任何帮助!我有一种感觉,这是由于我对返回函数的误解,和/或误解了变量的“局部性”与“全局性”

【问题讨论】:

    标签: python recursion


    【解决方案1】:

    首先,我将说明如何修复上面的代码,然后我会给你一个更好的方法来编写这个,如果你正在寻找的话。

    如何解决

    您的错误突然出现,因为您对_calc 的递归调用将编辑coins,但不会 left。这是因为在函数_calc 中,left 已被替换为局部变量。

    我添加了一些“quick-and-dirty”动态缩进来展示它是如何工作的:

    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 &gt; left然后它移动到更小的硬币上。它在left == 0 时返回。如果您给出的 given 或 cost 值错误,则会引发 ValueError,这样left == 0 就不会发生。

    享受吧!

    【讨论】:

    • 梦幻般的响应,这很有帮助(而且创建调试日志的更好方法也非常方便)。谢谢!
    • 别担心!很高兴能提供帮助。
    【解决方案2】:

    您在递归函数中使用了 for 循环,并且相同的条件被计算了两次,即。

    这段代码

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

    将在内部函数内部和外部函数内部进行评估

    在for循环for i, c in enumerate(coin_vals):中具有相同的索引值i=1

    实际上,您首先不需要递归,或者您需要将索引传递给内部函数。

    【讨论】:

      【解决方案3】:
      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 从子递归调用返回后。

      【讨论】:

      • 太棒了,这是我一直在寻找的简单修复,事后看来简单得令人讨厌。谢谢!
      • @qubyt ,如果它解决了您的目的,请投票并接受答案。谢谢
      猜你喜欢
      • 1970-01-01
      • 2023-03-02
      • 1970-01-01
      • 1970-01-01
      • 2020-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多