Problem 1の解答例を読む
Project EulerにProblem1の解答例がアップされていたので読んでみる。
問題は、
10未満の自然数のうち、3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり、これらの合計は 23 になる。
同じようにして、1,000 未満の 3 か 5 の倍数になっている数字の合計を求めよ。
解答例1
僕はこう解答した。
(define (problem1 n) (fold (lambda (n acc) (if (or (zero? (modulo n 3)) (zero? (modulo n 5))) (+ acc n) acc)) 0 (iota n))) (problem1 10) ; 23 (problem1 1000) ; 233168
1から999までのリストを作って、3か5で割り切れたら合計に足していく。
擬似コードで書くと、こんなんらしい。
target=999 sum=0 for i=1 to target do if (i mod 3=0) or (i mod 5)=0 then sum:=sum+i output sum
僕は解答例1の方法で解いた。
でも、
10億未満の・・・と言われたらあっという間にオーバーフローしちゃうよね。
と問題提起。
解答例2
で、解答例2。
target = 999,n=3の場合、
3 + 6 + 9 + 12 + ...... + 999 = 3 * (1 + 2 + 3 + 4 + ... + 333)
となるので、終端をpとして、
p = target div n
(999 div 5の場合、199となる。)
1からpまでの等差数列の和
1 + 2 + 3 + ... + p = 1 ⁄ 2 * p * (p + 1)
合計は、
SumDivisibleBy(3)+SumDivisibleBy(5)-SumDivisibleBy(15)
として求めればいいよね。
「等差数列の和」なるほど。
擬似コードは、
target=999 Function SumDivisibleBy(n) p=target div n return n*(p*(p+1)) div 2 EndFunction Output SumDivisibleBy(3)+SumDivisibleBy(5)-SumDivisibleBy(15)
ふむふむ。
Schemeで書き直し。SumDivisibleByを3回書くのが面倒なので、pは絶対値を取ってmapで求められるように改造してみた。
(define (problem1 n) (let ((sum-divisible-by (lambda (m) (let ((p (quotient n (abs m)))) (quotient (* m p (+ p 1)) 2))))) (apply + (map sum-divisible-by '(3 5 -15))))) (problem1 999) ; 233168 (problem1 99999) ; 2333316668 (problem1 99999999) ; 2333333316666668 (problem1 99999999999) ; 2333333333316666666668
確かに一瞬で求まる。等差数列の和スゲー。
細かいディティールまで読めないのが悲しい。きっちり英語が読めるようになりたいなぁ・・・。