SICP 読み (80) 2.5.3 例: 記号代数
この_SICP 読み_というシリーズ、回数カウントしてるんですが、きちんと継続できれば 3 桁いくのは間違いないな。それは良いとして 2.89 の続きを。
問題 2.89
これ、もの凄く紆余曲折しています。読みづらいかもしれませんが、時系列で。
まず、直前エントリのソレを整理
- empty-termlist?
- リストの要素が全部 0 なら #t
- 空リストだったら #t
- それ以外は #f
- first-term
- リストの length - 1 とリストの car をリストにして返す
- rest-terms
- order
- coeff
- 同じでよい
- make-term
- 第二引数をそのまま返却
- adjoin-term
- (cons 第一引数 第二引数)
- the-empty-termlist
- '() を返せばよい (同じ)
- =zero?
- これもそのままで OK
という事でまず試験を検討。結構直接的な表現してる試験が多い。
term-list の理解が間違ってるかもしれませんが、以下に試験を。
#!/usr/bin/env gosh (use test.unit) (require "2.5.3") (define-test-suite "2.5.3" ("2-88" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("first (1x - 1x = 0)" (assert-true (=zero? (add (make-polynomial 'x '(1 0)) (neg (make-polynomial 'x '(1 0)))))) ) ("2nd (1x - 0 = 1x)" (let ((p (make-polynomial 'x '(1 0)))) (assert-equal p (add p (neg (make-polynomial 'x '()))))) ) ("3rd (1x - 0x = 1x" (let ((p1 (make-polynomial 'x '(1 0))) (p0 (make-polynomial 'x '(0 0)))) (assert-equal p1 (add p1 (neg p0)))) ) ("4th polynomial neg" (let ((p1 (make-polynomial 'x '(1 2 3 4))) (p2 (make-polynomial 'x '(-1 -2 -3 -4)))) (assert-equal p1 (neg p2)) (assert-equal p2 (neg p1))) ) ) ("2-87" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("() is zero" (assert-true (=zero? (make-polynomial 'x '()))) ) ("() is zero (2)" (assert-true (=zero? '(polynomial x))) ) ("(0 0 0 0) is zero" (assert-true (=zero? '(polynomial x 0 0 0 0))) ) ("(y + 1)x + (-y - 1)x is zero" (let ((py1 (make-polynomial 'y '(1 1))) (py2 (make-polynomial 'y '(-1 -1)))) (assert-true (=zero? (add (make-polynomial 'x (list py1 0)) (make-polynomial 'x (list py2 0)))))) ) ) ("make-polynomial test" (setup (lambda () (install-polynomial-package))) ("car is 'polynomial" (assert-equal 'polynomial (car (make-polynomial 'var 'terms))) ) ("cadr is var" (assert-equal 'var (cadr (make-polynomial 'var 'terms))) ) ("cddr is terms" (assert-equal 'terms (cddr (make-polynomial 'var 'terms))) ) ) ("add test (1)" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("different variable" (assert-error (lambda () (add (make-polynomial 'x '(0 5)) (make-polynomial 'y '(0 5))))) ) ("add constant" (let ((test (add (make-polynomial 'x '(1)) (make-polynomial 'x '(2))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(3) (cddr test))) ) ("result 0" (let ((test (add (make-polynomial 'x '(0)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("add 0 to 5 (1)" (let ((test (add (make-polynomial 'x '(0)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 0 to 5 (2)" (let ((test (add (make-polynomial 'x '()) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 5 to 0 (1)" (let ((test (add (make-polynomial 'x '(5)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 5 to 0 (2)" (let ((test (add (make-polynomial 'x '(5)) (make-polynomial 'x '())))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("normal (2x + 3) + (x^2 + x + 2) -> x^2 + 3x + 5" (let ((test (add (make-polynomial 'x '(2 3)) (make-polynomial 'x '(1 1 2))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(1 3 5) (cddr test))) ) ) ("mul test" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("different variable" (assert-error (lambda () (mul (make-polynomial 'x '(5)) (make-polynomial 'y '(5))))) ) ("p1 is not variable" (assert-error (lambda () (mul (make-polynomial 1 '(5)) (make-polynomial 'x '(5))))) ) ("p2 is not variable" (assert-error (lambda () (mul (make-polynomial 'x '(5)) (make-polynomial 2 '(5))))) ) ("0 times 5 (1)" (let ((test (mul (make-polynomial 'x '()) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("0 times 5 (2)" (let ((test (mul (make-polynomial 'x '(0)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("5 times 0 (1)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '())))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("5 times 0 (2)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("mul constant" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(25) (cddr test))) ) ("normal (6x + 5) * (2x^2 + 3) -> 12x^3 + 10x^2 + 18x + 15" (let ((test (mul (make-polynomial 'x '(6 5)) (make-polynomial 'x '(2 0 3))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(12 10 18 15) (cddr test))) ) ) )
上記の整理事項含め、バグが沢山。
まず、empty-termlist? は同じでよいはず。がしかし "first (1x - 1x = 0)" な試験にパスしない。=zero? 付近な模様。
あ、違う。neg-terms 付近か。問題 2.88 の書き方が微妙なんだな。empty-termlist? だの the-empty-termlist とかを使ってはイケナイのか。neg-terms での意味としてはリストの末端かどうか、という判定で末端だったら空リストを、という事だし。
という事で neg-terms を以下のように修正。
(define (neg-terms L) (if (null? L) '() (let ((t (first-term L))) (adjoin-term (make-term (order t) (- (coeff t))) (neg-terms (rest-terms L))))) )
で、empty-termlist? は上記の整理事項の通りに。
(define (empty-termlist? term-list) (define (all-zero? term-list) (cond ((null? term-list) #t) ((not (= 0 (car term-list))) #f) (else (all-zero? (cdr term-list)))) ) (or (null? term-list) (all-zero? term-list)))
試験に失敗しているのは乗算の試験のみになりました。コードを見るに 0 な乗算は結果が空リストになりますな。
("0 times 5 (1)" (let ((test (mul (make-polynomial 'x '()) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("0 times 5 (2)" (let ((test (mul (make-polynomial 'x '(0)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("5 times 0 (1)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '())))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("5 times 0 (2)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) )
あ、違う。これも通ってない。
("(y + 1)x + (-y - 1)x is zero" (let ((py1 (make-polynomial 'y '(1 1))) (py2 (make-polynomial 'y '(-1 -1)))) (assert-true (=zero? (add (make-polynomial 'x (list py1 0)) (make-polynomial 'x (list py2 0)))))) )
って現状の実装をリストしてねぇからワケワカだぞ。(とほほ
adjoin-term がクサいか、と思ってたら empty-termlist? だった。
(define (empty-termlist? term-list) (define (all-zero? term-list) (cond ((null? term-list) #t) ((not (=zero? (car term-list))) #f) (else (all-zero? (cdr term-list)))) ) (or (null? term-list) (all-zero? term-list)))
all-zero? ん中のゼロ判定を = でやっとりました。
で、残りが多項式な乗算。
("normal (6x + 5) * (2x^2 + 3) -> 12x^3 + 10x^2 + 18x + 15" (let ((test (mul (make-polynomial 'x '(6 5)) (make-polynomial 'x '(2 0 3))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(12 10 18 15) (cddr test))) )
(cddr test) が (22 0 33) になっている、との事。うーん、なんとなく
((+ (* 6 2) (* 5 2)) (+ (* 6 0) (* 5 0)) (+ (* 6 3) (* 5 3)))
みたい。加算なソレでしか検討してないからなぁ。adjoin-term がアヤシげ。cons しかしてないし。ここでの乗算の考え方は上記の式をサンプルとすると
(* ((1 6) (0 5)) ((2 2) (0 3))) (+ (f (1 6) ((2 2) (0 3))) (* ((0 5)) ((2 2) (0 3)))) (+ (((1+2) (6*2)) ((1+0) (6*3))) (* ((0 5)) ((2 2) (0 3)))) (+ ((3 12) (1 18)) (* ((0 5)) ((2 2) (0 3))))
adjoin-term で次数を考慮したリストを作るようになってりゃ良いのかな? 上記の
((3 12) (1 18))
なリストは
(12 0 18 0)
にしてくれれば良い。んですがどうすりゃ良いかね。あるいは上記サンプルによるもう一つの乗算は
(* ((0 5)) ((2 2) (0 3))) (((0+2) (5*2)) ((0+0) (5*3))) ((2 10) (0 15)) (10 0 15)
という形に adjoin-term が整形してくれればシアワセ。
その後 (解決)
mul なソレを置き換えながら整理。置き換え大切。
- adjoin-term には同じ次数のソレは渡されないだろう、という勝手読み
- term-list の次数より大きい次数な term が渡されるはず
- 単純に cons ではなく、次数を意識すれば良い
という事で adjoin-term を以下のように修正。試験パス。
(define (adjoin-term term term-list) (let f ((term term) (term-list term-list)) (if (> (order term) (length term-list)) (f term (cons 0 term-list)) (cons (coeff term) term-list))))
最初、条件式を (> (order term) (+ 1 (length term-list))) とかやってました。というか、scheme ってどうやるんだろうか、と思いつつコードを書いてると簡潔に書けてしまうのでいつもびっくりする。上記ももっと簡潔に書けるんじゃなかろうか。
このエントリ、投入するの嫌だなぁ。(とほほほ
ちなみにダウトだったのは
- adjoin-term
- (cons 第一引数 第二引数)
で、ここが一番キモなのに mul なソレをスルーして実装にかかったのが敗因。
一応、試験と実装を以下に。
追記
簡潔に書けそげ。
(define (adjoin-term term term-list) (let f ((term-list term-list)) (if (> (order term) (length term-list)) (f (cons 0 term-list)) (cons (coeff term) term-list))))
動作は確認してません。(を
試験 (test-2.5.3.scm)
#!/usr/bin/env gosh (use test.unit) (require "2.5.3") (define-test-suite "2.5.3" ("2-88" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("first (1x - 1x = 0)" (assert-true (=zero? (add (make-polynomial 'x '(1 0)) (neg (make-polynomial 'x '(1 0)))))) ) ("2nd (1x - 0 = 1x)" (let ((p (make-polynomial 'x '(1 0)))) (assert-equal p (add p (neg (make-polynomial 'x '()))))) ) ("3rd (1x - 0x = 1x" (let ((p1 (make-polynomial 'x '(1 0))) (p0 (make-polynomial 'x '(0 0)))) (assert-equal p1 (add p1 (neg p0)))) ) ("4th polynomial neg" (let ((p1 (make-polynomial 'x '(1 2 3 4))) (p2 (make-polynomial 'x '(-1 -2 -3 -4)))) (assert-equal p1 (neg p2)) (assert-equal p2 (neg p1))) ) ) ("2-87" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("() is zero" (assert-true (=zero? (make-polynomial 'x '()))) ) ("() is zero (2)" (assert-true (=zero? '(polynomial x))) ) ("(0 0 0 0) is zero" (assert-true (=zero? '(polynomial x 0 0 0 0))) ) ("(y + 1)x + (-y - 1)x is zero" (let ((py1 (make-polynomial 'y '(1 1))) (py2 (make-polynomial 'y '(-1 -1)))) (assert-true (=zero? (add (make-polynomial 'x (list py1 0)) (make-polynomial 'x (list py2 0)))))) ) ) ("make-polynomial test" (setup (lambda () (install-polynomial-package))) ("car is 'polynomial" (assert-equal 'polynomial (car (make-polynomial 'var 'terms))) ) ("cadr is var" (assert-equal 'var (cadr (make-polynomial 'var 'terms))) ) ("cddr is terms" (assert-equal 'terms (cddr (make-polynomial 'var 'terms))) ) ) ("add test (1)" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("different variable" (assert-error (lambda () (add (make-polynomial 'x '(0 5)) (make-polynomial 'y '(0 5))))) ) ("add constant" (let ((test (add (make-polynomial 'x '(1)) (make-polynomial 'x '(2))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(3) (cddr test))) ) ("result 0" (let ((test (add (make-polynomial 'x '(0)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(0) (cddr test))) ) ("add 0 to 5 (1)" (let ((test (add (make-polynomial 'x '(0)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 0 to 5 (2)" (let ((test (add (make-polynomial 'x '()) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 5 to 0 (1)" (let ((test (add (make-polynomial 'x '(5)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("add 5 to 0 (2)" (let ((test (add (make-polynomial 'x '(5)) (make-polynomial 'x '())))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(5) (cddr test))) ) ("normal (2x + 3) + (x^2 + x + 2) -> x^2 + 3x + 5" (let ((test (add (make-polynomial 'x '(2 3)) (make-polynomial 'x '(1 1 2))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(1 3 5) (cddr test))) ) ) ("mul test" (setup (lambda () (install-scheme-number-package) (install-polynomial-package))) ("different variable" (assert-error (lambda () (mul (make-polynomial 'x '(5)) (make-polynomial 'y '(5))))) ) ("p1 is not variable" (assert-error (lambda () (mul (make-polynomial 1 '(5)) (make-polynomial 'x '(5))))) ) ("p2 is not variable" (assert-error (lambda () (mul (make-polynomial 'x '(5)) (make-polynomial 2 '(5))))) ) ("0 times 5 (1)" (let ((test (mul (make-polynomial 'x '()) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("0 times 5 (2)" (let ((test (mul (make-polynomial 'x '(0)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("5 times 0 (1)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '())))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("5 times 0 (2)" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '(0))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '() (cddr test))) ) ("mul constant" (let ((test (mul (make-polynomial 'x '(5)) (make-polynomial 'x '(5))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(25) (cddr test))) ) ("normal (6x + 5) * (2x^2 + 3) -> 12x^3 + 10x^2 + 18x + 15" (let ((test (mul (make-polynomial 'x '(6 5)) (make-polynomial 'x '(2 0 3))))) (assert-equal 'polynomial (car test)) (assert-equal 'x (cadr test)) (assert-equal '(12 10 18 15) (cddr test))) ) ) )
実装 (2.5.3.scm)
(define (install-polynomial-package) (define (adjoin-term term term-list) (let f ((term term) (term-list term-list)) (if (> (order term) (length term-list)) (f term (cons 0 term-list)) (cons (coeff term) term-list)))) (define (the-empty-termlist) '()) (define (first-term term-list) (if (null? term-list) term-list (list (- (length term-list) 1) (car term-list)))) (define (rest-terms term-list) (cdr term-list)) (define (empty-termlist? term-list) (define (all-zero? term-list) (cond ((null? term-list) #t) ((not (=zero? (car term-list))) #f) (else (all-zero? (cdr term-list)))) ) (or (null? term-list) (all-zero? term-list))) (define (make-term order coeff) (list order coeff)) (define (order term) (car term)) (define (coeff term) (cadr term)) (define (make-poly variable term-list) (cons variable term-list)) (define (variable p) (car p)) (define (term-list p) (cdr p)) (define (variable? x) (symbol? x)) (define (same-variable? v1 v2) (and (variable? v1) (variable? v2) (eq? v1 v2))) (define (add-poly p1 p2) (if (same-variable? (variable p1) (variable p2)) (make-poly (variable p1) (add-terms (term-list p1) (term-list p2))) (error "Polys not in same var --- ADD-POLY" (list p1 p2)))) (define (add-terms L1 L2) (cond ((empty-termlist? L1) L2) ((empty-termlist? L2) L1) (else (let ((t1 (first-term L1)) (t2 (first-term L2))) (cond ((> (order t1) (order t2)) (adjoin-term t1 (add-terms (rest-terms L1) L2))) ((< (order t1) (order t2)) (adjoin-term t2 (add-terms L1 (rest-terms L2)))) (else (adjoin-term (make-term (order t1) (add (coeff t1) (coeff t2))) (add-terms (rest-terms L1) (rest-terms L2))))))))) (define (mul-poly p1 p2) (if (same-variable? (variable p1) (variable p2)) (make-poly (variable p1) (mul-terms (term-list p1) (term-list p2))) (error "Polys not in same var --- MUL-POLY" (list p1 p2)))) (define (mul-terms L1 L2) (if (empty-termlist? L1) (the-empty-termlist) (add-terms (mul-term-by-all-terms (first-term L1) L2) (mul-terms (rest-terms L1) L2)))) (define (mul-term-by-all-terms t1 L) (if (empty-termlist? L) (the-empty-termlist) (let ((t2 (first-term L))) (adjoin-term (make-term (+ (order t1) (order t2)) (mul (coeff t1) (coeff t2))) (mul-term-by-all-terms t1 (rest-terms L)))))) (define (neg-poly p) (make-poly (variable p) (neg-terms (term-list p)))) (define (neg-terms L) (if (null? L) '() (let ((t (first-term L))) (adjoin-term (make-term (order t) (- (coeff t))) (neg-terms (rest-terms L))))) ) (define (tag p) (attach-tag 'polynomial p)) (put '=zero? '(polynomial) (lambda (p) (empty-termlist? (term-list p)))) (put 'neg '(polynomial) (lambda (p) (tag (neg-poly p)))) (put 'add '(polynomial polynomial) (lambda (p1 p2) (tag (add-poly p1 p2)))) (put 'mul '(polynomial polynomial) (lambda (p1 p2) (tag (mul-poly p1 p2)))) (put 'make 'polynomial (lambda (var terms) (tag (make-poly var terms)))) 'done) (define (make-polynomial var terms) ((get 'make 'polynomial) var terms)) (define (neg x) (apply-generic 'neg x))
上記実装は scheme-number-package 等の記述を略しています。