SICP 読み (47) 2.3.4 例: Huffman 符号化木

考え続けてはいるんですが、さすがに集中できん連休中は。
と言いつつスデに連休も明け、ばたばたしている間にこんな時間。(昨晩の独り言)

問題 2.69

何のためなんだ、と思っていた weight という手続きがようやく日の目を見るのか。

それは良いのですが、具体的な手続きが思いつかん。試験ドリブンでやってみる。とりあえず、こんな試験を。

  (assert-equal '((leaf B 1) (leaf A 1) (B A) 2)
                (successive-merge '((leaf A 1) (leaf B 1))))

通すのみであればこれで大丈夫。

(define (successive-merge l)
 (make-code-tree (cadr l) (car l)))

あるいは

  (assert-equal '((leaf C 1) ((leaf B 1) (leaf A 1) (B A) 2) (C B A) 3)
                (successive-merge '((leaf A 1) (leaf B 1) (leaf C 1))))

だったらこんなカンジですか。

(define (successive-merge l)
 (if (= (length l) 2)
     (make-code-tree (cadr l) (car l))
     (make-code-tree (caddr l)
                     (make-code-tree (cadr l) (car l)))))

こんな事をいつまで続けててもキリがない。現時点の実装では重みの判断が全然ないな。例えば例示されているナニで言えば

(make-code-tree (leaf A 8)
              (make-code-tree
               (make-code-tree (leaf B 3)
                               (make-code-tree (leaf C 1)
                                               (leaf D 1)))
               (make-code-tree (make-code-tree (leaf E 1)
                                               (leaf F 1))
                               (make-code-tree (leaf G 1)
                                               (laef H 1)))))

な手続き、にならないとイケナイんだけども。(困

うーん。問題文によれば

successive-merge は自分で書く手続きで、make-code-tree を使い、集合の最小重みの順に合体させ、要素が一つになったら止める。

とある。(他にも参考になった記述があったのですが、引用は略)

とりあえず、重みが最初の要素と同じ間、な繰り返しが必要か。紙の上で検証してみても大体は合っているみたい。方針は以下。

  • 引数で受けたリストを修正しながら処理を進める。最終的に length が 1 になったら終了すれば良い
  • 先に先頭二つの make-code-tree はやっておく
  • 要素が二つだけの場合は merge の先頭に戻る。(処理終了)
  • 要素が三つだった場合、make-code-tree の結果リストに cddr を append して先頭に戻る (要素は二つになる)
  • 要素が四以上ある場合は繰り返しにおいて、(weight 先頭) と三番目以降の要素の weight が同じ値であれば、その要素とその後の要素を make-code-tree する

同じリストを使い回す、という方針 (戦略??) が微妙だったのか、リストの扱いでハマりまくってしまいました。

で、現時点での実装は以下なんですが

(define (successive-merge l)
 (let f ((l l))
  (if (= (length l) 1)
      (car l)
      (let g ((w (weight (car l)))
              (ll (cddr l))
              (ret (list (make-code-tree (cadr l) (car l)))))
        (cond ((null? ll) (f ret))
              ((null? (cdr ll))
               (f (append ret ll)))
              ((= w (weight (car ll)))
               (g w (cddr ll) (append ret (list (make-code-tree (cadr ll) (car ll))))))
              (else
               (f (append ret ll))))))))

なんか微妙。以下の試験はパスしている模様。

  (assert-equal '((leaf B 1) (leaf A 1) (B A) 2)
                (successive-merge '((leaf A 1) (leaf B 1))))
  (assert-equal '((leaf C 1) ((leaf B 1) (leaf A 1) (B A) 2) (C B A) 3)
                (successive-merge '((leaf A 1) (leaf B 1) (leaf C 1))))

  (assert-equal '((leaf A 3) ((leaf B 1) (leaf C 1) (B C) 2) (A B C) 5)
                (successive-merge '((leaf C 1) (leaf B 1) (leaf A 3))))
  (assert-equal '((leaf A 8) (((leaf B 3) ((leaf C 1) (leaf D 1) (C
D) 2) (B C D) 5) (((leaf E 1) (leaf F 1) (E F) 2) ((leaf G 1) (leaf H
1) (G H) 2) (E F G H) 4) (B C D E F G H) 9) (A B C D E F G H) 17)
                (successive-merge '((leaf H 1) (leaf G 1) (leaf F 1)
                                    (leaf E 1) (leaf D 1) (leaf C 1)
                                    (leaf B 3) (leaf A 8))))

手続きを見直すリキは今んトコ無い。