SICPを読む(72) 問題2.61 - 2.62 順序づけられたリストとしての集合

まだまだ集合が続きます。今回は順序づけられたリスト。つまり、

(1 3 5 7) ; OK
(8 6 4 2) ; NG

順番は守ろう〜。というリスト。

問題 2.61

あ、前回、スペルミスしてたっぽい。ajoinだと思ったら、adjoinだった。

英語の弱さも直さねば。

(define (adjoin-set x set)
  (if (null? set)
      (cons x '())
      (let ((y (car set)))
        (cond ((= x y) set)
              ((< x y) (cons x set))
              (else
                (cons y
                      (adjoin-set x (cdr set))))))))

前回はただ突っ込んでいけばなんとかなった。

今回は「順番を守らなければならない」ので、挿入する場所を決める必要がある。ステップ数は1からnに増えてしまった。順序があると挿入にかかるコストが増えるというのがポイント。

問題 2.62

和集合の問題。

adjoin-setを使うとn^2ステップかかってしまうので、真面目にpushする。

(define (union-set set1 set2)
  (cond ((null? set1) set2)
        ((null? set2) set1)
        (else
          (let ((x1 (car set1))
                (x2 (car set2)))
            (cond ((= x1 x2)
                   (cons x1
                         (union-set (cdr set1) (cdr set2))))
                  ((< x1 x2)
                   (cons x1
                         (union-set (cdr set1) set2)))
                  ((> x1 x2)
                   (cons x2
                         (union-set set1 (cdr set2)))))))))

2つのリストをマージすればいいので、積集合の逆をやればいい。

小さい方をpop。どっちかが空っぽになったら、残りを突っ込む。


単調な部分を除いてみる。

(define (union-set set1 set2)
  (cond ((null? set1) set2)
        ((null? set2) set1)
        (else
          (let ((x1 (car set1))
                (x2 (car set2))
                (push-next
                  (lambda (p s1 s2)
                    (cons p (union-set s1 s2)))))
            (cond ((= x1 x2) (push-next x1 (cdr set1) (cdr set2)))
                  ((< x1 x2) (push-next x1 (cdr set1) set2))
                  ((> x1 x2) (push-next x2 set1 (cdr set2))))))))

ちょっとすっきり。再帰しないなら、letrecを使わなくてもいいっぽい。