Project Euler Problem 21
問題21: 10000未満の友愛数の合計を求めよ。[=>問題文]
素数リスト、素因数分解、約数、メモ化と、これまでに出てきた要素がいろいろ活用できる良い問題ですね。
問題21の解答
;; Emacs Lisp (require 'cl) (require 'math-lib) (defun problem021 (n) (let ((primes (primes-sieve (1+ (truncate (sqrt n))))) (memo (make-vector n -1)) (amicable-numbers)) (do ((m 1 (1+ m))) ((= m n)) (let ((sum (- (apply #'+ (get-divisors m primes)) m))) (aset memo m sum) (when (and (< sum n) (not (= sum m)) (= (aref memo sum) m)) (push m amicable-numbers) (push sum amicable-numbers)))) (apply #'+ amicable-numbers))) (problem021 10000)
素数列挙等のライブラリを改めてまとめた math-lib ファイルを作りました。素数リストを必要とする関数は引数に素数リスト (primes)をとるようにしています。必要な素数の上限がわかる問題なので素数リストは、エラトステネスのふるい (primes-sieve) で作っています。
整数 m の真の約数の和を memo のインデックス m 番目に保存して、友愛数チェックを行っています。
約数リスト作成関数
今回作った約数リスト作成関数です。
(defun get-divisors (n primes) "約数リスト作成" (let ((factors (factorize n primes)) (dest)) (labels ((repeat (lis acc) (if (null lis) (push acc dest) (destructuring-bind (n . count) (car lis) (do ((i 0 (1+ i))) ((< count i)) (repeat (cdr lis) acc) (setq acc (* acc n))))))) (repeat factors 1)) dest))
素因数分解した後、順列を作る要領で再帰関数 repeat で約数を順次作成しています。作成される約数リストはソートされていません。また n 自身もリストに含まれます(真の約数リストではない)。
前掲の problem021 では、この get-divisors の作成したリストの要素の和をとった後、対象の数自身を引くことで「真の約数の和」を得ています。