对于Collatz序列而言,记忆化的目的是避免重复计算已经完成的部分。一个序列的余下部分完全由当前值决定。因此,我们希望尽可能经常地检查表格,并在尽早退出余下的计算。
def collatz_sequence(start, table={}):
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l:
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()):
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={}):
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l:
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 - 发现错误点!
keyerror
。 - Jason Sperske