素数を読み上げる。

すっかりゆっくりの声で定着しているhttp://cncc.hp.infoseek.co.jp/を入れてみた。exeに文字列を投げれば、その場で読み上げてくれるので非常に使いやすい。

(defvar *softalk* "SofTalk.exe" "softalkのパス")

(defun call-softalk (&optional (str "ゆ?"))
  (interactive)
  (call-process (format nil "~A ~A" *softalk* (if (interactive-p) (get-clipboard-data) str))))

パスをスペシャル変数に入れておけば、あとは適当に呼び出せる。とりあえず、クリップボードか渡した文字列を読み上げるコマンドを作っておく*1。あとは(call-softalk "ふんちゃら")で評価するなり、M-x call-softalk をするなり。


で、試してみると結構聞き取れるし、数字なんかは無量大数まで読んでくれる。そこで、適当に素数を読み上げるうざいスクリプトを書いてみた。標準では素数判定する関数がないので、適当にミラー–ラビン素数判定法 - Wikipediaを使う。

(defun prime-p (x &optional (k 20))
  "フェルマー法→ミラー-ラビン法の順で素数判定する"
  (labels
      ((modexp2 (n)
         (loop (if (evenp n) (setq n (ash n -1)) (return n))))
       (expmod (b p m)
         (let ((r 1))
           (loop
             (if (oddp p) (setq r (mod (* r b) m)))
             (if (zerop (setq p (ash p -1)))
                 (return r)
               (setq b (mod (* b b) m))))))
       (composite-p (n a d)
         (let ((y (expmod a d n)))
           (loop
             (if (or (zerop (- n y 1))
                     (zerop (- n d 1))
                     (= y 1))
                 (return (and (evenp d)
                              (not (zerop (- n y 1)))))
               (setq y (mod (* y y) n)
                     d (* d 2))))))
       (fermat-prime-p (n &optional (b 2))
         (= (expmod b n n) b))
       (mr-prime-p (n keys)
         (let ((d (modexp2 (1- n))))
           (dolist (a keys t)
             (if (composite-p n a d) (return nil))))))
    (cond
     ((= x 1) nil)
     ((= x 2) t)
     ((or (evenp x)
          (not (fermat-prime-p x))) nil)
     (t (let (keys)
          (mr-prime-p x (dotimes (a k keys) (push (+ (random (- x 3)) 2) keys))))))))

wikiにあったスクリプトrubyを読み慣れてないせいで良く分からなかったのは秘密だ。これを用いて素数を探せば良いんだけど、ちょっと動作が重いので、コンパイルして動作を確認する。

(defun random-num (x)
  "ランダム数を桁指定で発生"
  (let ((num 0) (column 1))
    (while (< -1 (decf x))
    (setq num (+ num (* (random 10) column))
          column (* column 10)))
    num))

> (compile 'prime-p)
prime-p

> (prime-p 17)
t

> (let ((x (random-num 10))) (format t "~A:~A" x (if (prime-p x) "prime" "composite")))
7483941364:composite
nil

また、あまり大きい数はrandomでつくれないので、そのための関数も用意する。ランダム数からではあんまり素数は見つからないけど(例では偶数だし)。実際は奇数にして、2と5の倍数くらい飛ばして探索すれば、ちゃんと見つかる。

(defun seek-prime (d)
  "桁指定で素数探索"
  (long-operation
   (let ((t1 (get-internal-real-time)))
    (let* ((try -1)
           (ti (get-internal-real-time))
           x)
      (loop (when (< 1000 (- (get-internal-real-time) ti))
              (setq ti (get-internal-real-time))
              (format t ".")(refresh-screen)(do-events))
        (when (zerop (mod (incf try) 100)) (format t "?") (setq x (random-num d)) (if (evenp x) (incf x)))
        (if (prime-p x) (return (values x try))
          (if (= (mod x 5) 3) (incf x 4) (incf x 2))))
      (format t "~%elapse ~A ms~%try:~A~%get:~A" (- (get-internal-real-time) t1) try x)))))

> (seek-prime 300)
?..........?...........?..........?...........?..........?........
elapse 69265 ms
try:576
get:228078007758460841141522675428554598943623641685694224034734044684058377250905302970548014500006030768266577036814659433055273877076386369748770904932791550461140825954509834695966882347417588017627459499299441642020758252350779275352230709323553164163590137007591168868856420080338322477577689283721
nil

300桁とかはみ出るな〜。でも普通に計算してくれる。桁数が多いと時間がかかるけど、無量大数程度なら、PCのスペックにもよるけれど、1秒もあれば出るはず。で、実際に動かすのはこっち。

(defun prime-softalk ()
  (interactive)
  (let* ((try (1+ (random 72)))
         (x (random-num try)))
    (incf try (random 10))
    (let ((prime-p
           (loop
             (if (prime-p x) (return t)
               (if (< 0 (decf try)) (incf x)
                 (return nil))))))
      (call-softalk
       (format nil "~Aは、素数~A~A" x
               (if prime-p "だよ。" "じゃないよ。")
               (case (mod x 5)
                 (0 "バカなの? しぬの?")
                 (1 "けらけらけら")
                 (2 "ゆっくりしていってね?")
                 (3 "どうでもいいけど")
                 (t ""))))
      (+ (log x 12) 6))))

(defun toggle-prime-softalk ()
  (interactive)
  (unless (stop-timer 'prime-softalk-loop)
    (prime-softalk-loop)))

(defun prime-softalk-loop ()
  (start-timer (prime-softalk) 'prime-softalk-loop t))

> (toggle-prime-softalk)
t

素数を読み上げるだけじゃ芸がないので、素数じゃないものも適当に読み上げる。淡々と読み続けるから結構うざいよ?

*1:本当はプロセスを取得しておいて、途中で停止できれば良いんだけど、xyzzyのkill-processでは何故か停止できなかった。