SICP 問題2.91(多項式の除算)
問題
一元多項式はもう一つのもので割り、多項式の商と多項式の剰余が得られる。例えば
除算は長い除算で実行される。つまり被除数の最高次の項を除数の最高次の項で割る。この結果が商の第一項である。次にこの結果に除数を掛け、それを被除数から引き、答えの残りは再帰的にこの差を除数で割って作る。除数の次数が被除数の次数を超えたら停止し、その被除数を剰余とせよ。また被除数がゼロになったら商と剰余の両方にゼロを返せ。
add-poly, mul-polyにならってdiv-polyを設計できる。手続きは二つの多項式が同じ変数を持つか調べる。そうであればdiv-polyは変数を剥がし、問題をdiv-termsに送る。それが項リストについて除算を行う。div-polyは最後にdiv-termsからもらった結果に変数を取り付ける。div-termsを除算の商と剰余の両方を計算するように設計すると便利である。div-termsは引数として二つの項リストを取り、商の項リストと剰余の小売すとのリストを返す。
次のdiv-termsの定義を欠けている式を補って完成せよ。これを使って、引数として二つの多項式をとり、商と剰余の多項式のリストを返すdiv-polyを実装せよ。
(define (div-terms L1 L2) (if (empty-termlist? L1) (list (the-empty-termlist) (the-empty-termlist)) (let ((t1 (first-term L1)) (t2 (first-term L2))) (if (> (order t2) (order t1)) (list (the-empty-termlist) L1) (let ((new-c (div (coeff t1) (coeff t2))) (new-o (- (order t1) (order t2)))) (let ((rest-of-result <<結果の残りを再帰的に計算する>>)) <<完全な結果を形勢する>> ))))))
解答
アルゴリズムがよくわからねぇ。文章を分解してソースに張り付けていってみよう。
こんな感じ?ソースのどこに該当するか・・・
0.除算は長い除算で実行される。
1.つまり被除数の最高次の項を除数の最高次の項で割る。
2.この結果が商の第一項である。
3.次にこの結果に除数を掛け、それを被除数から引き、答えの残りは再帰的にこの差を除数で割って作る。
4.除数の次数が被除数の次数を超えたら停止し、その被除数を剰余とせよ。
5.また被除数がゼロになったら商と剰余の両方にゼロを返せ。
;L1:被除数 ;L2:除数 (define (div-terms L1 L2) (if (empty-termlist? L1) (list (the-empty-termlist) (the-empty-termlist)) (let (;被除数の最高次の項を取得 (t1 (first-term L1)) ;除数の最高次の項を取得 (t2 (first-term L2))) ;4.除数の次数が被除数の次数を超えたら停止し、その被除数を剰余とせよ。 (if (> (order t2) (order t1)) (list (the-empty-termlist) L1) ;2番目のアイテムが剰余 ;1.つまり被除数の最高次の項を除数の最高次の項で割る。 ;2.この結果が商の第一項である。 (let ((new-c (div (coeff t1) (coeff t2))) ;係数の除算 (new-o (- (order t1) (order t2)))) ;次数の除算 ;3.次にこの結果に除数を掛け、それを被除数から引き、答えの残りは再帰的にこの差を除数で割って作る。 (let ((rest-of-result (sub-terms L1 (mul-term-by-all-terms (make-term new-o new-c) L2)))) (modulo (cadr answer))) (let* ((answer (div-terms rest-of-result L2)) ;リストの第2項に剰余が格納された形で返却されてくる。 (quotient (car answer)) ;第1項である商を取得 (modulo (cadr answer))) ;第2項以降の剰余を取得 (list (cons (make-term new-o new-c) quotient) ;商を加算して戻っていく。 modulo))))))) ;剰余はそのまま返却
こんな感じか?
で、そもそもこのdiv-termsは薄い多項式用のやつだから、薄い多項式のファイルに定義することになる。薄い多項式の*-termsの呼び出し方はほとんど同じなので、汎用手続き「*-terms」を作ってまとめてしまった。そのファイルを掲載しよう。手を加えた箇所にコメントしとく。
calculate-polynomial-light.scm
(use srfi-1) (define (install-polynomial-light-package) (define (tag p) (attach-tag 'light p)) (define (variable? x) (symbol? x)) (define (same-variable? v1 v2) (and (variable? v1) (variable? v2) (eq? v1 v2))) (define (make-term order coeff) (list order coeff)) (define (order term) (car term)) (define (coeff term) (cadr term)) (define (first-term term-list) (car term-list)) (define (rest-terms term-list) (cdr term-list)) (define (empty-termlist? term-list) (null? term-list)) (define (the-empty-termlist) '()) (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 (adjoin-term term term-list) (if (=zero? (coeff term)) term-list (cons term term-list))) (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 (sub-terms L1 L2) (define (reverse-signs terms) (map (lambda (term) (make-term (order term) (* -1 (coeff term)))) terms)) (add-terms L1 (reverse-signs L2))) (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 (div-terms L1 L2) (if (empty-termlist? L1) (list (the-empty-termlist) (the-empty-termlist)) (let ((t1 (first-term L1)) (t2 (first-term L2))) (if (> (order t2) (order t1)) (list (the-empty-termlist) L1) (let ((new-c (div (coeff t1) (coeff t2))) (new-o (- (order t1) (order t2)))) (let ((rest-of-result (sub-terms L1 (mul-term-by-all-terms (make-term new-o new-c) L2)))) (let* ((answer (div-terms rest-of-result L2)) (quotient (car answer)) (modulo (cadr answer))) (list (cons (make-term new-o new-c) quotient) modulo)))))))) ;;------------------------------ (define (make-poly variable term-list) (cons variable term-list)) (define (variable p) (car p)) (define (term-list p) (cdr p)) ;;------------------------------ ;;汎用演算手続き (define (*-poly make-proc calc-proc p1 p2) (if (same-variable? (variable p1) (variable p2)) (make-proc (variable p1) (calc-proc (term-list p1) (term-list p2))) (error "Polys not in same var -- " (x->string proc) (list p1 p2)))) ;;------------------------------ ;;------------------------------ ;;加算、減算、乗算用の多項式生成用共通手続き (define (*-make-poly var result) (make-poly var result)) ;;------------------------------ ;;------------------------------ ;;加算、減算、乗算の定義を変更(汎用)手続きを使用する。 (define (add-poly p1 p2) (*-poly *-make-poly add-terms p1 p2)) (define (sub-poly p1 p2) (*-poly *-make-poly sub-terms p1 p2)) (define (mul-poly p1 p2) (*-poly *-make-poly mul-terms p1 p2)) ;;------------------------------ ;;------------------------------ ;;多項式の除算手続き (define (div-poly p1 p2) (*-poly (lambda (var result) (map (lambda (item) (make-poly var item)) result)) div-terms p1 p2)) ;;------------------------------ (define (=zero-poly? polynomial) (define (coeff-list term-list) (filter-map (lambda (x) (not (=zero? (coeff x)))) term-list)) (let ((terms (term-list polynomial))) (cond ((empty-termlist? terms) #t) ((null? (coeff-list terms)) #t) (else #f)))) (put 'add-poly '(light light) (lambda (p1 p2) (tag (add-poly p1 p2)))) (put 'sub-poly '(light light) (lambda (p1 p2) (tag (sub-poly p1 p2)))) (put 'mul-poly '(light light) (lambda (p1 p2) (tag (mul-poly p1 p2)))) ;;------------------------------ ;;公開用除算手続きを (put 'div-poly '(light light) (lambda (p1 p2) (map tag (div-poly p1 p2)))) ;;------------------------------ (put '=zero-poly? 'light (lambda (p) (=zero-poly? p))) (put 'make-polynomial-light 'light (lambda (var terms) (tag (make-poly var terms)))) (put 'coerce 'light (lambda (poly) poly)) 'done) (install-polynomial-light-package)
続いて濃い多項式の表現でも計算できるようにしちゃう。calculate-polynomial-heavy.scmをこんな感じで修正。(例によって追加、修正した箇所をコメントで注釈。コメントって注釈って意味だっけ?)
calculate-polynomial-heavy.scm
(use srfi-1) (define (install-polynomial-heavy-package) (define (variable? x) (symbol? x)) (define (same-variable? v1 v2) (and (variable? v1) (variable? v2) (eq? v1 v2))) (define (tag p) (attach-tag 'heavy p)) (define (make-poly variable highest-order coeffs) (cons variable (cons highest-order coeffs))) (define (variable p) (car p)) (define (terms p) (cdr p)) ;;------------------------------ ;;空の多項式 (define the-empty-termlist (make-terms 0 '())) ;;------------------------------ (define (make-terms highest-order coeffs) (cons highest-order coeffs)) (define (make-terms-padding high low terms) (let ((h (highest-order terms)) (l (lowest-order terms))) (if (or (< high h) (< l low)) (error "Illegal highest-order or lowest-order.") (let ((h-pad (iota (- high h) 0 0)) (l-pad (iota (- l low) 0 0))) (cons high (append h-pad (coeffs terms) l-pad)))))) (define (coeffs t) (cdr t)) (define (highest-order t) (car t)) (define (lowest-order t) (+ (- (highest-order t) (length (coeffs t))) 1)) ;;------------------------------ ;;正規化(最高次元、最低次元の係数が0だった場合、その係数を削除) (define (regulate-terms t) (define (cut-zero number-list) (let loop ((count 0) (numbers number-list)) (if (=zero? (car numbers)) (loop (+ 1 count) (cdr numbers)) (list count numbers)))) (let* ((h-order (highest-order t)) (coeff-list (coeffs t)) (result1 (cut-zero coeff-list)) (h-order1 (- h-order (car result1))) (coeff-list1 (cadr result1)) (coeff-list2 (reverse (cadr (cut-zero (reverse coeff-list1)))))) (make-terms h-order1 coeff-list2))) ;;------------------------------ (define (+/-terms +/- terms1 terms2) (let* ((h-order (max (highest-order terms1) (highest-order terms2))) (l-order (min (lowest-order terms1) (lowest-order terms2))) (p-terms1 (make-terms-padding h-order l-order terms1)) (p-terms2 (make-terms-padding h-order l-order terms2))) (make-terms h-order (map (lambda (c1 c2) (+/- c1 c2)) (coeffs p-terms1) (coeffs p-terms2))))) (define (add-terms terms1 terms2) (+/-terms + terms1 terms2)) (define (sub-terms terms1 terms2) (+/-terms - terms1 terms2)) (define (mul-terms terms1 terms2) (let ((high1 (highest-order terms1)) (high2 (highest-order terms2)) (coeffs1 (coeffs terms1)) (coeffs2 (coeffs terms2))) (let loop ((current-order high2) (current-coeffs coeffs2) (ans-terms (make-terms 0 '()))) (cond ((null? current-coeffs) ans-terms) ((=zero? (car current-coeffs)) (loop (- current-order 1) (cdr current-coeffs) ans-terms)) (else (loop (- current-order 1) (cdr current-coeffs) (add-terms ans-terms (make-terms (+ high1 current-order) (map (lambda (x) (* x (car current-coeffs))) coeffs1))))))))) ;;------------------------------ ;;項リストの除算を行う手続き (define (div-terms L1 L2) (let ((h-order1 (highest-order L1)) (h-order2 (highest-order L2)) (coeffs1 (coeffs L1)) (coeffs2 (coeffs L2))) (if (< h-order1 h-order2) (list the-empty-termlist L1) (let ((new-c (div (car coeffs1) (car coeffs2))) (new-o (- h-order1 h-order2))) (let* ((target-term (make-terms new-o (list new-c))) (rest-of-result (regulate-terms (sub-terms L1 (mul-terms target-term L2))))) (let* ((answer (div-terms rest-of-result L2)) (quotient (car answer)) (modulo (cadr answer))) (list (add-terms target-term quotient) modulo))))))) ;;------------------------------ ;;------------------------------ ;;多項式の汎用演算手続きをさらに汎用化 (define (*-poly make-proc calc-proc p1 p2) (if (same-variable? (variable p1) (variable p2)) (make-proc (variable p1) (calc-proc (terms p1) (terms p2))) (error "Polys not in same var -- " (x->string calc-proc) (list p1 p2)))) ;;------------------------------ ;;------------------------------ ;;加算、減算、乗算用の汎用項リスト生成手続き (define (*-make-poly var result) (make-poly var (highest-order result) (coeffs result))) ;;------------------------------ ;;------------------------------ ;;多項式の演算手続きにて、更に汎用化した演算手続きを使用するように変更 (define (add-poly p1 p2) (*-poly *-make-poly add-terms p1 p2)) (define (sub-poly p1 p2) (*-poly *-make-poly sub-terms p1 p2)) (define (mul-poly p1 p2) (*-poly *-make-poly mul-terms p1 p2)) ;;------------------------------ ;;------------------------------ ;;多項式の除算手続き (define (div-poly p1 p2) (*-poly (lambda (var result) (map (lambda (item) (make-poly var (highest-order item) (coeffs item))) result)) div-terms p1 p2)) ;;------------------------------ (define (=zero-poly? p) (let* ((ts (terms p)) (cs (coeffs ts)) (filterd (filter-map (lambda (x) (not (=zero? x))) cs))) (null? filterd))) (put 'add-poly '(heavy heavy) (lambda (p1 p2) (tag (add-poly p1 p2)))) (put 'sub-poly '(heavy heavy) (lambda (p1 p2) (tag (sub-poly p1 p2)))) (put 'mul-poly '(heavy heavy) (lambda (p1 p2) (tag (mul-poly p1 p2)))) ;;------------------------------ ;;公開用の多項式の除算手続き (put 'div-poly '(heavy heavy) (lambda (p1 p2) (map tag (div-poly p1 p2)))) ;;------------------------------ (put '=zero-poly? 'heavy (lambda (p) (=zero-poly? p))) (put 'make-polynomial-heavy 'heavy (lambda (var highest-order coeffs) (tag (make-poly var highest-order coeffs)))) (put 'coerce 'heavy (lambda (poly) (let* ((p (contents poly)) (var (variable p)) (term-list (terms p)) (h-order (highest-order term-list)) (coeff-list (coeffs term-list)) (orders (iota (length coeff-list) h-order -1))) ((get 'make-polynomial-light 'light) var (map (lambda (order coeff) (list order coeff)) orders coeff-list))))) 'done) (install-polynomial-heavy-package)
よし、じゃあ多項式の除算テストを追記したテストスクリプトを。。
(define (calculate-polynomial-test) (define (make-polynomial-*-test) (define (print-test-light answer var terms) (let ((p (make-polynomial-light var terms))) (if (equal? p answer) (print "[ O K ] (print-test-light " var " " terms ")") (begin (print "[ERROR] (print-test-light " var " " terms ")") (print " result:" p) (print " answer:" answer))))) (define (print-test-heavy answer var highest-order coeffs) (let ((p (make-polynomial-heavy var highest-order coeffs))) (if (equal? p answer) (print "[ O K ] (print-test-heavy " var " " highest-order " " coeffs ")") (begin (print "[ERROR] (print-test-heavy " var " " highest-order " " coeffs ")") (print " result:" p) (print " answer:" answer))))) (print "<<構成子のテスト>>") (print-test-light '(polynomial light x (3 2) (2 1) (0 4)) 'x '((3 2) (2 1) (0 4))) (print-test-heavy '(polynomial heavy x 5 1 2 0 4 -2 1) 'x 5 '(1 2 0 4 -2 1)) ) (define (=zero?-test) (define (print-test answer p) (let ((result (=zero? p))) (if (equal? result answer) (print "[ O K ] " p) (begin (print "[ERROR] " p) (print " result:" result) (print " answer:" answer))))) (print "<<ゼロ確認>>") (print-test #f (make-polynomial-light 'x '((3 1) (2 1) (1 1) (0 1)))) (print-test #f (make-polynomial-light 'x '((3 1) (2 1) (1 1) (0 0)))) (print-test #f (make-polynomial-light 'x '((3 1) (2 1) (1 0) (0 0)))) (print-test #f (make-polynomial-light 'x '((3 1) (2 0) (1 0) (0 0)))) (print-test #t (make-polynomial-light 'x '((3 0) (2 0) (1 0) (0 0)))) (print-test #f (make-polynomial-heavy 'x 3 '(1 1 1 1))) (print-test #f (make-polynomial-heavy 'x 3 '(1 1 1 0))) (print-test #f (make-polynomial-heavy 'x 3 '(1 1 0 0))) (print-test #f (make-polynomial-heavy 'x 3 '(1 0 0 0))) (print-test #t (make-polynomial-heavy 'x 3 '(0 0 0 0)))) (define (add-test) (define (print-test answer p1 p2) (let ((result (add p1 p2))) (if (equal? result answer) (print "[ O K ] " result) (begin (print "[ERROR] ") (print " p1 :" p1) (print " p2 :" p2) (print " result:" result) (print " answer:" answer))))) (print "<<加算>>") (print-test (make-polynomial-light 'x '((3 1) (2 1) (1 -2) (0 1))) (make-polynomial-light 'x '( (2 2) (1 -3) (0 1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-heavy 'x 3 '(1 1 -2 1)) (make-polynomial-heavy 'x 2 '(2 -3 1)) (make-polynomial-heavy 'x 3 '(1 -1 1))) (print-test (make-polynomial-light 'x '((3 1) (2 1) (1 -2) (0 1))) (make-polynomial-heavy 'x 2 '(2 -3 1)) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-light 'x '((3 1) (2 1) (1 -2) (0 1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1))) (make-polynomial-heavy 'x 2 '(2 -3 1))) ) (define (sub-test) (define (print-test answer p1 p2) (let ((result (sub p1 p2))) (if (equal? result answer) (print "[ O K ] " result) (begin (print "[ERROR] ") (print " p1 :" p1) (print " p2 :" p2) (print " result:" result) (print " answer:" answer))))) (print "<<減算>>") (print-test (make-polynomial-light 'x '((3 -1) (2 3) (1 -4) (0 1))) (make-polynomial-light 'x '( (2 2) (1 -3) (0 1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-heavy 'x 3 '(-1 3 -4 1)) (make-polynomial-heavy 'x 2 '( 2 -3 1)) (make-polynomial-heavy 'x 3 '( 1 -1 1))) (print-test (make-polynomial-light 'x '((3 -1) (2 3) (1 -4) (0 1))) (make-polynomial-heavy 'x 2 '(2 -3 1)) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-light 'x '((3 1) (2 -3) (1 4) (0 -1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1))) (make-polynomial-heavy 'x 2 '(2 -3 1))) ) (define (mul-test) (define (print-test answer p1 p2) (let ((result (mul p1 p2))) (if (equal? result answer) (print "[ O K ] " result) (begin (print "[ERROR] ") (print " p1 :" p1) (print " p2 :" p2) (print " result:" result) (print " answer:" answer))))) (print "<<乗算>>") (print-test (make-polynomial-light 'x '((5 2) (4 -5) (3 6) (2 -4) (1 1))) (make-polynomial-light 'x '((2 2) (1 -3) (0 1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-heavy 'x 5 '(2 -5 6 -4 1)) (make-polynomial-heavy 'x 2 '(2 -3 1)) (make-polynomial-heavy 'x 3 '(1 -1 1))) (print-test (make-polynomial-light 'x '((5 2) (4 -5) (3 6) (2 -4) (1 1))) (make-polynomial-heavy 'x 2 '(2 -3 1)) (make-polynomial-light 'x '((3 1) (2 -1) (1 1)))) (print-test (make-polynomial-light 'x '((5 2) (4 -5) (3 6) (2 -4) (1 1))) (make-polynomial-light 'x '((3 1) (2 -1) (1 1))) (make-polynomial-heavy 'x 2 '(2 -3 1))) ) ;------------------------------ ;除算テスト (define (div-test) (define (print-test answer p1 p2) (let ((result (div p1 p2))) (if (equal? result answer) (print "[ O K ] " result) (begin (print "[ERROR] ") (print " p1 :" p1) (print " p2 :" p2) (print " result:" result) (print " answer:" answer))))) (print "<<除算>>") ;;薄い多項式同士 (print-test (list (make-polynomial-light 'x '((3 1) (1 1))) (make-polynomial-light 'x '((1 1) (0 -1)))) (make-polynomial-light 'x '((5 1) (0 -1))) (make-polynomial-light 'x '((2 1) (0 -1)))) ;;濃い多項式同士 (print-test (list (make-polynomial-heavy 'x 3 '(1 0 1)) (make-polynomial-heavy 'x 1 '(1 -1))) (make-polynomial-heavy 'x 5 '(1 0 0 0 0 -1)) (make-polynomial-heavy 'x 2 '(1 0 -1))) ;;薄い多項÷濃い多項式 (print-test (list (make-polynomial-light 'x '((3 1) (1 1))) (make-polynomial-light 'x '((1 1) (0 -1)))) (make-polynomial-light 'x '((5 1) (0 -1))) (make-polynomial-heavy 'x 2 '(1 0 -1))) ;;濃い多項式÷薄い多項 (print-test (list (make-polynomial-light 'x '((3 1) (1 1))) (make-polynomial-light 'x '((1 1) (0 -1)))) (make-polynomial-heavy 'x 5 '(1 0 0 0 0 -1)) (make-polynomial-light 'x '((2 1) (0 -1)))) ) (make-polynomial-*-test) (=zero?-test) (add-test) (sub-test) (mul-test) (div-test) )
テストケース少なすぎ?まぁテストを繰り返してできる環境を作っておくのが大事なので、テストが少ないのは多めにみてやってくださいな。。
では実験。
よっしゃ!できた!ということにして次へいこうっと。
gosh> (calculate-polynomial-test)<<構成子のテスト>>
[ O K ] (print-test-light x ((3 2) (2 1) (0 4)))
・
・
・<<除算>>
[ O K ] ((polynomial light x (3 1) (1 1)) (polynomial light x (1 1) (0 -1)))
[ O K ] ((polynomial heavy x 3 1 0 1) (polynomial heavy x 1 1 -1))
[ O K ] ((polynomial light x (3 1) (1 1)) (polynomial light x (1 1) (0 -1)))
[ O K ] ((polynomial light x (3 1) (1 1)) (polynomial light x (1 1) (0 -1)))
#
gosh>