【问题标题】:python Reverse Collatz Conjecturepython 逆科拉兹猜想
【发布时间】:2019-01-03 09:32:12
【问题描述】:

程序应该做的是采取步骤和一个数字,然后输出你有多少个独特的序列,恰好是 x steps 来创建 number

有人知道我可以如何节省一些内存吗?因为我应该在 4 秒的限制内处理大量数据。

def IsaacRule(steps, number):
    if number in IsaacRule.numbers:
        return 0
    else:
        IsaacRule.numbers.add(number)
    if steps == 0:
        return 1
    counter = 0
    if ((number - 1) / 3) % 2 == 1:
        counter += IsaacRule(steps-1, (number - 1) / 3)
    if (number * 2) % 2 == 0:
        counter += IsaacRule(steps-1, number*2)

    return counter

IsaacRule.numbers = set()

print(IsaacRule(6, 2))

如果有人知道带有记忆功能的版本,我将不胜感激,现在它可以工作,但仍有改进的空间。

【问题讨论】:

  • 您可以使用记忆缓存加快速度,例如functools.lru_cache。不过,这将使用 more 内存。而且它并没有节省很多递归调用。例如,IsaacRule(40, 1) 我得到了 66588 的计数和这些缓存统计信息:CacheInfo(hits=6057, misses=48335, maxsize=None, currsize=48335)。但我想 12% 左右的缓存命中总比没有好。
  • 顺便说一句,您应该为此使用楼层除法运算符//。在 Python 2 中使用 / 不会有什么坏处,但在 Python 3 中会有所不同。
  • 我刚刚注意到您的第二个if 是多余的:(number * 2) % 2 == 0 对于任何整数都是正确的。但我没有仔细分析你的代码逻辑。你确定它在做正确的事吗?
  • 哈哈很好,谢谢 PM 2Ring - 如果已经为
  • 我们需要在条件检查中使用浮点除法。例如,23 不能由 3n+1 规则生成,而是((23 - 1) // 3) % 2 == 1。或者,此条件应替换为 number % 6 == 4

标签: python collatz


【解决方案1】:

基线:IsaacRule(50, 2) 需要 6.96 秒

0) 使用 LRU 缓存

这使代码花费更长的时间,并给出了不同的最终结果

1) 消除 if 条件:(number * 2) % 2 == 0True

IsaacRule(50, 2) 耗时 0.679 秒。感谢 Pm2Ring 这个。

2) 将((number - 1) / 3) % 2 == 1 简化为number % 6 == 4 并尽可能使用楼层划分:

IsaacRule(50, 2) 耗时 0.499 秒

真值表:

| n | n-1 | (n-1)/3 | (n-1)/3 % 2 | ((n-1)/3)%2 == 1 |
|---|-----|---------|-------------|------------------|
| 1 | 0   | 0.00    | 0.00        | FALSE            |
| 2 | 1   | 0.33    | 0.33        | FALSE            |
| 3 | 2   | 0.67    | 0.67        | FALSE            |
| 4 | 3   | 1.00    | 1.00        | TRUE             |
| 5 | 4   | 1.33    | 1.33        | FALSE            |
| 6 | 5   | 1.67    | 1.67        | FALSE            |
| 7 | 6   | 2.00    | 0.00        | FALSE            |

代码:

def IsaacRule(steps, number):
    if number in IsaacRule.numbers:
        return 0
    else:
        IsaacRule.numbers.add(number)
    if steps == 0:
        return 1
    counter = 0
    if number % 6 == 4:
        counter += IsaacRule(steps-1, (number - 1) // 3)
    counter += IsaacRule(steps-1, number*2)

    return counter

3) 使用集合重写代码

IsaacRule(50, 2) 耗时 0.381 秒

这让我们可以利用为集合所做的任何优化。基本上我在这里进行广度优先搜索。

4) 打破循环,这样我们就可以跳过跟踪以前的状态。

IsaacRule(50, 2) 耗时 0.256 秒

我们只需要添加一个检查 number != 1 以打破唯一已知的循环。这可以加快速度,但如果您从 1 开始,则需要添加一个特殊情况。感谢 Paul 的建议!

START = 2
STEPS = 50

# Special case since we broke the cycle
if START == 1:
    START = 2
    STEPS -= 1

current_candidates = {START} # set of states that can be reached in `step` steps

for step in range(STEPS):
    # Get all states that can be reached from current_candidates
    next_candidates = set(number * 2 for number in current_candidates if number != 1) | set((number - 1) // 3 for number in current_candidates if number % 6 == 4)

    # Next step of BFS
    current_candidates = next_candidates
print(len(next_candidates))

【讨论】:

  • 真的很好,想不出没有递归的方法,即使我还不太明白...为什么 next_candidates = set(...) | set(...) 用 * 2 或其他选择正确的下一个数字
  • 它相当于:for number in current_candidates: next_candidates.append(2 * number); if (... == 4) next_candidates.append(...)。基本上我正在尝试获得current_candidates的所有可能继任者
  • 如果你在第一组理解中过滤掉number != 1,我认为你不需要seen_numbers
  • @PaulHankin 好点,谢谢!严格来说,只有当 Collat​​z 猜想为真时,这才是真的:)。我们还需要一个从 1 开始的特殊情况,但它提供了很好的加速
  • 我发现{n * 2 for n in c - {1}}set(number * 2 for number in current_candidates if number != 1) 更适用于更多的步骤。生成一个带有数字的集合然后在一次之后删除 1 而不是检查每个潜在数字的相等性更便宜
猜你喜欢
  • 2021-11-22
  • 1970-01-01
  • 2011-01-24
  • 2012-10-22
  • 2017-06-28
  • 1970-01-01
  • 2021-10-15
  • 2021-11-20
  • 1970-01-01
相关资源
最近更新 更多