n倍完全数

自分自身も含めた約数の和が自分自身の n 倍になる数を n 倍完全数というらしい。ということで、n 倍完全数を列挙するプログラムとか。
愚直な解き方。

def divisors(n):
    return (i for i in xrange(1, n + 1) if n % i == 0)

def perfect_numbers(n, limit):
    def pred(x):
        return x * n == sum(divisors(x))

    return (i for i in xrange(1, 1 + limit) if pred(i))

if __name__ == '__main__':
    for i in perfect_numbers(3, 10000):
        print i

i が n の約数なら n / i も n の約数。これなら、自身の平方根までで探索を打ち切れる。

from itertools import count, takewhile

def divisors(n):
    for i in takewhile(lambda i: i * i <= n, count(1)):
        if n % i == 0:
            yield i
            if i * i != n:
                yield n / i

def perfect_numbers(n, limit):
    def pred(x):
        return x * n == sum(divisors(x))

    return (i for i in xrange(1, 1 + limit) if pred(i))

if __name__ == '__main__':
    for i in perfect_numbers(3, 10000):
        print i

i * j == n で i が素数なら n の約数は j の約数 or j の約数 * i になるはず。で、 2 から探して最初に見付かった約数は素数

from itertools import takewhile, count
import operator

def divisors(n):
    for i in takewhile(lambda i: i * i <= n, count(2)):
        if n % i == 0:
            div = divisors(n / i)
            return set(j * i for j in div) | div
    else:
        return set((1, n))

def perfect_numbers(n, limit):
    def pred(x):
        return x * n == sum(divisors(x))

    return (i for i in xrange(2, 1 + limit) if pred(i))
    

if __name__ == '__main__':
    for n in perfect_numbers(3, 10000):
        print n

約数を見付けるより倍数を列挙する方が簡単なので、表を作って、自身の倍数を表の要素に加えていく。

from itertools import takewhile, count

def perfect_numbers(n, limit):
    table = [1] * (limit + 1)

    for i in xrange(2, limit / 2 + 1):
        for j in takewhile(lambda j: i * j <= limit, count(1)):
            table[i * j] += i

    return [i for i, s in enumerate(table) if i * n == s]

if __name__ == '__main__':
    for n in perfect_numbers(3, 10000):
        print n

速度比較。番号はここに列挙している順。

% for i in 1 2 3 4; do time python perfect_numbers$i.py > /dev/null; done
python perfect_numbers$i.py > /dev/null  12.21s user 0.02s system 99% cpu 12.328 total
python perfect_numbers$i.py > /dev/null  0.51s user 0.02s system 97% cpu 0.542 total
python perfect_numbers$i.py > /dev/null  0.41s user 0.02s system 94% cpu 0.456 total
python perfect_numbers$i.py > /dev/null  0.10s user 0.00s system 80% cpu 0.119 total

まぁ、こんなものか。ちなみに、3 番目のは memoize すれば速くなるかと思って、下のような memoize decorator を作ってみたけど全然効果がなかった。

def memoize(func):
    memo = dict()

    def memoized_func(*args):
        if args not in memo:
            memo[args] = func(*args)
        return memo[args]
    return memoized_func