Project Euler Problem 26
問題26: 1000未満のdにおいて 1/d を小数にした時、循環部の長さがもっとも長くなる d を求めよ。[=>問題文]
問題26の解答
気がつけば do ばっかり使ってる自分がいる。
;; Emacs Lisp (defun problem026 (limit) (let ((recurring-length ; 1/d の循環部の長さを求める関数 (lambda (d) (do ((table (make-hash-table)) (r 1) (count 0 (1+ count)) (result)) ((not (null result)) result) (cond ((zerop r) (setq result 0)) ((gethash r table) (setq result (- count (gethash r table)))) (t (puthash r count table) (setq r (mod (* r 10) d)))))))) ;; メインループ (do ((max-length 0) (result) (i (1- limit) (1- i))) ((<= i max-length) result) (let ((len (funcall recurring-length i))) (when (> len max-length) (setq max-length len) (setq result i)))))) (problem026 1000)
考え方のベースは割り算の筆算です。ここで重要なのは「商」として並ぶ数字列ではなく「余り」の部分。無限小数になるということはいつまでも割り切れない・・・つまり「余り」が0にならないことが条件になります。
筆算をしていった時、以前に出現した「余り」が再び現れると、それ以降は以前と同じ計算が繰り返されることになり、結果として循環した小数になります。同じ「余り」が出現するまでのループ回数を数えれば、それが循環部の長さになります。
recurring-length では、hash table に、「キー:余り、値:ループカウンタ」を保存してゆき、計算した「余り」がすでに登録されていた場合、保存されているカウンタ値と、現在のカウンタ値の差をとることで、求める循環部の長を得ています。
割り算の「余り」はその性質上「割る数」より小さい値にしかなりえません。したがって循環するための「余り」のバリエーションは「割る数」以上にはならないため、循環小数の循環部も「割る数」より長くはなりません。だから 1/d の d は大きい方からチェックを行い、既に計算済みの中での最大長(max-length)より d が小さくなったらそれ以下の d は調べる必要がありません。