SICP 読み (21)
修行より戻り、問題 2.27 以降に着手。
問題 2.27
2.18 の reverse を修正、との事。reverse は以下。
(define (reverse l) (let f ((l l) (result '())) (if (null? l) result (f (cdr l) (cons (car l) result)))))
再帰にすると話が早そうだな。そういえば試験ドリブンだった。何がチェックできれば良いかな、というと逆になってるコト、なんですが。
- () は () に
- (1) は (1) に
- (1 2) は (2 1) に
- ((1 2) 3 4) なら (4 3 (2 1)) に
- ((1 2) (3 4)) なら ((4 3) (2 1)) に
- (1 2 3 (4 5) 6 (7 8)) なら ((8 7) 6 (5 4) 3 2 1) に
- (1 (2 3 (4 5)) 6 (7 (8))) は (((8) 7) 6 ((5 4) 3 2) 1) か
なんか試験として、とっても微妙な気がするんですが気のせいですか??
ま、いいや。とりあえずやってみよう。
まず試験を書いてみた。以下。
#!/usr/bin/env gosh (use test.unit) (require "2.27") (define-test-case "'() reverse" ("'() reverse" (assert-equal '() (deep-reverse '()))))
このままでは動かんので、本体も試験を通る形で書いてみる。
(define (deep-reverse l) '() )
これは酷い。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) '() reverse test: . 1 tests, 1 assertions, 1 successes, 0 failures, 0 errors Testing time: 1.85e-4 $
がしかし、通れば良いのだ。(ってをぃ
もう少し試験の追加を。
#!/usr/bin/env gosh (use test.unit) (require "2.27") (define-test-case "'() reverse" ("'() reverse" (assert-equal '() (deep-reverse '())))) (define-test-case "'(1) reverse" ("'() reverse" (assert-equal '(1) (deep-reverse '(1))))) (define-test-case "'(1 2) reverse" ("'() reverse" (assert-equal '(2 1) (deep-reverse '(1 2)))))
この位までなら 2.18 な reverse でも通る。
(define (deep-reverse l) (let f ((l l) (result '())) (if (null? l) result (f (cdr l) (cons (car l) result)))) )
試験。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) '(1 2) reverse: . -- (test case) '(1) reverse: . -- (test case) '() reverse: . 3 tests, 3 assertions, 3 successes, 0 failures, 0 errors Testing time: 3.4599999999999995e-4
オヤツでも食いたい所ではありますが、ぐっと我慢して続行。試験を全部追加します。以下、差分のみを。
(define-test-case "'((1 2) 3 4) reverse" ("'((1 2) 3 4) reverse" (assert-equal '(4 3 (2 1)) (deep-reverse '((1 2) 3 4))))) (define-test-case "'((1 2) (3 4))reverse" ("'((1 2) (3 4)) reverse" (assert-equal '((4 3) (2 1)) (deep-reverse '((1 2) (3 4)))))) (define-test-case "'(1 2 3 (4 5) 6 (7 8)) reverse" ("'(1 2 3 (4 5) 6 (7 8)) reverse" (assert-equal '((8 7) 6 (5 4) 3 2 1) (deep-reverse '(1 2 3 (4 5) 6 (7 8)))))) (define-test-case "'(1 (2 3 (4 5)) 6 (7 (8))) reverse" ("'(1 (2 3 (4 5)) 6 (7 (8))) reverse" (assert-equal '(((8) 7) 6 ((5 4) 3 2) 1) (deep-reverse '(1 (2 3 (4 5)) 6 (7 (8)))))))
このまま試験しても無論通らん。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) '(1 (2 3 (4 5)) 6 (7 (8))) reverse: F expected:<(((8) 7) 6 ((5 4) 3 2) 1)> but was:<((7 (8)) 6 (2 3 (4 5)) 1)> in '() reverse -- (test case) '(1 2 3 (4 5) 6 (7 8)) reverse: F expected:<((8 7) 6 (5 4) 3 2 1)> but was:<((7 8) 6 (4 5) 3 2 1)> in '() reverse -- (test case) '((1 2) (3 4))reverse: F expected:<((4 3) (2 1))> but was:<((3 4) (1 2))> in '() reverse -- (test case) '((1 2) 3 4) reverse: F expected:<(4 3 (2 1))> but was:<(4 3 (1 2))> in '() reverse -- (test case) '(1 2) reverse: . -- (test case) '(1) reverse: . -- (test case) '() reverse: . 7 tests, 7 assertions, 3 successes, 4 failures, 0 errors Testing time: 0.002408 $
結果を見ると中のリストが reverse してません。で、実装としては、cons してる (car l)
がペアかどうかを判断すれば良いかな。まずは単純に再帰的に解決してみる。
(define (deep-reverse l) (let f ((l l) (result '())) (if (null? l) result (f (cdr l) (cons ; (car l) (if (pair? (car l)) (deep-reverse (car l)) (car l)) result)))) )
で、試験を。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) '(1 (2 3 (4 5)) 6 (7 (8))) reverse: . -- (test case) '(1 2 3 (4 5) 6 (7 8)) reverse: . -- (test case) '((1 2) (3 4)) reverse: . -- (test case) '((1 2) 3 4) reverse: . -- (test case) '(1 2) reverse: . -- (test case) '(1) reverse: . -- (test case) '() reverse: . 7 tests, 7 assertions, 7 successes, 0 failures, 0 errors Testing time: 0.0015040000000000001
うむ。(何
しかし、今まで assert-equal しか使ったコト無いってものナニ。
問題 2.28
この問題は修行の時に経験したような気がなんとなくするが、試験ドリブンでやってみましょうね。試験のナニとしては以下か。
- (fringe '()) ;; => ()
- (fringe '(1)) ;; => (1)
- (fringe '(1 2 3)) ;; => (1 2 3)
- (fringe '(1 (2 3)) ;; => (1 2 3)
- (fringe '((1 2) (3 4)) ;; => (1 2 3 4)
- (fringe '((((1))))) ;; => (1)
- (fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9)) ;; => (1 2 3 4 5 6 7 8 9)
で、以下の試験は
#!/usr/bin/env gosh (use test.unit) (require "2.28") (define-test-case "(fringe '())" ("(fringe '())" (assert-equal '() (fringe '())))) (define-test-case "(fringe '(1))" ("(fringe '(1))" (assert-equal '(1) (fringe '(1))))) (define-test-case "(fringe '(1 2 3))" ("(fringe '(1 2 3))" (assert-equal '(1 2 3) (fringe '(1 2 3)))))
こんな感じの実装でも通る。
(define (fringe l) l )
がしかし、これはそろそろ NG ですな。
(define-test-case "(fringe '(1 (2 3)))" ("(fringe '(1 (2 3)))" (assert-equal '(1 2 3) (fringe '(1 (2 3))))))
という事でこんな実装を。
(define (fringe l) (if (null? l) '() (append (if (pair? (car l)) (car l) (list (car l))) (fringe (cdr l)))))
で、試験を。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) (fringe '(1 (2 3))): . -- (test case) (fringe '(1 2 3)): . -- (test case) (fringe '(1)): . -- (test case) (fringe '()): . 4 tests, 4 assertions, 4 successes, 0 failures, 0 errors Testing time: 4.4199999999999996e-4 $
以下のテストケースも盛り込んでみる。
(define-test-case "(fringe '((1 2) (3 4)))" ("(fringe '((1 2) (3 4)))" (assert-equal '(1 2 3 4) (fringe '((1 2) (3 4)))))) (define-test-case "(fringe '((((1)))))" ("(fringe '((((1)))))" (assert-equal '(1) (fringe '((((1)))))))) (define-test-case "(fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9))" ("(fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9))" (assert-equal '(1 2 3 4 5 6 7 8 9) (fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9)))))
で実行。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) (fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9)): F expected:<(1 2 3 4 5 6 7 8 9)> but was:<(1 2 (3 (4 (5) 6) 7) 8 9)> in (fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9)) -- (test case) (fringe '((((1))))): F expected:<(1)> but was:<(((1)))> in (fringe '((((1))))) -- (test case) (fringe '((1 2) (3 4))): . -- (test case) (fringe '(1 (2 3))): . -- (test case) (fringe '(1 2 3)): . -- (test case) (fringe '(1)): . -- (test case) (fringe '()): . 7 tests, 7 assertions, 5 successes, 2 failures, 0 errors Testing time: 0.0018760000000000003
あいやー。失敗。よく考えたら list をぱくっと返しちゃ駄目じゃん。ってコトは修行なナニもダウトかも。
で、以下のように修正。どっかで見たような感じ。
(define (fringe l) (cond ((null? l) '()) ((not (pair? l)) (list l)) (else (append (fringe (car l)) (fringe (cdr l))))))
試験も通りました。
$ test/run-test.scm -vv - (test suite) Default test suite -- (test case) (fringe '(1 (2 (3 (4 (5) 6) 7) 8) 9)): . -- (test case) (fringe '((((1))))): . -- (test case) (fringe '((1 2) (3 4))): . -- (test case) (fringe '(1 (2 3))): . -- (test case) (fringe '(1 2 3)): . -- (test case) (fringe '(1)): . -- (test case) (fringe '()): . 7 tests, 7 assertions, 7 successes, 0 failures, 0 errors Testing time: 8.510000000000001e-4 $
うーん。とりあえず次はハードル高そうなのできちんと考えよう。