【问题标题】:Reset decorator for recursive function in Python重置 Python 中递归函数的装饰器
【发布时间】:2015-12-29 03:20:54
【问题描述】:

我正在使用递归函数来解决具有完整 KK 算法的分区问题。

我的算法搜索可能的分区树。我希望通过停止特定的树枝来修剪那棵树。我的“修剪器”想要跟踪最小结果(实际上是到目前为止找到的两个子集之和之间的最小差异)。

我的代码是这样的:

def prune(branch):

    def pruner(list_):
         # If only single element in list, reached end of branch
         if len(list_) == 1:
             return list_[0]

         # ... pruning code here ... 
         # Decision to prune depends on pruner.min

         # If didn't prune, calculate as usual
         # tracking minimum
         min_branch = branch(list_)
         pruner.min = min(pruner.min, min_branch)
         return min_branch

     pruner.__name__ = branch.__name__
     pruner.min = float("inf")
     return pruner


 @prune
 def branch(list_):
     # ... code to make two new branches here ...
     return min(branch(list_1), branch(list_2))

对于branch单个 调用,它工作得很好——pruner.min 在我的装饰器的开头设置了一次——然后branch 返回正确的答案。

问题是如果我连续使用该功能两次或更多次。在这种情况下,pruner.min 不会在每次调用后重置,从而导致我决定修剪分支时出现问题。

我怎样才能(优雅地)为我的branch 函数的每个初始(即非递归)调用重置pruner.min = float("inf")?我唯一能想到的是添加一个first 关键字参数def branch(list_, first=True),我将其设置为False 用于branch 函数内的递归调用。我的装饰师可以看看这个论点。这是最好的方法吗?

为了完整起见,这里有一个可运行的示例,可以得出正确的答案。欢迎任何更一般的 cmets。

import copy

def prune(CKK_branch):
    def pruner(list_):

        internal_list = copy.deepcopy(list_)

        # Check whether end of branch or whether optimal solution achieved
        if pruner.min == 0.:
            return pruner.min
        elif len(internal_list) == 1:
            return list_[0]

        # Sort branch
        internal_list.sort(reverse=True)

        # Prune branch
        if pruner.min < internal_list[0] - sum(internal_list[1:]):
            return internal_list[0]

        # Find minimum in branch and track minimum of all branches
        min_branch = CKK_branch(internal_list)
        pruner.min = min(pruner.min, min_branch)

        return min_branch

    pruner.__name__ = CKK_branch.__name__
    pruner.min = float("inf")
    return pruner


@prune
def CKK(list_):
    internal_list = copy.deepcopy(list_)
    # Replace maximum two numbers by their difference and their sum in two
    # branches
    diff = internal_list[0] - internal_list[1]
    sum_ = internal_list[0] + internal_list[1]
    sum_tree = copy.deepcopy(internal_list)
    diff_tree = copy.deepcopy(internal_list)
    del sum_tree[0:2]
    del diff_tree[0:2]
    sum_tree.append(sum_)
    diff_tree.append(diff)

    return min(CKK(diff_tree), CKK(sum_tree)) 

example_list_1 = [1.4,
                  10.1,
                  19.55,
                  11.71,
                  51.7,
                  122.1
                  ]
example_list_2 = [10,
                  5,
                  1,
                  1
                  ]   
print CKK(example_list_1)
print CKK(example_list_2)
print CKK(example_list_1)
# 27.64  # Correct (122.1) - (1.4, ...)
# 3  # Correct (10) - (5, 1, 1)
# 122.1  # Something went wrong

【问题讨论】:

  • 你能添加一个可运行的例子吗?
  • @PadraicCunningham 当然,希望它足够清楚,更长的代码 sn-p 应该可以运行

标签: python recursion python-decorators


【解决方案1】:

您可以将标志设置为 None 并在 CKK 中传递任何值,就像使用默认参数一样:

import copy

def prune(CKK_branch):
    def pruner(list_, flag=None):
        internal_list = copy.deepcopy(list_)
        if flag is None:
            pruner.min = float("inf")
        # Check whether end of branch or whether optimal solution achieved
        if pruner.min == 0.:
            return pruner.min
        elif len(internal_list) == 1:
            return list_[0]
        # Sort branch
        internal_list.sort(reverse=True)    
        # Prune branch
        if pruner.min < internal_list[0] - sum(internal_list[1:]):
            return internal_list[0]  
        # Find minimum in branch and track minimum of all branches
        min_branch = CKK_branch(internal_list)
        pruner.min = min(pruner.min, min_branch)
        return min_branch
    pruner.__name__ = CKK_branch.__name__
    return pruner


@prune
def CKK(list_):
    internal_list = copy.deepcopy(list_)
    # Replace maximum two numbers by their difference and their sum in two
    # branches
    diff = internal_list[0] - internal_list[1]
    sum_ = internal_list[0] + internal_list[1]
    sum_tree = copy.deepcopy(internal_list)
    diff_tree = copy.deepcopy(internal_list)
    del sum_tree[0:2]
    del diff_tree[0:2]
    sum_tree.append(sum_)
    diff_tree.append(diff)
    return min(CKK(diff_tree,True), CKK(sum_tree,True))

每次调用min(CKK(diff_tree,True), CKK(sum_tree,True)),您都会将最小值重置为inf。

In [26]:  CKK(example_list_1)
Out[26]: 27.639999999999993

In [27]:  CKK(example_list_2)
Out[27]: 3

In [28]:  CKK(example_list_1)
Out[28]: 27.639999999999993

【讨论】:

  • 我认为pruner 会调用自己,但prune 应该跟踪每次外部调用的最小值。
  • 谢谢,看来可以了。
【解决方案2】:

为什么不添加另一层?

def prune(f):
    def wrapper(branch):
        def wrapped(branch):
            ...
        wrapper.min = float('inf')
        return wrapped(branch)
    return wrapper

现在您的装饰器将返回一个调用其嵌套函数的闭包。

【讨论】:

  • 这不会太频繁地重置wrapper.min = float('inf')吗?它最终会得到正确的结果,但是通过蛮力,因为它基本上通过在每一步设置wrapper.min = float('inf') 来关闭修剪。
  • 不,因为wrapped 是应该设置和检查.min 的那个,并且是您要递归的那个。
  • 啊但不幸的是wrapped需要wrapper.min的先前值,即wrapper.min = float('inf')必须在每个分区问题开始时只执行一次。
  • @innisfree 这不是问题,因为 wrapped 在被调用之前不会被执行,到那时你已经定义了 wrapper.min
  • 你能做一个可运行的代码,最好是我修改我的完整代码吗?这样做时,添加例如print("pruned") 每次修剪分支时,我们可以看到行为。
猜你喜欢
  • 2022-01-01
  • 2012-06-01
  • 2020-01-05
  • 2020-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多