SICP 問題 2.71(数式に従って生成されるシンボルと頻度のペアでHuffman木を生成)
問題
n 記号のアルファベットの Huffman 木があるとし、記号の相対頻度が、
n = 5, n = 10 の木を描け。
(一般の n の)こういう木で、最高頻度の記号を符号化するのに必要なビット数はいくらか。
最低頻度の記号ではどうか。
解答
まずは入力されるデータを生成する手続きを考えようか。
;iota を使用したいので。 (use srfi-1) (define (create-test-data n) (map (lambda (x) (expt 2 (- x 1))) ;2^(n-1) を返却する手続き ;初期値 1 で、MAX が n の数列を生成 (iota n 1)))
確認してみる。
いいねぇ。あ、でもアルファベットとこの求めた頻度を対にしなきゃならんな。ちょっと改造したいが、n を元にしてアルファベットに変換できねーかな。int->char 的な関数ねぇか?探すには apropos 手続きを使う。
gosh> (create-test-data 5)
(1 2 4 8 16)
gosh> (create-test-data 10)
(1 2 4 8 16 32 64 128 256 512)
gosh>
お〜、integer->char ってアヤシイ感じのがあるね。じゃあ d 手続きでもう少し詳しく。
gosh> (apropos 'int)(※中略※)
integer->char (scheme)
integer->digit (gauche)
integer-length (gauche)
integer? (scheme)(※中略※)
gosh>
更に詳しい情報を。
gosh> (d integer->char)
#char> is an instance of class
slots:
required : 1
optional : #f
locked : #f
info : "integer->char"
setter : #f
gosh>
あくまでも「文字」を返す感じ。シンボルに変化させることってできないのかな。
gosh> (info "integer->char")
6.8 Characters
==============(※中略※)
-- Function: char->integer char
-- Function: integer->char n
[R5RS] `char->integer' returns an exact integer that represents
internal encoding of the character CHAR. `integer->char' returns
a character whose internal encoding is an exact integer N. The
following expression is always true for valid character CHAR:
(eq? char (integer->char (char->integer char)))The result is undefined if you pass N to `integer->char' that
doesn't have a corresponding character.(※中略※)
gosh>
お、string->symbol ってのがあるな。じゃ、char->string 的なものは・・・ざっと見た感じなさげ。Google 先生に聞いてみたら、make-string ってのがそれっぽいことができそうな感じ。
gosh> (apropos 'sym)
(gauche)
(gauche)
gensym (gauche)
identifier->symbol (gauche)
string->symbol (scheme)
symbol->string (scheme)
symbol-bound? (gauche)
symbol? (scheme)
sys-symlink (gauche)
gosh>
呼び出し時の引数を確認してみよう。
第1引数に何回繰り返すかを、第2引数に該当する文字を渡すみたいだな。では実験。
gosh> (apropos 'make-string)
make-string (scheme)
make-string-pointer (gauche)
gosh> (d make-string)
#is an instance of class
slots:
required : 1
optional : #t
locked : #f
info : "make-string"
setter : #f
gosh> (info "make-string")
6.10.3 String Constructors
- -
-- Function: make-string k &optional char
[R5RS] Returns a string of length K. If optional CHAR is given,
the new string is filled with it. Otherwise, the string is filled
with a whitespace. The result string is always complete.(make-string 5 #\x) => "xxxxx"
Note that the algorithm to allocate a string by `make-string' and
then fills it one character at a time is _extremely_ inefficient
in Gauche, and should be avoided. That kind of algorithms
unnecessarily assumes underlying string allocation and
representation mechanism, which Gauche doesn't follow. You can
use an output string port for a string construction (*Note String
ports::). Even creating a list of characters and using
`list->string' is faster than using `make-string' and
`string-set!'.(※後略※)
gosh>
できた!よ〜しよしよし、これでやりたい処理ができそうだぞ。n と文字(コード)の関係はこんな感じ。
gosh> (make-string 1 (integer->char 70))
"F"
gosh> (string->symbol (make-string 1 (integer->char 70)))
F
gosh>
こいつを create-test-data に組み込んでやる。
n = 1 → A(64 + 1 = 65)
n = 2 → B(64 + 2 = 66)
n = 3 → C(64 + 3 = 67)
・
・
・
;iota を使用したいので。 (use srfi-1) (define (create-test-data n) (map (lambda (x) (list ;x を元にシンボルを生成 (string->symbol (make-string 1 (integer->char (+ 64 x)))) ;出現頻度を数列式 2^(n-1) で生成。 (expt 2 (- x 1)))) ;初期値 1 で、MAX が n の数列を生成 (iota n 1)))
こんな感じか。では実験。
お、いいねぇ。じゃ、データできたから Huffman 木を生成しよう。
gosh> (create-test-data 5)
( (A 1) (B 2) (C 4) (D 8) (E 16))
gosh> (create-test-data 10)
( (A 1) (B 2) (C 4) (D 8) (E 16) (F 32) (G 64) (H 128) (I 256) (J 512))
gosh>
さて、木にしてみようか。
gosh> (generate-huffman-tree (create-test-data 5))
(((((leaf A 1) (leaf B 2) (A B) 3) (leaf C 4) (A B C) 7) (leaf D 8) (A B C D) 15) (leaf E 16) (A B C D E) 31)
gosh>
こんな感じ。n = 10 の場合は・・・
(A B C D E)31
/ \
(A B C D)15 (E 16)
/ \
(A B C)7 (D 8)
/ \
(A B)3 (C 4)
/ \
(A 1) (B 2)
木にすると・・・
gosh> (generate-huffman-tree (create-test-data 10))
((((((((((leaf A 1) (leaf B 2) (A B) 3) (leaf C 4) (A B C) 7) (leaf D 8) (A B C D) 15) (leaf E 16) (A B C D E) 31) (leaf F 32) (A B C D E F) 63) (leaf G 64) (A B C D E F G) 127) (leaf H 128) (A B C D E F G H) 255) (leaf I 256) (A B C D E F G H I) 511) (leaf J 512) (A B C D E F G H I J) 1023)
gosh>
最高頻度の記号を符号化するのに必要なビット数は、1ビットでOK。これは n に依存しない。頻度が多いから1ビットで表現するのがHuffman符号化木なわけで。
(A B C D E F G H I J)1023
/ \
(A B C D E F G H I)511 (J 512)
/ \
(A B C D E F G H)255 (I 256)
/ \
(A B C D E F G)127 (H 128)
/ \
(A B C D E F)63 (G 64)
/ \
(A B C D E)31 (F 32)
/ \
(A B C D)15 (E 16)
/ \
(A B C)7 (D 8)
/ \
(A B)3 (C 4)
/ \
(A 1) (B 2)
さて、最低頻度について。
n = 5 の時は、最低頻度の A を表現するのに必要なビット数は、4 ビット。
n = 10 の時は、最低頻度の A を表現するのに必要なビット数は、9 ビット。
これを見ると思いっきり n-1 になってるんだが、ちゃんと証明するにはどうしたもんかな。。