オイラー関数のメモ化 素数表つき

オイラー関数のメモ化

まずはメモ化しない愚直なコード

# euler.py

def gcd(x, y):
    while y != 0:
        x,y = y,x%y
    return x

def euler(n):
    if n==1: return 1
    cnt = 0
    for i in xrange(1, n):
        if gcd(n, i) == 1:
            cnt += 1
    return cnt

def main():
    for n in xrange(1, 10000):
        euler(n)

if __name__ == '__main__':
    main()

実行:

$ time python euler.py 

real	1m45.373s
user	1m41.762s
sys	0m0.080s

Psycoの"import psyco; psyco.profile()"を使って高速化すると:

$ time python euler.py 

real	0m7.349s
user	0m7.000s
sys	0m0.004s

次にちょっとかしこいメモ化なしのコード

# euler2.py
def gcd(x, y):
    while y != 0:
        x,y = y,x%y
    return x

def euler(n):
    if n==1: return 1
    cnt = 0
    for i in xrange(1, n):
        g = gcd(n, i)
        if g == 1:
            cnt += 1
        else:
            if gcd(n/g, i) == 1:
                return euler(n/g) * euler(i)
    return cnt

def main():
    for n in xrange(1, 10000):
        euler(n)

if __name__ == '__main__':
    main()

実行:

$ time python euler2.py 

real	0m21.795s
user	0m21.221s
sys	0m0.028s

Psyco付きだと:

$ time python euler2.py 

real	0m1.543s
user	0m1.516s
sys	0m0.000s

メモ化コード

# euler_memoize.py
# euler(p) = p-1 (p is prime)
# euler(m*n) = euler(m) * euler(n) (m,nは互いに素)

def gcd(x, y):
    while y != 0:
        x,y = y, x%y
    return x

# 樹状再帰するメモ化オイラー関数
cache = {0:0, 1:1, 2:1}
def euler(n):
    global cache
    if n in cache:
        return cache[n]
    cnt = 0
    for i in xrange(1, n):
        g = gcd(i, n)
        if g == 1:
            cnt += 1
        else:
            if gcd(n/g, i) == 1:
                #print "phi(%d) = phi(%d)*phi(%d)" %(n,n/g,i)
                cnt = euler(n/g) * euler(i)
                cache[n] = cnt
                return cache[n]
    cache[n] = cnt
    return cache[n]
    
def main():    
    for x in xrange(1, 10000):
        euler(x)

if __name__ == '__main__':
    main()

実行:

$ time python euler_memoize.py 

real	0m14.088s
user	0m13.897s
sys	0m0.008s

Psyco付き:

$ time python euler_memoize.py 

real	0m0.998s
user	0m0.960s
sys	0m0.008s

速くなった。最後に、明らかに、引数が素数の場合に時間がかかる。オイラー関数(p) = p-1 (p:素数) なので、予めメモしておけばさらに早くなるはず。

# euler(p) = p-1 (p is prime)
# euler(m*n) = euler(m) * euler(n) (m,nは互いに素)

#import psyco; psyco.profile()

def gcd(x, y):
    while y != 0:
        x,y = y, x%y
    return x

# 樹状再帰するメモ化オイラー関数
cache = {0:0, 1:1, 2:1}
def euler(n):
    global cache
    if n in cache:
        return cache[n]
    cnt = 0
    for i in xrange(1, n):
        g = gcd(i, n)
        if g == 1:
            cnt += 1
        else:
            if gcd(n/g, i) == 1:
                #print "phi(%d) = phi(%d)*phi(%d)" %(n,n/g,i)
                cnt = euler(n/g) * euler(i)
                cache[n] = cnt
                return cache[n]
    cache[n] = cnt
    return cache[n]

def fill_prime():
    global cache
    table = range(10000)
    table[1] = 0
    for i in xrange(2, 100):
        for k in xrange(i*2, 10000, i):
            table[k] = 0
    for p in [x for x in table if x != 0]:
        cache[p] = p-1
    
def main():
    fill_prime()
    
    for x in xrange(1, 10000):
        euler(x)

if __name__ == '__main__':
    main()

実行:

$ time python euler_memoize.py 

real	0m0.445s
user	0m0.440s
sys	0m0.008s

Psycoつき

$ time python euler_memoize.py 

real	0m0.113s
user	0m0.112s
sys	0m0.004s

更に速くなった。