对于 Collatz 序列,记忆的重点是避免计算您已经完成的列表部分。序列的其余部分完全由当前值决定。因此,我们希望尽可能多地检查表格,并尽快退出其余的计算。
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
l += table[n]
break
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({n: l[i:] for i, n in enumerate(l) if n not in table})
return l
它在工作吗?让我们监视它以确保正在使用记忆的元素:
class NoisyDict(dict):
def __getitem__(self, item):
print("getting", item)
return dict.__getitem__(self, item)
def collatz_sequence(start, table=NoisyDict()):
# etc
In [26]: collatz_sequence(5)
Out[26]: [5, 16, 8, 4, 2, 1]
In [27]: collatz_sequence(5)
getting 5
Out[27]: [5, 16, 8, 4, 2, 1]
In [28]: collatz_sequence(32)
getting 16
Out[28]: [32, 16, 8, 4, 2, 1]
In [29]: collatz_sequence.__defaults__[0]
Out[29]:
{1: [1],
2: [2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
16: [16, 8, 4, 2, 1],
32: [32, 16, 8, 4, 2, 1]}
编辑:我知道它可以优化!秘密在于函数中有两个地方(两个返回点)我们知道l
并且table
不共享任何元素。虽然之前我通过测试避免调用table.update
已经存在的元素table
,但这个版本的函数反而利用了我们对控制流的了解,节省了大量时间。
[collatz_sequence(x) for x in range(500001, 1000000)]
现在在我的计算机上运行大约 2 秒,而 @welter 版本的类似表达式在 400 毫秒内运行。我认为这是因为这些函数实际上并不计算相同的东西——我的版本生成整个序列,而 @welter 只是找到它的长度。所以我不认为我可以让我的实现速度降低到相同的速度。
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
table.update({x: l[i:] for i, x in enumerate(l)})
return l + table[n]
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({x: l[i:] for i, x in enumerate(l)})
return l
PS - 发现错误!