Scheme 代入の練習 (素数)
私は、Schemeで代入を使うのが苦手である。
そこで、次のような練習問題を解いてみる。
問題1:10000以上で、2番目に小さい素数を求めよ。
問題2:10000以上で、n番目に小さい素数を求めよ。
準備
(define (square x) (* x x)) (define (divides? a b) (= (remainder b a) 0)) (define (smallest-divisor n) (define (find-divisor n test-divisor) (cond ((> (square test-divisor) n) n) ((divides? test-divisor n) test-divisor) (else (find-divisor n (+ test-divisor 1))))) (find-divisor n 2)) (define (prime? n) (= (smallest-divisor n) n))
prime?は入力が素数であるかどうか判定する。
(prime? 7) => #t (prime? 8) => #f
10000以上で、素数がどのような分布かは、それほど自明ではない。
この問題には、代入を使う方が良いだろう。
http://nibosiiwasi.hatenablog.com/entry/2016/08/11/170848
これと同じような感じで、コードを書いていく。
(create-prime-class-beta には欠陥がある。修正版create-prime-class も書いた。)
欠陥あり!
(define (create-prime-class-beta start) (define (prime-number-count start prime-list counter the-number) (define (next) (begin (if (prime? the-number) (begin (set! prime-list (cons the-number prime-list)) (set! counter (+ counter 1)))) (set! the-number (+ the-number 1)))) (define (search-forward n) (begin (while (< counter n) (next)) (car prime-list))) (define (dispatch m) (cond ((eq? m 'start) start) ((eq? m 'prime-list) prime-list) ((eq? m 'counter) counter) ((eq? m 'the-number) the-number) ((eq? m 's-f) search-forward))) dispatch) (prime-number-count start '() 0 start))
create-prime-class-beta は次のような動作をする。
;10000をスタートとする (define prime-over-10000 (create-prime-class-beta 10000)) => prime-over-10000 ;2番目の素数を求める ((prime-over-10000 's-f) 2) => 10009 ;素数のリスト (prime-over-10000 'prime-list) => (10009 10007)
ここまでは、期待どおりだが、
;5番目の素数を求める ((prime-over-10000 's-f) 5) => 10061 ;素数のリスト (prime-over-10000 'prime-list) => (10061 10039 10037 10009 10007) ;2番目の素数を求めようとするが、 ;素数のリストの先頭が返ってくる。間違った答え! ((prime-over-10000 's-f) 2) => 10061
となる。
解決法は次の2つが考えられる。
- 使い方を限定する。(最初に状態変数を)全て初期化してから実行する。
- コードを修正する。次のような修正を加えればよい:進みすぎている場合には、過去に調べた素数prime-listを探索する。
コードを修正します。
修正版
(define (create-prime-class start) (define (prime-number-count start prime-list counter the-number) (define (next) (begin (if (prime? the-number) (begin (set! prime-list (cons the-number prime-list)) (set! counter (+ counter 1)))) (set! the-number (+ the-number 1)))) ;前(未探索の素数)を探す (define (search-forward n) (begin (while (< counter n) (next)) (car prime-list))) ;後ろ(既に探索した素数)を探す (define (search-back n) (define (lookfor pl k) (if (= k 0) (car pl) (lookfor (cdr pl) (- k 1)))) (let ((k (- counter n))) (lookfor prime-list k))) ;n番目の素数を探す。 (define (find-prime n) (if (> counter n) (search-back n) (search-forward n))) (define (dispatch m) (cond ((eq? m 'start) start) ((eq? m 'prime-list) prime-list) ((eq? m 'counter) counter) ((eq? m 'the-number) the-number) ((eq? m 'find-prime) find-prime))) dispatch) (prime-number-count start '() 0 start))
create-prime-classは、次のように動作する。
これは問題1,問題2 の解答を与える。
;10000をスタートとする (define prime-over-10000 (create-prime-class 10000)) => prime-over-10000 ;初期化されている。当たり前ですが、一応確認。 (prime-over-10000 'prime-list) => () ;2番目の素数を求める ((prime-over-10000 'find-prime) 2) => 10009 ;5番目の素数を求める ((prime-over-10000 'find-prime) 5) => 10061 ;2番目の素数を求める。今度は正しい答え! ((prime-over-10000 'find-prime) 2) => 10009