【问题标题】:Cannot figure out how to memoize subset function无法弄清楚如何记忆子集函数
【发布时间】:2017-08-18 23:02:30
【问题描述】:

我的函数应该找出数字列表的值是否以任何形状或形式加起来到我的目标。我的代码是:

def memoizedSubset(target, numberList, memo):
    ''' Returns True if there exists a subset of numberList that adds
        up to target and returns False otherwise.'''
    if target == 0:
        return True;
    elif numberList == ():
        return False;
    elif (target, numberList) in memo:
        return memo[(target, numberList)];
    elif numberList[0] > target:
        solution = memoizedSubset(target, numberList[1:], memo);
        memo[(target, numberList)] = solution;
        return solution;
    else:
        useIt = memoizedSubset(target - numberList[0], numberList, memo);
        loseIt = memoizedSubset(target, numberList[1:], memo);
        solution = useIt or loseIt;
        memo[(target, numberList)] = solution;
        return solution;

numberTuple = tuple(range(2, 100, 2));
print(memoizedSubset(1234567, numberTuple, {}));

逻辑似乎完美无缺,但是当我尝试运行该函数时,我得到了达到最大递归深度的错误。我使用字典来加速这个过程,因为对于给定的值,没有字典需要一段时间才能完成它。我终其一生都无法弄清楚问题所在。

更新:该代码适用于较小的值,但不适用于较大的值,例如上面的 1234567。记忆化不应该超过递归限制驼峰吗?

【问题讨论】:

  • 您正在尝试使用非常非常深的递归。您正在达到递归深度限制。选择一个递归不那么深的算法。
  • 备忘不是问题。你的记忆逻辑很好。
  • 第一个if 语句应该是:if target == 0: return True。否则没有True 返回。 (0 == 错误)。不是吗?
  • @falsetru 是的,我刚刚在我的代码中修复了这个问题,我在这里快速编辑它
  • “记忆不是应该克服递归限制驼峰吗?” - 不。记忆并非旨在解决递归限制问题。它减少了递归函数进行的冗余递归调用的数量,但不能保证使调用堆栈更浅。

标签: python arrays dictionary recursion tuples


【解决方案1】:

删除记忆逻辑并将print(target, numberList[:5])添加到函数顶部给出:

(1234567, (2, 4, 6, 8, 10))
(1234565, (2, 4, 6, 8, 10))
(1234563, (2, 4, 6, 8, 10))
(1234561, (2, 4, 6, 8, 10))
(1234559, (2, 4, 6, 8, 10))
(1234557, (2, 4, 6, 8, 10))
(1234555, (2, 4, 6, 8, 10))
(1234553, (2, 4, 6, 8, 10))
...

Traceback (most recent call last):
  File "/Users/raymond/Documents/tmp3.py", line 22, in <module>
    print(memoizedSubset(1234, numberTuple, {}));
  File "/Users/raymond/Documents/tmp3.py", line 16, in memoizedSubset
    useIt = memoizedSubset(target - numberList[0], numberList, memo);

这表明递归进行得太慢以至于无法正常工作。

即使添加sys.setrecursionlimit(10000) 也不能缓解问题。

调试代码:

import sys
sys.setrecursionlimit(10000)

def memoizedSubset(target, numberList, memo):
    ''' Returns True if there exists a subset of numberList that adds
        up to target and returns False otherwise.'''
    print(target, numberList[:5])
    if target == 0:
        return 0;
    elif numberList == ():
        return False;
    elif numberList[0] > target:
        solution = memoizedSubset(target, numberList[1:], memo);
        return solution;
    else:
        useIt = memoizedSubset(target - numberList[0], numberList, memo);
        loseIt = memoizedSubset(target, numberList[1:], memo);
        solution = useIt or loseIt;
        return solution;

numberTuple = tuple(range(2, 100, 2));
print(memoizedSubset(1234567, numberTuple, {}));

【讨论】:

  • 是的,但是只有当元组数组中的第一个元素大于目标时才会触发 elif。如果不是,它应该使用该值,因此 target-numberList[0]
  • 这对我来说很好。我很困,所以我可能想错了,但是当新目标足够小时,elif 会被击中,对吧?
  • @user2357112 是的,当它这样做时,它根本不会使用该元素并跳过它。
  • “但添加 sys.setrecursionlimit(10000) 并没有缓解问题” - 在我看来,该算法只是尝试使用有限数量的递归超过 10000 次调用,但不是无限的.
  • 您可以看到它doesn't hit the recursion limit 用于较小的输入。
【解决方案2】:

记忆本身没有问题。但是,如果您使用的是 Python 3.4+,则可以改用 functools.lru_cache(无需自己传递备忘录)。

  • if target == 0: return 0 应该是 if target == 0: return True。否则函数总是返回 False (0 == False);在我发表评论后在问题中进行了编辑。
  • useIt = memoizedSubset(target - numberList[0], numberList, memo) 应该是 useIt = memoizedSubset(target - numberList[0], numberList[1:], memo)
    • numberList 永远不会减少。
  • 假设numberList已排序,如果是numberList[0] &gt; target,您可以立即返回False
  • 您可以将以下几行合并为一条以利用短路的好处(即使 useIt 为 True,否则 loseIt 部分将被执行)

    useIt = memoizedSubset(target - numberList[0], numberList, memo);
    loseIt = memoizedSubset(target, numberList[1:], memo);
    solution = useIt or loseIt;
    

from functools import lru_cache

@lru_cache(None)
def solve(target, numbers):
    # assuming numbers is a sorted tuple of `int`s.
    if target == 0:
        return True
    elif (not numbers) or numbers[0] > target:
        return False
    else:
        return solve(target - numbers[0], numbers[1:]) or \
               solve(target, numbers[1:])

【讨论】:

    猜你喜欢
    • 2017-09-11
    • 2014-12-18
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多