递归和辅助函数

时间:2013-02-28 05:53:27

标签: python

很抱歉,如果这是一般性问题,但我是Python的初学者,很多时候当我看到其他人使用递归代码时,他们为main函数创建一个辅助函数,然后调用自己递归的辅助函数。

这似乎与递归的最简单的情况有点不同,例如(列表的总和,因子),其中函数只调用自身。

有人可以通过示例更仔细地解释这种技术吗?

非常感谢。

示例1 :(使用递归反转链表)

def revert_list(self):
    self.head = self._revert_helper(self.head)


def _revert_helper(self, node):
    temp = None
    if node.forward == None: 
        return node
    else:
        temp = self._revert_helper(node.forward)
        node.forward.forward = node
        node.forward = None
    return temp

示例2 :(二进制搜索树)

def __contains__(self, key):
    return self._bstSearch(self._root, key)

# Returns the value associated with the key.
def valueOf(self, key):
    node = self._bstSearch(self._root, key)
    assert node is not None, "Invalid may key."
    return node.value

# Helper method that recursively searches the tree for a target key:
# returns a reference to the Node. This allows 
# us to use the same helper method to implement
# both the contains and valueOf() methods of the Map class.

def _bstSearch(self, subtree, target):
    if subtree is None:  # base case
        return None
    elif target < subtree.key: # target is left of the subtree root
        return self._bstSearch(subtree.left) 
    elif target > subtree.key: # target is right of the subtree root
        return self.bstSearch(subtree.right) 
    else:                      # base case
        return subtree 

3 个答案:

答案 0 :(得分:7)

这实际上在其他语言中使用得更频繁,因为python通常可以使用可选参数模拟该行为。这个想法是递归获得了许多初始参数,用户不需要提供,这有助于跟踪问题。

def sum(lst):
    return sumhelper(lst, 0)

def sumhelper(lst, acc):
    if lst:
        acc += lst[0]
        return sumhelper(lst[1:], acc)
    return acc

这里用于将起始参数设置为0,因此用户无需提供它。但是,在python中,您可以通过使acc可选:

来模拟它
def sum(lst, acc=0):
    if lst:
        acc += lst[0]
        return sum(lst[1:], acc)
    return acc

答案 1 :(得分:4)

通常当我这样做时,这是因为递归函数调用很棘手或烦人,所以我有一个更方便的包装器。例如,想象一个迷宫求解器函数。递归函数需要一个数据结构来跟踪迷宫内的访问点,但为了方便调用者,我只是希望调用者需要传入一个迷宫来解决。您可以使用Python中的默认变量处理此问题。

我这样做的另一个主要原因是速度。递归函数非常信任,并假设它的参数都是有效的;它只是全速前进递归。然后,在第一次调用递归函数之前,包装函数会仔细检查所有参数。作为一个简单的例子,factorial:

def _fact(n):
    if n == 0:   # still need to handle the basis case
        return 1
    return n*_fact(n-1)

def fact(n):
    n0 = int(n)
    if n0 != n:
        raise ValueError("argument must make sense as an int")
    if n < 0:
        raise ValueError("negative numbers not allowed")
    return _fact(n)

我已经从原版编辑了这个,现在它实际上是一个非常合理的例子。我们将参数强制转换为整数(“duck typing”)但我们要求!=运算符不要指示它通过此强制改变了值;如果将其转换为int会更改值(例如,截断了小数部分的float值),我们会拒绝该参数。同样,我们检查否定并拒绝参数。然后实际的递归函数非常信任,根本不包含任何检查。

如果你发布了一个你看过激发这个问题的代码的例子,我可以给出不那么模糊的答案。

编辑:好的,讨论你的例子。

  • 示例1 :(使用递归反转链表)

非常简单:“helper”函数是一个通用递归函数,可以在具有链表的类中的任何节点上运行。然后包装器是一个方法函数,它知道如何查找列表的头部self.head。这个“帮助器”是一个类成员函数,但它也可以是一般数据结构东西库中的一个简单函数。 (这在Python中比在像C这样的语言中更有意义,因为像这样的函数可以使用任何链接列表,它是一个名为forward的成员作为其“下一个指针”值的类。所以你真的可以写这一次然后将它与多个实现链表的类一起使用。)

  • 示例2 :(二进制搜索树)

如果找不到具有指定None的节点,则实际递归函数返回key。然后有两个包装器:一个实现__contains__()的包装器,如果它返回None就可以正常工作;和valueOf(),如果找不到密钥则会引发异常。正如评论所指出的,两个包装器让我们用一个递归函数解决两个不同的问题。

此外,与第一个示例一样,两个包装器在特定位置启动搜索:self._root,树的根。实际的递归函数可以在树内的任何地方开始。

如果使用要搜索的节点的默认参数实现__contains__(),并且默认值设置为某个唯一值,则可以检查特殊值并在该情况下从根开始。然后,当正常调用__contains__()时,将传入唯一值,并且递归函数可以知道它需要查看特殊位置self._root。 (您不能只传入self._root作为默认值,因为默认值是在编译时设置的,并且类实例可以在此之后更改,因此它无法正常工作。)

class UniqueValue:
    pass

def __contains__(self, key, subtree=UniqueValue):
    if subtree is UniqueValue:
        subtree = self._root

    if subtree is None:  # base case
        return None
    elif key < subtree.key: # target is left of the subtree root
        return self.__contains__(key, subtree.left) 
    elif key > subtree.key: # target is right of the subtree root
        return self.__contains__(key, subtree.right) 
    else:                      # base case
        return subtree

请注意,虽然我说可以实现,但我没有说我更喜欢它。其实我更喜欢两种包装版本。这有点棘手,每次递归调用检查都会浪费时间来查看是否subtree is UniqueValue。更复杂和浪费时间......不是一场胜利!只需编写两个包装器,它们就会在正确的位置启动它。简单。

答案 2 :(得分:3)

根据我的经验(以及我的经验),我在

时使用这种编码方式
  1. 递归仅适用于较大的函数(不是很推荐,但我有一些不良习惯)

  2. 需要为该功能做好准备,但只需要做一次(而不是标志或其他开关)

  3. 我使用它的一种方法是用于记录目的,同时避免重新记录级别

    
    def _factorial(x):
        return 1 if x == 0 else x*_factorial(x)
    
    @log #assuming some logging decorator "log"
    def factorial(x):
        return _factorial(x)
    

    否则,对于阶乘函数的每个递归级别都会调用log,这是我可能不希望的。

    另一种用法是解决默认参数。

    def some_function(x = None):
        x = x or set() #or whatever else
        #some stuff
        return some_function()
    

    检查x是否每次迭代都是假的,而我实际需要的是装饰器,或作为替代:

    def some_function(x = None):
       return _some_function(x if x else set())
    

    其中_some_function是辅助函数。

    特别是2,它允许一些抽象自由。如果由于某种原因你不想使用bstsearch,你可以将它换成__contains__中的其他函数(你也可以在不同的地方重用代码)