2013-03-18 6 views
5

Kiedy miałem problemy z wykonaniem Problem 14 in Project Euler, odkryłem, że mogę użyć rzeczy zwanej memoization, aby przyspieszyć mój proces (pozwoliłem jej działać przez dobre 15 minut, i wciąż nie powrócił Odpowiedź). Chodzi o to, jak mogę to zaimplementować? Próbowałem, ale otrzymuję keyerror (zwracana wartość jest nieprawidłowa). To budzi mnie, ponieważ jestem pewien, że mogę zastosować do tego memoize i szybciej to zrobić.Python - Memoization i Collatz Sequence

lookup = {} 

def countTerms(n): 
    arg = n 
    count = 1 
    while n is not 1: 
     count += 1 
     if not n%2: 
     n /= 2 
     else: 
     n = (n*3 + 1) 
     if n not in lookup: 
     lookup[n] = count 

    return lookup[n], arg 

print max(countTerms(i) for i in range(500001, 1000000, 2)) 

Dzięki.

+0

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ż –

+0

Czy to python 2 lub python 3? – Bryce

Odpowiedz

3

Istnieje również niezły sposób rekurencyjny, który prawdopodobnie będzie wolniejszy od rozwiązania poorsod, ale jest bardziej podobny do początkowego kodu, więc może być łatwiejszy do zrozumienia.

lookup = {} 

def countTerms(n): 
    if n not in lookup: 
     if n == 1: 
     lookup[n] = 1 
     elif not n % 2: 
     lookup[n] = countTerms(n/2)[0] + 1 
     else: 
     lookup[n] = countTerms(n*3 + 1)[0] + 1 

    return lookup[n], n 

print max(countTerms(i) for i in range(500001, 1000000, 2)) 
+0

To faktycznie 22 sekundy szybciej niż jego na 1.7 sekundy. Dobra robota, to jest dla mnie łatwiejsze do zrozumienia! Jesteś niesamowity :) – Tetramputechture

+0

Podejrzewam, że moja wersja może być zoptymalizowana! Pracuję nad tym. –

+0

Właściwie to zoptymalizowałem, zamieniając 500001 na 3, ponieważ okazuje się, że jest szybszy, jeśli zaczyna się od mniejszej liczby (więc może łatwo z łatwością buforować numery). – Tetramputechture

3

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!

+0

+1 tam idziemy :) –

+0

Jak mogę uzyskać oryginalny numer sekwencji? – Tetramputechture

+0

@Tetramputechture 'collatz_sequence' zwraca' l', listę wszystkich liczb w sekwencji. 0 elementem zwróconej listy byłby oryginalny numer (ten, który podałeś jako argument dla 'collatz_sequence'). Tak więc 'collatz_sequence (n) [0] == n' dla wszystkich liczb całkowitych' n'. –

-1

To jest moje rozwiązanie PE14:

memo = {1:1} 
def get_collatz(n): 

if n in memo : return memo[n] 

if n % 2 == 0: 
    terms = get_collatz(n/2) + 1 
else: 
    terms = get_collatz(3*n + 1) + 1 

memo[n] = terms 
return terms 

compare = 0 
for x in xrange(1, 999999): 
if x not in memo: 
    ctz = get_collatz(x) 
    if ctz > compare: 
    compare = ctz 
    culprit = x 

print culprit 
+0

Proszę podać prawidłowy kod. Czy możesz wyjaśnić, w jaki sposób twoja wersja odnosi się do innych? – bartekbrak