【问题标题】:Why recursive function is considering last element in an array twice?为什么递归函数两次考虑数组中的最后一个元素?
【发布时间】:2021-08-14 01:59:59
【问题描述】:

试图理解递归函数,因此创建了这个程序,但输出不正确。想了解我在这里做错了什么

class Recursive:
    
    def __init__(self, arr):
        self.arr = arr
        self.sum = 0
        
    def sumRecursive(self):
    
        if len(self.arr) == 0:
            return self.sum
        self.sum = self.arr.pop(0)
        return self.sum + self.sumRecursive()
    
def main():
    recur = Recursive([1,2,3])
    print(recur.sumRecursive())

main()

输出:9

【问题讨论】:

  • 程序员在设计递归算法时,首先要考虑什么?
  • @quamrana 基本案例
  • 那么,你的基本情况是什么?
  • 为什么 sum 是 Recursive 类的成员变量?您可能希望它成为 sumRecursive 函数的局部变量。那里的代码会覆盖每次调用中的 sum 变量,当第一次调用解决时,总和为 3 + 3 + 3。
  • @quamrana if len(self.arr) == 0:

标签: python class recursion


【解决方案1】:

基本原理

您正在使这对自己更具挑战性,因为您将 sum 函数与类纠缠在一起。 sum 函数只需要处理一个列表,但对象的上下文 self 让您难以集中注意力。

递归是一种函数式遗产,这意味着以函数式和模块化的方式编写代码。它更容易读/写小的、单一用途的函数,它促进了程序其他区域的重用。下次需要对另一个类的列表求和时,要不要再重写sumRecursive

编写一次求和函数,然后在需要的地方import -

# mymath.py

def mysum(t):
  if not t:
    return 0
  else:
    return t.pop() + mysum(t)

看看mysum 是如何不关心上下文的,self?为什么要呢?它所做的一切都是对列表的元素求和。

现在编写您的 recursive 模块 -

# recursive.py

from mymath import mysum

class Recursive:
  def __init__(self, arr): self.arr = arr
  def sum(self): return mysum(self.arr)

看看Recursive.sum 是如何将self.arr 交给mysum 的?它不必比这更复杂。 mysum 将适用于每个列表,而不仅仅是 Recursive 模块中的列表。我们不必知道mysum 是如何工作的,这是mymath 模块所关心的。

现在我们编写main 模块。每个模块代表一个抽象的障碍。这意味着一个模块的细节不应该溢出到其他模块中。我们不知道Recursive 实际上是如何对输入求和的,从调用者的角度来看,我们不在乎。这就是Recursive模块的关注点。

# main.py

from recursive import Recursive

recur = Recursive([1,2,3])
print(recur.sum())
6

正常运行

上面我们在您的问题中使用.pop 技术编写了mysum。我这样做是因为这似乎是您现在理解问题的方式。但是请注意当我们这样做时会发生什么 -

x = [1,2,3]
print(mysum(x)) # 6
print(mysum(x)) # 0

为什么mysum 第二次返回不同的答案?因为正如现在所写,mysum 使用了t.pop(),它变异 t。当mysum 运行完毕后,t 就完全清空了!

为什么我们要这样写我们的函数?如果5 + x 每次调用都返回不同的结果怎么办?

x = 3
print(5 + x) # 8
print(5 + x) # 8

如果我们不能依赖不改变的价值观,那该有多烦人。输入的总和,[1,2,3],是6。但正如它所写,输入的总和是返回6 清空输入。清空(更改)输入的第二部分称为副作用。即,期望的效果是求和并清空输入列表是无意的,但我们使用.pop 来计算结果的结果。这不是功能方式。函数式风格意味着避免突变、变量重新分配和其他副作用。

def mysum(t):
  if not t:
    return 0
  else:
    return t[0] + mysum(t[1:])

以这种方式编写时,t 不会更改。这使我们可以使用equational reasoning,从而我们可以将任何函数调用替换为其返回值,并且总是得到正确的答案

x = [1,2,3]
mysum(x)
== 1 + mysum([2,3])
== 1 + 2 + mysum([3])
== 1 + 2 + 3 + mysum([])
== 1 + 2 + 3 + 0
== 1 + 2 + 3
== 1 + 5
== 6

并且x 没有因为运行mysum 而被更改-

print(x)
# [1,2,3]

请注意,Recursive 模块无需进行任何更改即可获得以这种方式重写 mysum 的好处。在更改之前,我们会看到这种行为 -

# main.py

from recursive import Recursive

recur = Recursive([1,2,3])
print(recur.sum())
print(recur.sum())
6
0

因为对sum 的第一次调用将self.arr 传递给mysum,这会清空self.arr 作为副作用。对recur.sum() 的第二次调用将得出一个空的self.arr!修复 mysum 后,我们得到了预期的行为 -

# main.py

from recursive import Recursive

recur = Recursive([1,2,3])
print(recur.sum())
print(recur.sum())
6
6

补充阅读

我已经写了大量关于此答案中使用的技术的文章。按照链接查看它们在其他上下文中的使用,并提供了额外的解释 -

【讨论】:

    【解决方案2】:

    有两种类型的递归需要考虑:尾递归,其中单个递归调用的返回值按原样返回,以及“常规”递归,您可以对递归的返回值执行某些操作在返回之前致电。

    您正在将两者结合起来。您可以将列表中的一个值添加到递归总和中,不使用累加器:

    def non_tail_recursive(self):
        if len(self.arr) == 0:
            return 0
        return self.arr.pop(0) + self.non_tail_recursive()
    

    或者你使用累加器:

    def tail_recursive(self):
        if len(self.arr) == 0:
             return self.sum
    
        self.sum += self.arr.pop(0)
        return self.tail_recursive()
    

    【讨论】:

    • 非常感谢您的解释。很有帮助。
    • 递归方案与我在这里绘制的区别有些正交。
    【解决方案3】:

    要在不重写代码的情况下回答您的问题(因为显然有更好的方法来对数组求和),答案是递归的最后一站调用self.sum + self.sumRecursive,它最后一次命中您的sumRecursive 函数,它返回(来自您的 if 语句)self.sum 这是您列表中的最后一个元素(您已经总结了)。

    当数组为空时,返回0。

    class Recursive:
    
        def __init__(self, arr):
            self.arr = arr
            self.sum = 0
            
        def sumRecursive(self):
    
            if len(self.arr) == 0:
                return 0 
            self.sum = self.arr.pop(0) 
            return self.sum + self.sumRecursive()
    
    
        def main():
            recur = Recursive([1,2,3,4])
            print(recur.sumRecursive())
    
    main()
    

    可以选择将您的 if 移动到我个人认为更有意义的底部:

    def sumRecursive(self):
        self.sum = self.arr.pop(0)  
        if len(self.arr) == 0:
            return self.sum
        else:      
            return self.sum + self.sumRecursive()
    

    【讨论】:

      【解决方案4】:

      您的self.sum 属性是多余的。递归算法处理的信息很少需要成员传递信息。

      class Recursive:
          
          def __init__(self, arr):
              self.arr = arr
              
          def sumRecursive(self):
          
              if not len(self.arr):
                  return 0
              return self.arr.pop(0) + self.sumRecursive()
          
      def main():
          recur = Recursive([1,2,3])
          print(recur.sumRecursive())
      
      main()
      

      输出:6

      【讨论】:

      • 非常感谢您对此的解释。非常感谢。
      【解决方案5】:

      您通常不会使用带状态的对象来实现递归。如果您保持状态,那么您通常根本不需要递归解决方案。

      以下是如何进行“无状态”递归求和。

      def sumRecursive(arr):
          if not arr:
              return 0
          return arr[0] + sumRecursive(arr[1:])
      
      def main():
          print(sumRecursive([1,2,3]))
      
      main()
      

      【讨论】:

      • 非常感谢您的解释。很有帮助。
      猜你喜欢
      • 1970-01-01
      • 2023-01-13
      • 1970-01-01
      • 1970-01-01
      • 2021-07-12
      • 2013-09-23
      • 1970-01-01
      • 2013-07-20
      • 2021-09-20
      相关资源
      最近更新 更多