問題3-24〜27
「3.3.3 表の表現」ということで、set-car!/set-cdr!を内部的に使いながら、テーブルの要素を追加・更新・削除できるようなデータ構造を構築していきます。
問題3-24では、「キーに該当する要素」を見つけるときに、equal?で厳密にチェックするのではなく、何らかの許容範囲のようなものを定められるようにします。
(define (make-table same-key?) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc same-key? key-1 (cdr local-table)))) (if subtable (let ((record (assoc same-key? key-2 (cdr subtable)))) (if record (cdr record) #f)) #f))) (define (insert! key-1 key-2 value) (let ((subtable (assoc same-key? key-1 (cdr local-table)))) (if subtable (let ((record (assoc same-key? key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error "Unknown operation" m)))) dispatch)) (define (assoc same-key? key records) (cond ((null? records) #f) ((same-key? key (caar records)) (car records)) (else (assoc same-key? key (cdr records))))) (define (ex-eq x y) (and (<= x (* y 1.1)) (>= x (* y 0.9)))) (define operation-table (make-table ex-eq)) (define get (operation-table 'lookup-proc)) (define put (operation-table 'insert-proc!)) (put 1 10 'a) ;=>ok (put 1 20 'b) ;=>ok (put 1 30 'c) ;=>ok (get 1 10) ;=>a (get 1 11) ;=>a (get 1 12) ;=>#f
上記のコードでは、上下10%以内ならキーが一致しているとみなすようなex-eqを定義して、それをsame-key?として渡しています。
問題3-25は、一次元・二次元の表を一般化する問題ですが、かなり時間をかけて複数のキーでも値を登録できるような処理を模索しましたが、個々のキーが一致したとき、値とのペアを作るだけで良いのか、後続のキーが残っていたときにどのような処理を書けばいいのか、上手いこと正しいと思われるコードを書くことができませんでした。埒があかなくなってきたので、ここの解答例を見てみました。反復的なループでキーを操作しなければならないと思っていただけに、最初は目を疑いましたが、デバッグをしてみて、ようやく腑に落ちました。equal?はlistとlistにも効くわけで、それをそのまま使えばいいという発想が足りませんでした。
問題3-26は、二進木で表現することで実現できそうですが、上記サイトの解答例を見てしまったので、飛ばしました。
問題3-27では、一度計算した処理の引数と結果をテーブルに保存しておいて、再び同じ引数で計算しようとした場合は、計算自体は実行せずに、テーブルに保持してある結果を取得するような技法が紹介されています。デバッグをしながら、一度計算したFibonacci数は確かにテーブルから取得しているということを確認しました。memo-fibを定義した時点で、memoize内の局所変数であるテーブルが新しい環境に用意され、(memo-fib 3)を適用するときに、その環境を利用していると思われます。(memoize fib)は、純粋なfib内のfib呼び出しが、memo-fibでなく純粋なfibなので、キャッシュを利用する機会がありません。やはり、memo-fibのように、memoizeの引数に実際の手続きを書いてやる必要がありそうです。