Celem zapamiętywania, dla sekwencji Collatza, jest uniknięcie obliczania części listy, które już wykonałeś. Pozostała część sekwencji jest w pełni określona przez bieżącą wartość. Dlatego chcemy sprawdzać tabelę tak często, jak to możliwe, i wyskakując z reszty obliczeń tak szybko, jak to możliwe.
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
Czy to działa? Załóżmy szpiegować na to, aby upewnić się, że memoised elementy są używane:
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]}
EDIT: wiedział mogło być zoptymalizowane! Sekretem jest to, że istnieją dwa miejsca w funkcji (dwa punkty zwrotne), które znamy: l
i table
nie udostępniają żadnych elementów. Podczas gdy poprzednio unikałem wywoływania table.update
z elementami już w table
przez testowanie ich, ta wersja funkcji wykorzystuje zamiast tego naszą wiedzę o strumieniu sterowania, oszczędzając dużo czasu.
[collatz_sequence(x) for x in range(500001, 1000000)]
teraz około 2 sekund na moim komputerze, podczas gdy podobne wyrażenie z zegarkami wersji @ welter w ciągu 400ms. Myślę, że dzieje się tak dlatego, że funkcje nie obliczają dokładnie tego samego - moja wersja generuje całą sekwencję, podczas gdy @speed po prostu znajduje swoją długość. Nie sądzę więc, żebym mógł tak szybko wdrożyć moją implementację.
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 - znajdź błąd!
Myślałem punkt memoization było zobaczyć, czy nie było najpierw obliczona wartość, a jeśli nie, to aby obliczyć go i przechowywać go. Wygląda na to, że go przechowujesz, ale nigdy nie testujesz, aby sprawdzić, czy nie musisz go ponownie obliczać. Moja obserwacja nie wyjaśnia 'keyerror' chociaż –
Czy to python 2 lub python 3? – Bryce