1 #-*- indent-tabs-mode: nil -*-
18 これらをすべて理解すれば、ソース解析は完了と思ってよい。
19 この段階では、変数名とソースの概観から木を使ってるっぽい程度に認識する。
20 アルゴリズムの予想がある程度立っている場合は、変数名からその用途を予想しても
21 良い。私はわからなかったのでとっとと次に進むことにする。
36 huf_encode_start(opts.method);
39 remainder = fread_crc(&text[DICSIZ], DICSIZ + MAXMATCH, infile);
42 if (outfile != stdout && opts.quiet < 1) {
52 if (matchlen > remainder)
56 while (remainder > 0 && !unpackable) {
57 lastmatchlen = matchlen;
58 lastmatchpos = matchpos;
60 if (matchlen > remainder)
62 if (matchlen > lastmatchlen || lastmatchlen < THRESHOLD)
63 output(text[pos - 1], 0);
65 output(lastmatchlen + (UCHAR_MAX + 1 - THRESHOLD),
66 (pos - lastmatchpos - 2) & (DICSIZ - 1));
67 while (--lastmatchlen > 0)
69 if (matchlen > remainder)
81 (A) allocate_memory() を見ると以下のようなメモリ割り当てを行っている。
82 (B) の初期化に該当する部分もある程度(適当に)図示した。
87 +---------------------+---------------------+----+
89 +---------------------+---------------------+----+
95 +---------------------+---+
97 +---------------------+---+
105 +---------------------+---+
107 +---------------------+---+
112 +---------------------+---------------------+
114 +---------------------+---------------------+
117 +---------------------+---------------------+
119 +---------------------+---------------------+
121 DICSIZ DICSIZ*2 max_hash_val + 1
123 +---------------------+---------------------+---------------------+----+
124 next | 234... NIL| | NIL | |
125 +---------------------+---------------------+---------------------+----+
128 なお、サイズは適当だ。ざっと上記のようにまとめて次に移る
130 (C) huf_encode_start() は、Huffman coding の初期化であることを
131 既にLHa for UNIXの解析で知っているので、次に進む。
133 (D) 圧縮対象のファイルを読み込んで text の右半分に書き込んでいる。
135 (E) 単に圧縮の進行状況を画面に出力する(ために、最初の "."を出力している)
138 (F) matchlen, pos を初期化して insert_node() を呼んでいる。
139 insert_node() をざっと見ても内容はすぐにわからなさそうなので、
142 (G) if (matchlen > remainder) という判定を行っている。(F)の段階で、
143 matchlen = 0 の設定を行っているので、insert_node() にて、matchlen
144 が変更される場合があるのだろう程度に考える。おそらくは、insert_node()
145 実行後の例外条件に対応しているのではないかと予想しつつ次に進む。
147 (H) ちょっと長いが、LHa for UNIX の解析により以下の通り要約できるのでは
152 while (remainder > 0 && !unpackable) {
155 lastmatchlen = matchlen;
156 lastmatchpos = matchpos;
163 if (matchlen > remainder)
164 matchlen = remainder;
166 /* 次のマッチした長さが前回よりも大きいか、前回のマッチが
167 THRESHOLDよりも小さい場合は、前回のマッチを出力する */
168 if (matchlen > lastmatchlen || lastmatchlen < THRESHOLD)
169 output(text[pos - 1], 0);
171 /* 前回のマッチの方が最適であるなら、マッチ長と位置を出力する */
172 output(lastmatchlen + (UCHAR_MAX + 1 - THRESHOLD),
173 (pos - lastmatchpos - 2) & (DICSIZ - 1));
175 /* 前回のマッチ長分のマッチを繰り返す。 */
176 while (--lastmatchlen > 0)
180 if (matchlen > remainder)
181 matchlen = remainder;
185 おおよそ、LHa for UNIX のスライド辞書のアルゴリズムと同様の処理が
188 (I) huf_encode_end() にて、Huffman coding の後処理を行う。
190 以上から、スライド法の出力ループは(H)が、単語のマッチをinsert_node()
191 と get_next_match() が実行していることがわかる。
192 また、insert_node() が最初のループの直前だけしか呼ばれていないことから
193 insert_node() と get_next_match() の処理は似ているのではないかと
196 とにかく、insert_node() および get_next_match() の解析を進める
199 前の予想があっているかどうか、insert_node() および get_next_match()
202 ・・・見たところ、insert_node() の処理は大きい、また get_next_match()
203 は予想以上に小さく、どうやら勘ははずれてこの二つの処理は違うことを行っ
206 予想に反して get_next_match() の処理内容は短いので、こちらを先に
218 if (++pos == DICSIZ * 2) {
219 memmove(&text[0], &text[DICSIZ], DICSIZ + MAXMATCH);
220 n = fread_crc(&text[DICSIZ + MAXMATCH], DICSIZ, infile);
223 if (outfile != stdout && opts.quiet < 1) {
234 (A) 残りサイズを縮めている。一文字読み進んでいるということだろう。
236 (B) pos を一つ進めている。remainder と対応しているのだろう。
237 なお、if 分の中身は例外条件(pos が端に到達した場合)であるので、
238 無視することにする。(ざっと見ればファイルを読み込んで各変数を
239 再度初期化しているだけであることはわかる)
241 (C), (D) ここがこの処理のメインである。delete_node() して
243 結局、マッチ処理は処理の冒頭にて最初に insert_node() していることから
244 このinsert_node()の部分ではないか?また、delete_node() は「次」を
245 行うのに必要な処理なのではないかとなんとなく考える。
247 ここで、insert_node() と delete_node() のいずれを読むべきか?
248 insert_node() は処理が複雑なので、delete_node() の方を読み進めることと
260 if (parent[pos] == NIL)
268 if (r >= DICSIZ || --childcount[r] > 1)
271 t = position[r] & ~PERC_FLAG;
280 while ((u = position[q]) & PERC_FLAG) {
286 position[q] = (s | DICSIZ);
294 position[q] = s | DICSIZ | PERC_FLAG;
297 s = child(r, text[t + level[r]]);
308 parent[s] = parent[r];
315 なかなか複雑である。が、このまま読み進める(insert_node()よりはマシだか
316 ら) ここまで、PERCOLATEの部分は無視してきたが、PERCOLATEは未定義である
317 として読み進めることにする。PERCOLATE の部分はアルゴリズムの改善を行っ
318 ている部分のように思えるので、より複雑である可能性が高いからまずは基本
322 PERCOLATE を未定義であるとして、処理を再掲する。
330 if (parent[pos] == NIL)
343 if (r >= DICSIZ || --childcount[r] > 1)
354 s = child(r, text[t + level[r]]);
373 parent[s] = parent[r];
379 これだけ削るだけでも、なんとか解析できそうな気がしてくる。モチベーショ
384 if (parent[pos] == NIL)
387 例外条件なので、無視する。ただし、変数pos(これは現在読んでいる位置であ
388 るようだ)を使用している。これはこの関数の入力であるらしいことは意識する。
389 (入力と出力が何であるかは常に意識すべき)
403 である。ただし、r と s はそれぞれ右辺値であることから
405 next[prev[pos]] = next[pos];
406 prev[next[pos]] = prev[pos];
408 である。なんとなくわかりそうな気がする。日本語に訳すと
409 pos の prev の next は、pos の next である。
410 pos の next の prev は、pos の prev である。
423 つまりは、双方向リンクリストで pos を中心に環状にリンクを作っている。
430 pos の親を取得し、pos の親を NIL で上書きしている。
431 pos の親を「取り出している」と読めばよいのか?
435 if (r >= DICSIZ || --childcount[r] > 1)
444 r つまり、pos の親の position を取得している。変数名から pos の親のマッ
452 例外条件であろうか?t つまり、posの親の position が pos より大きい
453 場合これは text バッファにおいて、posより右側の位置を t が指している
454 ことを示しているのだろう。このような場合があるのかどうかはわからないが
459 s = child(r, text[t + level[r]]);
461 r つまり、pos の親と text[t + level[r]] つまり、pos の親の位置 t + level[r]
462 を引数に child() 関数を呼んでいる。わかりそうなわからなさそうな・・・
472 これは前に出てきたのと同じである。つまり、
474 next[prev[s]] = next[s];
475 prev[next[s]] = prev[s];
477 で、child()の戻り値についてリンクトリストを構築している。
484 r つまり、pos の親の prev について
486 next[prev[parent[pos]]] = child()
487 prev[child()] = prev[parent[pos]]
489 としている。親の前の次と子を、子の前を親の前と繋げている。
497 parent[s] = parent[r];
502 parent[r] つまり、posの親の親とparent[child()]
505 なんだかわかりそうなわからなさそうな・・・まだ情報が足りない。
507 キモとしては、next[], prev[], parent[] を理解すれば、データ構造が見えて
508 くるであろうことから、この辺りの操作をしている小さめの関数を探してみる
511 ざっと見て、child(), makechild(), split() が見つかる。これらは
512 ほとんど、insert_node() で呼ばれている。
523 といったコールグラフとなっている。insert_node()が複雑であることから
524 やはり、これらの小さな関数群をまず調べるべきであろう。
526 ボトムアップ的にまずは、makechild() から
529 makechild(node q, uchar c, node r)
530 /* Let r be q's child for character c. */
544 コメントから q の子を r につなげる処理らしい。
546 まず、q と文字 c からハッシュ値を求める。
581 r がちゃんと挿入されていることがわかる。
586 r の parent として、q を設定している。
587 next[], prev[] が q と c のHASH値をさしているのに対し、
588 parent[] は、q そのものを指しているらしい。
592 q の子を繋げたので、q の子の数を足しているらしい。
597 child(node q, uchar c)
598 /* q's child for character c (NIL if not found) */
602 r = next[HASH(q, c)];
603 parent[NIL] = q; /* sentinel */
604 while (parent[r] != q)
611 親は、直接 parent[] で取得できるが、子を取得するには、
612 リンクをたどる必要があることを示すようだ。
614 q の next を取得し、parent[] が q である r を探している。見つからなけれ
615 ば sentinel として設定してある NIL を返すようだ。
619 ・q と c のHASH値により、next[], prev[] が指す先は q の子の候補である。
643 parent[new] = parent[old];
644 level[new] = matchlen;
646 makechild(new, text[matchpos + matchlen], old);
647 makechild(new, text[pos + matchlen], pos);
650 ここで、ようやく元々の処理目的であるマッチ位置、マッチ長を指す変数が
654 ・avail を新たに next[new] に設定
655 ・new の childcount は 0
657 どうやら avail は空きの位置を示しているらしく、新たな node の割り当て
662 で次の空きをavailにセットしているようだ。avail, next[avail], next[next[avail]] は
663 空き領域のリストとなるらしい。そういえば、初期化の処理で
666 for (i = 1; i < DICSIZ - 1; i++)
668 next[DICSIZ - 1] = NIL;
670 というのがあったが、next の領域(下図)のうち、左、1/3 は、この空きリス
671 トをさすらしい(そして、そのリンクは、next[DICSIZ-1]でNILとなる。
672 なお、next[0] は使われておらず、next[1] -> 2 から始まるらしい。
674 -------------------------------------------------------------------------------------------
676 DICSIZ DICSIZ*2 max_hash_val + 1
678 +---------------------+---------------------+---------------------+----+
679 next | 234... NIL| | NIL | |
680 +---------------------+---------------------+---------------------+----+
682 -------------------------------------------------------------------------------------------
684 ・old の prev は、new の prev に設定(あわせて next も再設定)
685 ・new の parent を old の parent に
689 ・newの level に matchlen を設定
690 ・newの position に pos を設定
691 ・new の子として old および pos を設定
693 注目すべきは、最後の makechild() 2連発である。
694 最初の makechild() により、matchpos + matchlen の文字を old として、
695 子に連結し、次の makechild() により、新たな pos + matchlen を
698 ここで、matchpos, matchlen は前回のマッチを、pos, matchlen は
699 今回のマッチを指し、new の子の浅い方がより最近のマッチを指すことを
702 ここまでで、どうやらおおよその構造の使い方はつかめて来たが、まだまだよ
703 くわからない。結局、 insert_node() を読み解くまでは全貌はつかめないよう
706 insert_node() を読み進めることにする。例によって、PERCOLATE は未定義として
719 r = (matchpos + 1) | DICSIZ;
720 while ((q = parent[r]) == NIL)
722 while (level[q] >= matchlen) {
736 q = text[pos] + DICSIZ;
738 if ((r = child(q, c)) == NIL) {
739 makechild(q, c, pos);
755 matchpos = position[r] & ~PERC_FLAG;
761 t1 = &text[pos + matchlen];
762 t2 = &text[matchpos + matchlen];
764 while (matchlen < j) {
774 if (matchlen >= MAXMATCH)
780 if ((r = child(q, *t1)) == NIL) {
781 makechild(q, *t1, pos);
799 next[r] = pos; /* special use of next[] */
803 最初に(A) matchlen >= 4 の場合を扱っている一旦無視することにする。
805 (B) matchlen < 4 の場合である。
807 q = text[pos] + DICSIZ;
810 ここで、pos の位置の文字と pos + 1 の位置の文字を取得している。
811 pos には DICSIZ を足しているがそのことは一旦無視する。
813 if ((r = child(q, c)) == NIL) {
814 makechild(q, c, pos);
819 1文字目の q と 2文字目の c を使って child() を呼び出している。つまり、
820 q の次に c が続く文字を木から探しているようだ。見つからない場合(NIL)は、
821 makechild() により、この2文字を木に登録し、matchlen = 1 としてこの関数
822 を抜けている。matchlen は実際のマッチ長が 1 の場合は、見つからなかった
827 見つかった場合は、最低 2 文字はマッチしているということから
828 matchlen を 2 に設定して次の処理に進む。
837 r つまり、q の子が DICSIZ以上であれば、j を MAXMATCHに matchpos を r に
838 設定している。child() の戻り値 r は pos を示すようだ。
843 matchpos = position[r] & ~PERC_FLAG;
846 DICSIZ より小さい場合は、j を level[r] に設定し、
847 matchpos を position[r] の位置にしている。position[r] も位置を示すらしい。
853 matchpos が pos 以上を指す場合は、matchpos から DICSIZ を引いている。
854 スライド前の位置を差す場合を想定しているのか?
857 t1 = &text[pos + matchlen];
858 t2 = &text[matchpos + matchlen];
860 t1 が現在の文字(の末尾)、t2がマッチ文字(の末尾)を指す。
863 while (matchlen < j) {
873 matchlen が j (最大マッチ長)となるまで、*t1 と *t2 を比較し、
874 一致する間先に進めている(matchlen++)そして、一致しない部分を
875 見つけたときに、split(r) により、木を分割している。
876 以前、split(r) を見たときに詳細は不明だが、新しいマッチを
877 木に登録する処理のように見えた。なんとなく理解できる。
880 if (matchlen >= MAXMATCH)
883 matchlen == j となったとき、matchlen が MAXMATCH より大きければ
884 break して for() ループを抜ける。
889 matchlen が MAXMATCH に達しない間は、新しい pos を position[r] に
894 if ((r = child(q, *t1)) == NIL) {
895 makechild(q, *t1, pos);
899 q を r にし、q と t1 について木を探索する
900 見つからなければ、新たな q, t1, pos について makechild() して
907 みつかるようなら matchlen を伸ばして、より長いマッチがあるか探索する。
918 (L) 以降は for() を抜けた後の処理である。つまり、matchlen が MAXMATCH
919 以上となった場合。このとき、木を更新している。
920 r を取り除き、pos とすり替えている。
925 next[r] = pos; /* special use of next[] */
927 pos の親を q とし、r の親をNILに、r の next は pos としているが、
928 先ほど木から切り離していることおよび、コメントから木とは関係ない
931 nextやprev、parentに対して似たような処理が何度か現れていた
936 ここまで読み進めてもはっきり言ってよくわからない
937 なぜかというとデータ構造がまだよくわからないからである
939 そこで今度は各変数を中心に処理を追うことにする
941 なお現時点でわかった情報を変数の一覧に書き込んでおく
944 text・・・読み込んだテキストのバッファ兼辞書
945 remainder・・・読み込んでいるデータのバッファ上の残りサイズ
947 matchpos・・・pos位置の文字とマッチした辞書上の位置
948 matchlen・・・pos位置の文字とマッチした辞書上のマッチ長
950 (2) 以下により木を現している。ざっと処理をみてトライ木なのではないかと予想
955 (3) 以下は上の木に格納されている補足情報らしい
961 avail・・・nextの空きを指すリストのトップ
963 ここで、不明確なのは(2)なので、この構造について追求してみる。
965 まず、型 next, prev, parent (ついでに、position) はいずれも node 型(の配列)である。
966 その添え字はというと、next を中心に利用している箇所を上から順に探してみると
968 child(node q, uchar c)
969 /* q's child for character c (NIL if not found) */
973 r = next[HASH(q, c)];
974 parent[NIL] = q; /* sentinel */
975 while (parent[r] != q)
980 next の添え字は、HASH(q, c) である。その後の
981 parent[r], next[r] の記述を見るとどうやら添え字も node である。
987 q = text[pos] + DICSIZ;
989 if ((r = child(q, c)) == NIL) {
990 makechild(q, c, pos);
996 child() の第一引数は、pos 位置の文字 text[pos] に DICSIZ を足している。
997 第二引数は、pos の次の位置の文字である。
999 child() は、next[HASH(第一引数, 第二引数)] を実行するのであった。
1000 ここで、HASH関数がどうなっているか見てみると
1002 #define HASH(p, c) ((p) + ((c) << (DICBIT - 9)) + DICSIZ * 2)
1006 + ((c) << (DICBIT - 9))
1009 である。HASH()の値はnext[]の添え字であり、常に DICSIZ * 2 以上である(c
1010 はunsigned charなので、<< により、負になることはない)。
1011 したがって、下図 *1 の部分の範囲がHASH()の値である。
1013 さらに言うと、HASH(p, c) の p は、text[pos] + DICSIZ なので、
1014 (この部分に関して言えば)、*2 の部分を返すことになる。
1016 -------------------------------------------------------------------------------------------
1018 DICSIZ DICSIZ*2 max_hash_val + 1
1020 +---------------------+---------------------+---------------------+----+
1021 next | 234... NIL| | NIL | |
1022 +---------------------+---------------------+---------------------+----+
1024 |----------- *1 -----------|
1027 -------------------------------------------------------------------------------------------
1029 ざっと、HASH() を使用している箇所を探してみると、他に makechild() がある
1030 これも、makechild()の第一、第二引数がそのままHASH()の引数となる。
1031 では、child(), makechild() を使用している箇所を探すと
1033 split() で、使用する new (avail で空きリストが割り当てられる箇所)がmakechild()
1034 の第一引数となる場合がある、*1 の範囲がHASH()の戻りのようだ。
1036 HASH() の ((c) << (DICBIT - 9)) の部分を考えてみる c は、text[] の値
1037 (つまり、文字)を示すので、c の値は、0 〜 255 である。そうすると
1038 DICBIT は、とりあえず13(-lh5)であるからビット列であらわすと
1041 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1042 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1043 | 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1| 0| 0| 0| 0|
1044 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1048 と11ビット目から4ビット目までを使用した値の範囲となる。
1049 ここでは、node 型(実体はunsigned short)の範囲を想定している。
1050 なお、short が16bitであることも前提となっているように思う。
1052 なお、2 * DICSIZ は、(DICSIZ は、1 << DICBITであるので) 以下である。
1054 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1055 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1056 | 0| 1| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0|
1057 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1059 p はというと、今のところ 1 から 255 + DICSIZ の範囲なので、
1061 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1062 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1063 | 0| 0| 1| 0| 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1|
1064 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1068 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1069 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1070 p | 0| 0| 1| 0| 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1|
1071 c<<4 | 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1| 0| 0| 0| 0|
1072 2*DICSIZ | 0| 1| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0|
1073 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1074 max 0 1 1 1 0 0 0 0 1 1 1 0 1 1 1 1
1076 最大でも、max の値にしかならない、これは、next の領域サイズに使われている
1078 #define MAX_HASH_VAL (3 * DICSIZ + (DICSIZ / 512 + 1) * UCHAR_MAX)
1084 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1085 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1086 | 0| 1| 1| 1| 0| 0| 0| 0| 1| 1| 1| 0| 1| 1| 1| 1|
1087 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1088 `-----' `-----------------------------------'
1090 3 * DICSIZ (DICSIZ / 512 + 1) * UCHAR_MAX{255}
1092 DICSIZ は、2^13、512 は、2^9 であるから、DICSIZ / 512 は、2^4 である)
1094 見事一致している。なにか納得いかない同じ値を示すのに、どうして違う計算式を
1095 使うのだろう?もう一度、HASH()とMAX_HASH_VALを列挙してみよう。
1097 #define HASH(p, c) ((p) + ((c) << (DICBIT - 9)) + DICSIZ * 2)
1098 #define MAX_HASH_VAL (3 * DICSIZ + (DICSIZ / 512 + 1) * UCHAR_MAX)
1101 p = DICSIZ + UCHAR_MAX
1105 #define HASH(p, c) ((DICSIZ+UCHAR_MAX) + ((UCHAR_MAX) << (DICBIT - 9)) + DICSIZ * 2)
1109 #define HASH(p, c) (3 * DICSIZ + UCHAR_MAX + ((UCHAR_MAX) << (DICBIT - 9)))
1111 << をべき乗(^ はここでは、べき乗)であらわすと(x << n)は、x * 2^(n)であるから
1113 #define HASH(p, c) (3 * DICSIZ + UCHAR_MAX + ((UCHAR_MAX)* 2^(DICBIT - 9)))
1117 #define HASH(p, c) (3 * DICSIZ + UCHAR_MAX * (1 + 2^(DICBIT - 9)))
1119 x^(a-b) は、x^a / x^b であるから
1121 #define HASH(p, c) (3 * DICSIZ + UCHAR_MAX * (1 + 2^DICBIT / 2^9))
1123 これで、MAX_HASH_VALと同じになった。やはり、MAX_HASH_VALは基のHASH()関数の計算式を保存して、
1125 #define MAX_HASH_VAL ((DICSIZ+UCHAR_MAX) + ((UCHAR_MAX) << (DICBIT - 9)) + DICSIZ * 2)
1129 #define MAX_HASH_VAL HASH(DICSIZ + UCHAR_MAX, UCHAR_MAX)
1131 とした方がわかりやすい。どうせ定数の計算式だからコンパイラが計算してくれるのである。
1133 横道にそれた、HASH()関数についてもう一度再考する。
1136 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1137 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1138 p | 0| 0| 1| 0| 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1|
1139 c<<4 | 0| 0| 0| 0| 1| 1| 1| 1| 1| 1| 1| 1| 0| 0| 0| 0|
1140 2*DICSIZ | 0| 1| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0|
1141 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1143 2*DICSIZ は、値の範囲をnext[]の領域下図(再掲)の*1の部分に押し上げるためのものである。
1145 -------------------------------------------------------------------------------------------
1147 DICSIZ DICSIZ*2 max_hash_val + 1
1149 +---------------------+---------------------+---------------------+----+
1150 next | 234... NIL| | NIL | |
1151 +---------------------+---------------------+---------------------+----+
1153 |----------- *1 -----------|
1156 -------------------------------------------------------------------------------------------
1158 では、2*DICSIZ を除いて、13ビット目をx その他をyであらわすと
1160 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1161 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1162 p | 0| 0| x| 0| 0| 0| 0| 0| y| y| y| y| y| y| y| y|
1163 c<<4 | 0| 0| 0| 0| y| y| y| y| y| y| y| y| 0| 0| 0| 0|
1164 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1165 x y y y y y y y y y y y y y
1167 13ビット目が立っている場合は、*2 の範囲を表しており、*2 の範囲はほぼDICSIZと同じであることが
1168 わかる。(正確にはMAX_HASH_VAL+1であるから、yyyyyyyyyyyyy は、1000011110000 半分よりちょっと
1171 そして、13ビット目が立っていない場合は、*1 の領域は、やはりyyyyyyyyyyyyyであるつまり、
1172 DICSIZの領域をすべて使っているわけではないのである図を少し訂正しよう。
1174 -------------------------------------------------------------------------------------------
1176 DICSIZ DICSIZ*2 DICSIZ*3
1177 / / / max_hash_val + 1
1178 +---------------------+---------------------+---------------------+----------+
1179 next | 234... NIL| | NIL | NIL |
1180 +---------------------+---------------------+---------------------+----------+
1182 |--- *1 ---| |--- *2 ---|
1185 -------------------------------------------------------------------------------------------
1187 *1 と *2 は同じである。max_hash_val + 1 - DICSIZ*3 は、*2 + 1 なので、
1190 *1 と *2 は、DICSIZ/2 より少し大きい
1192 もう少し、HASH()について考える。13 bit目をとりあえず無視して
1198 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1199 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1200 p | 0| 0| x| 0| 0| 0| 0| 0| y| y| y| y| y| y| y| y|
1201 c<<4 | 0| 0| 0| 0| y| y| y| y| y| y| y| y| 0| 0| 0| 0|
1202 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1203 x y y y y y y y y y y y y y
1206 p, c の2文字(2*8bit)の情報を、12bitに写像(桁上がりも考慮)しており、p の
1207 上位4bitとcの下位4bit を使っている。これを行う理由ははっきりとはまだわ
1208 からないが、HASH()が指す node は、引数の組み合わせにより同じ値を持つ場
1211 c<<4 はc<<(DICBIT-9)であるが、DICBITが大きくなるとbitをずらす範囲が広がる。
1212 c<<(DICBIT-9) は、2<<DICBIT つまり13bit目を上書きしない程度に2文字目の情報を
1213 ずらしているためであるから、DICBIT-9の意味は、DICBIT-8-1(8 は1文字のbit数で、
1214 DICBITから8bit+桁上がりの1bit引いて、何bit分左シフト可能かを表しているようだ。
1216 必然的に、辞書サイズがたとえば、DICBIT{16} の場合(これは-lh7- の場合)、
1221 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1222 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1223 p | 0| 0| x| 0| 0| 0| 0| 0| 0| 0| 0| y| y| y| y| y| y| y| y|
1224 c<<7 | 0| 0| 0| 0| y| y| y| y| y| y| y| y| 0| 0| 0| 0| 0| 0| 0|
1225 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
1226 x y y y y y y y y y y y y y y y y
1228 1文字目と2文字目は1bitのみしか重ならないため、同じHASH値が衝突する確率
1229 は減る。ただし、HASHの戻り値の型は 18bit (0〜17bit目まで)で、 16bit整数
1230 では収まらないので32bit整数にする必要がある。
1232 HASH()の戻り値については、next[]の添え字でしか使っていないので、まあ今時の
1235 node 型はというと、next[] が格納する値は、next[]の添え字である。
1237 つまり、next[] は、常に以下の図の領域のいずれかの位置(NILはリンク先がな
1238 いの意味)を指している。ということは、node型は MAX_HASH_VAL の値の範囲が
1239 必要であり、これは 32bit 整数なのである。
1241 下図で一つ一つの要素は32bitあるが、使うのは18bitだけとはちょっとさすがに
1244 どのくらいもったいないかというと、DICBIT{16}のとき(辞書サイズ64K)の
1245 MAX_HASH_VALは、229503 であり、その一要素が32bit とすると約896Kである。
1246 next だけで、1M近い領域をmalloc()することになる。
1248 しかし、HASH値は前にも述べたとおり、下図の *1 *2 の範囲しか使用しない。
1250 -------------------------------------------------------------------------------------------
1251 DICSIZ DICSIZ*2 DICSIZ*3
1252 / / / max_hash_val + 1
1253 +---------------------+---------------------+---------------------+----------+
1254 next | 234... NIL| | NIL | NIL |
1255 +---------------------+---------------------+---------------------+----------+
1256 |--- *1 ---| |--- *2 ---|
1259 -------------------------------------------------------------------------------------------
1261 -lh7- 対応は考える必要がありそうだ。