"simple_hashing": Hash table and binary searching library in C |
メンバ名 | 型 | 意味 |
---|---|---|
make_hash_value | ssize_t (*)(char **keys, size_t n_keys, ssize_t depth) | 'n_keys' 個のキー文字列 'keys' からハッシュ値を求める「ハッシュ関数」への関数ポインタです。 ユーザが指定するハッシュ関数はハッシュ値が求められない場合に負整数を返す必要があります。ハッシュ値の範囲は 0 〜 LONG_MAX です。 API デフォルトのハッシュ関数は simple_hash_value() 静的関数として simple_hashing.c に定義しています。simple_hash_value() がエラーを起こした場合は ILLEGAL_HASH_VALUE を返しています。 |
cmp_keys_element | int (*)(char **keys, size_t n_keys, void *elem) | データ要素 'elem' を 'n_keys' 個の 'keys' キー文字列と比較する「比較関数」への関数ポインタです。 この関数は API ユーザが実装して、その関数アドレスをハッシュ・テーブル作成時に指定する必要があります。比較関数の要件については create_hash_table() の説明も参考にしてください。 比較関数の実装例には、unit_test.c で定義している cmp_keys() 静的関数があります。 |
elem_cnt_per_page | ssize_t | システムのメモリ・ページに格納できるデータ要素数を示します。 システムのメモリーページ・サイズは sysconf(SC_PAGESIZE) あるいは NBPG マクロのいずれかが使用できる場合はその値をとります。いずれも使用できない場合は 4096kbytes (HASHTABLE_SYSTEM_PAGESIZE_DEFAULT マクロで定義) であると仮定しています。 |
depth | ssize_t | ハッシュ・テーブル生成時に指定されたエントリの大きさを示します。 この大きさは、ハッシュ値による振り分けを行いたい数より大きく、かつ、最小の素数を指定するのがよいでしょう。例えば、1000 エントリにデータ要素を振り分けるならば depth には 1009 を指定します。 100,000 番目までの素数のリストが 100000-primary-numbers.txt にあります。 |
used_cnt | ssize_t | 一つ以上のデータ要素を挿入したエントリの個数を示します。 この値と depth メンバの値の比が、ハッシュ・テーブルの使用率となります。 このメンバの値は get_used_cnt_hash_table() で取得することができます。 |
max_insert_cnt | ssize_t | ハッシュ・テーブルに挿入できるデータ要素の最大数を示します。 API デフォルト値は LONG_MAX です。 ハッシュ・テーブルの生成に渡すヒント情報で指定できます。 |
alloc_base_cnt | ssize_t | あるハッシュ・テーブル・エントリに初めてデータ挿入を行う際のデータ要素配列長を示します。 API デフォルト値は上記 elem_cnt_per_page の値です。 ハッシュ・テーブルの生成に渡すヒント情報で指定できます。 |
alloc_ext_cnt | ssize_t | あるハッシュ・テーブル・エントリのデータ要素配列が不足した際に、メモリ拡張を行う増分のデータ要素数を示します。 API デフォルト値は上記 elem_cnt_per_page の値です。 ハッシュ・テーブルの生成に渡すヒント情報で指定できます。 |
inserted_cnt | ssize_t | ハッシュ・テーブルに挿入されているデータ要素の総数を示します。 simple_hashing.h に、挿入されたデータ要素が最大値に達したことを確認する判別マクロ IS_HASHTABLE_TABLES_FULL が定義してあります。 このメンバの値は get_inserted_cnt_hash_table() で取得することが出来ます。 |
tables | struct hashtable_elems_t [1] | depth 個のハッシュ・テーブルのエントリを配列として配置するための可変長配列です。 depth で指定されている個数のエントリがハッシュ・テーブル生成時にメモリ獲得されます。つまり、[0] から [depth - 1] までの配列添え字が有効になります。 |
ハッシュ・テーブル構造体は create_hash_table() で生成し、destroy_hash_table() で破棄する必要があります。
ハッシュ・テーブル構造の tables[] メンバとして使用しているエントリ構造体です。「ハッシュ関数 make_hash_value」を使用してキー文字列から求めたハッシュ値を、tables[] 配列の添え字に適用することでエントリの一つが決まります。
エントリそれぞれには、ハッシュ値を同じくするデータ要素の個数・データ要素配列の要素数・データ要素配列として確保したメモリ領域へのポインタから構成されています。
ハッシュ・テーブル・エントリ構造体のメンバそれぞれの意味は下表に示したとおりになります。
メンバ名 | 型 | 意味 |
---|---|---|
duplicated_cnt | ssize_t | ハッシュ値が等しいデータ要素の個数を示します。 elements メンバが指すデータ要素ポインタ配列の [0] 〜 [duplicated_cnt - 1] の要素それぞれが挿入されているデータ要素を指すことになります。 このメンバの値は get_duplicated_cnt_hash_table() で取得できます。 |
elements_len | ssize_t | データ配列ポインタ配列として確保したメモリ領域の要素数を示します。 elements メンバが指すデータ要素ポインタ配列は [0] 〜 [elements_len - 1] までの要素が確保されていることになります。 挿入したデータ要素数が最大値に達していることを確認する判定マクロ IS_HASHTABLE_ELEMS_ELEMENTS_FULL を simple_hashing.h に定義しています。 |
elements | void ** | データ要素ポインタの配列を指します (つまり、データ要素へのポインタのポインタ)。 データ要素ポインタ配列はデータ要素をハッシュ・テーブルに挿入する際に必要なだけ動的に確保されます。 |
ハッシュ・テーブル・エントリ構造の elements メンバが指すデータ要素配列のメモリ領域は、insert_hash_table() および replace_hash_table() で必要時に動的獲得されます。また、獲得されたメモリ領域は destroy_hash_table() が呼び出されるまで解放されません。
static ssize_t simple_hash_value(char **keys
, size_t n_keys
, ssize_t depth
)
depth 個のエントリを持つハッシュ・テーブル table の table[] 配列メンバに対して添え字として使用する「ハッシュ値」を、n_keys 個の文字列ポインタ配列 keys から算出して返します。
ハッシュ値の算出は keys 配列の文字列ポインタそれぞれについて以下の処理を繰り返した後、ハッシュ値の符号 bit を落としてから depth のモジェロを求めることで行っています。説明上、ハッシュ値の初期値はゼロとしています。
引数が不正な場合、ILLEGAL_HASH_VAL を返します。
性能測定の結果は以下の各表のようになりました。結論として、ハッシュ・テーブル生成時にはヒント情報に挿入するデータ要素数を指定するのが処理速度とメモリ獲得量のバランスがよくなっています。
Hashtable_extend_hint_t hint;
hint.max_insert_cnt = <挿入データ要素総数>;
hint.elems_base_cnt = HASHTABLE_RECOMMENDED_ELEMS_BASE_CNT;
hint.elems_ext_cnt = HASHTABLE_RECOMMENDED_ELEMS_EXT_CNT;
create_hash_table(..., &hint);
データ要素挿入量をハッシュ・テーブル生成時に決定できない場合は、create_hash_table() API にヒント情報に NULL を渡すのがよいでしょう。
create_hash_table(..., NULL);
データ検索の処理時間は下表のようになっています。
ヒント情報設定 | 検索処理時間 (sec.) | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
17298 | 34596 | 51894 | 69192 | 86490 | 103788 | 121086 | 138384 | 155682 | 172980 | |
max_insert_cnt : API デフォルト (= LONG_MAX) elems_base_cnt : API デフォルト (= 1024) elems_ext_cnt : API デフォルト (= 1024) | 0.025 | 0.059 | 0.137 | 0.160 | 0.203 | 0.259 | 0.308 | 0.363 | 0.441 | 0.485 |
max_insert_cnt : データ要素総数 elems_base_cnt : API で最適値を算出 elems_ext_cnt : API で最適値を算出 | 0.023 | 0.060 | 0.102 | 0.149 | 0.199 | 0.252 | 0.306 | 0.371 | 0.428 | 0.493 |
max_insert_cnt : API デフォルト elems_base_cnt : 1 elems_ext_cnt : 1 | 0.024 | 0.061 | 0.108 | 0.164 | 0.217 | 0.267 | 0.325 | 0.394 | 0.451 | 0.517 |
データ挿入の処理時間は下表のようになっています。
ヒント情報設定 | 挿入処理時間 (sec.) | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
17298 | 34596 | 51894 | 69192 | 86490 | 103788 | 121086 | 138384 | 155682 | 172980 | |
max_insert_cnt : API デフォルト (= LONG_MAX) elems_base_cnt : API デフォルト (= 1024) elems_ext_cnt : API デフォルト (= 1024) | 0.054 | 0.112 | 0.190 | 0.251 | 0.335 | 0.405 | 0.495 | 0.611 | 0.732 | 0.835 |
max_insert_cnt : データ要素総数 elems_base_cnt : API で最適値を算出 elems_ext_cnt : API で最適値を算出 | 0.052 | 0.103 | 0.170 | 0.242 | 0.319 | 0.413 | 0.487 | 0.602 | 0.725 | 0.849 |
max_insert_cnt : API デフォルト elems_base_cnt : 1 elems_ext_cnt : 1 | 0.065 | 0.149 | 0.251 | 0.362 | 0.491 | 0.637 | 0.789 | 0.961 | 1.206 | 1.474 |
データ差換の処理時間は下表のようになっています。
ヒント情報設定 | 差換え処理時間(sec.) ※下段は検索処理時間を除いた値 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
17298 | 34596 | 51894 | 69192 | 86490 | 103788 | 121086 | 138384 | 155682 | 172980 | |
max_insert_cnt : API デフォルト (= LONG_MAX) elems_base_cnt : API デフォルト (= 1024) elems_ext_cnt : API デフォルト (= 1024) | 0.104 | 0.190 | 0.341 | 0.433 | 0.559 | 0.680 | 0.816 | 0.951 | 1.111 | 1.214 |
0.054 | 0.072 | 0.067 | 0.113 | 0.153 | 0.162 | 0.200 | 0.225 | 0.229 | 0.244 | |
max_insert_cnt : データ要素総数 elems_base_cnt : API で最適値を算出 elems_ext_cnt : API で最適値を算出 | 0.084 | 0.192 | 0.313 | 0.432 | 0.564 | 0.690 | 0.820 | 0.952 | 1.082 | 1.236 |
0.038 | 0.072 | 0.109 | 0.134 | 0.166 | 0.186 | 0.208 | 0.210 | 0.226 | 0.250 | |
max_insert_cnt : API デフォルト elems_base_cnt : 1 elems_ext_cnt : 1 | 0.087 | 0.198 | 0.318 | 0.443 | 0.566 | 0.702 | 0.843 | 0.978 | 1.119 | 1.261 |
0.039 | 0.076 | 0.102 | 0.115 | 0.132 | 0.168 | 0.193 | 0.190 | 0.217 | 0.227 |
データ削除の処理時間は下表のようになっています。
ヒント情報設定 | 削除処理時間 (sec.) ※下段は検索時間を除いた値 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
17298 | 34596 | 51894 | 69192 | 86490 | 103788 | 121086 | 138384 | 155682 | 172980 | |
max_insert_cnt : API デフォルト (= LONG_MAX) elems_base_cnt : API デフォルト (= 1024) elems_ext_cnt : API デフォルト (= 1024) | 0.050 | 0.125 | 0.215 | 0.290 | 0.395 | 0.501 | 0.620 | 0.725 | 0.860 | 0.961 |
0.025 | 0.066 | 0.078 | 0.130 | 0.192 | 0.242 | 0.312 | 0.362 | 0.419 | 0.476 | |
max_insert_cnt : データ要素総数 elems_base_cnt : API で最適値を算出 elems_ext_cnt : API で最適値を算出 | 0.050 | 0.116 | 0.198 | 0.287 | 0.390 | 0.498 | 0.622 | 0.728 | 0.837 | 0.973 |
0.027 | 0.056 | 0.096 | 0.138 | 0.191 | 0.246 | 0.316 | 0.357 | 0.409 | 0.480 | |
max_insert_cnt : API デフォルト elems_base_cnt : 1 elems_ext_cnt : 1 | 0.050 | 0.121 | 0.207 | 0.297 | 0.408 | 0.519 | 0.631 | 0.740 | 0.867 | 0.991 |
0.026 | 0.060 | 0.099 | 0.133 | 0.191 | 0.252 | 0.306 | 0.346 | 0.416 | 0.474 |
データ要素数に対する動的メモリ獲得量は下表のようになっています。
ヒント情報設定 | 動的メモリ獲得量 (bytes) | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
17298 | 34596 | 51894 | 69192 | 86490 | 103788 | 121086 | 138384 | 155682 | 172980 | |
max_insert_cnt : API デフォルト (= LONG_MAX) elems_base_cnt : API デフォルト (= 1024) elems_ext_cnt : API デフォルト (= 1024) | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 | 4145008 |
max_insert_cnt : データ要素総数 elems_base_cnt : API で最適値を算出 elems_ext_cnt : API で最適値を算出 | 85756 | 155868 | 226080 | 295384 | 364320 | 433300 | 503972 | 573580 | 642284 | 711912 |
max_insert_cnt : API デフォルト elems_base_cnt : 1 elems_ext_cnt : 1 | 81336 | 150528 | 219720 | 288912 | 358104 | 427296 | 496488 | 565680 | 634872 | 704064 |
Hashtable_t create_hash_table(
ssize_t depth
, ssize_t (*make_hash_value)(
char **keys
, size_t n_keys
, ssize_t depth
)
, int (*cmp_keys_element)(
char **keys
, size_t n_keys
, void *elem
)
, Hashtable_extend_hint_t *hint
)
depth 個のエントリを持つハッシュ・テーブルを生成します。呼び出し側は、cmp_keys_element に複数キー文字列とデータ要素の大小関係を比較する関数のアドレスを必ず指定する必要があります。
depth は 1 以上の正整数でなければいけません。指定できる最大値は LONG_MAX ですが、あまり大きなエントリ数を指定するとメモリ不足エラーを起こします。
比較関数は「キー文字列 < データ要素」では負整数を、「キー文字列 = データ要素」ではゼロを、「キー文字列 > データ要素」では正整数を返すように実装されている必要があります。
make_hash_value には複数キー文字列からハッシュ値を求めるハッシュ関数のアドレスを指定できます。この引数に NULL を渡した場合は、API デフォルトのハッシュ関数 simple_hash_value() 静的関数が使用されます。
hint はデータ挿入やデータ差換え操作でデータ要素ポインタ配列 (Hashtable_elem_t.elements が指すメモリ領域) をメモリ拡張する際のヒント情報を渡せます。NULL を渡した場合は、API のデフォルト設定が適用されます。
ヒント情報を指定する場合、ハッシュ・テーブルに挿入する最大データ要素数を Hashtable_extend_t 構造体の max_insert_cnt に、あるハッシュ値に対応するハッシュ・エントリに初めてデータ挿入を行う際のデータ要素配列 (Hashtable_elems_t.elements) の要素数を elems_base_cnt に、そして、データ要素配列のメモリ拡張を行う際の増分データ要素数を elems_ext_cnt に設定します。
設定するメンバそれぞれについて、API デフォルト設定を使用するには、max_insert_cnt に HASHTABLE_SYS_MAX_INSERT_CNT を、elems_base_cnt に HASHTABLE_RECOMMENDED_ELEMS_BASE_CNT を、そして elems_ext_cnt には HASHTABLE_RECOMMENDED_ELEMS_EXT_CNT を与えます。また、データ要素配列のメモリ拡張を行わないように指定するには elems_ext_cnt に HASHTABLE_NEVER_EXTEND を指定します。
生成したハッシュ・テーブルは destroy_hash_table() で破棄する必要があります。
ハッシュ・テーブルが正常に生成できた場合はそのアドレスを返します。なんらかのエラーが発生した場合は NULL を返します。エラーの詳細は errno の値として出力します (正常終了時の errno にはゼロが出力されます)。
エラーが起きた際の errno の値とその意味は以下のとおりです。
size_t get_allocated_size_hash_table(Hashtable_t *table)
ハッシュ・テーブルを作成しデータ挿入することで動的に獲得されたメモリのバイト数を返します。
エラーが起きた場合は戻り値にゼロが返され errno にゼロ以外の値が出力されます。正常終了時の errno には必ずゼロが出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
size_t get_inserted_cnt_hash_table(Hashtable_t *table)
ハッシュ・テーブル table に挿入されているデータ要素の総数を返します。
エラーが起きた場合は戻り値に ULONG_MAX が返され errno にゼロ以外の値が出力されます。正常終了時の errno には必ずゼロが出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
ssize_t get_used_cnt_hash_table(Hashtable_t *table)
ハッシュ・テーブル table でデータ要素を挿入されているエントリの数を返します。
エラーが起きた場合は戻り値に LONG_MIN が返され errno にゼロ以外の値が出力されます。正常終了時の errno には必ずゼロが出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
Hashtable_t *insert_hash_table(Hashtable_t *table
, char **keys
, size_t n_keys
, void *elem
)
ハッシュ・テーブル table に新たなデータ要素 elem を挿入します。挿入できた場合、戻り値に table を返します。
挿入するデータ要素のキー文字列 keys, n_keys が既にハッシュ・テーブルに挿入されている別のデータ要素と同一である (Hashtable_t の cmp_keys_element 関数がゼロを返す) 場合、挿入は行われずエラー (NULL が返され errno に ESRCH を出力) になります。
エラーが発生した場合は戻り値に NULL が返され errno にエラー内容が出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(make_hash_value 関数が負数を返している場合)
(挿入されているデータ要素を別のもので差し替えるには replace_hash_table() を使用します)
Hashtable_t *replace_hash_table(Hashtable_t *table
char **keys
, size_t n_keys
, void *elem
)
キー文字列 keys, n_keys が同一のデータ要素がハッシュテーブル table に挿入されている場合に、データ要素 elem で差し換えます。キー文字列が一致するデータ要素がハッシュ・テーブル中に存在していない場合は、insert_hash_table() と同様に、新規データ要素として elem をハッシュ・テーブルに挿入します。
データ要素の差し替えあるいは挿入ができた場合は、戻り値に table を返します。
エラーが起きた場合は戻り値に NULL が返され errno にエラー内容が出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(make_hash_value 関数が負数を返している場合)
(メモリ不足)
ssize_t get_duplicated_cnt_hash_table(Hashtable_t *table
, char **keys
, size_t n_keys
)
ハッシュ・テーブル table に挿入されていて、キー文字列 keys, n_keys から算出するハッシュ値が同一の挿入済みデータ要素数を返します。
エラーが起きた場合、LONG_MIN を返し errno にエラー内容を出力します。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(make_hash_value 関数が負数を返している場合)
void *get_elem_hash_table(Hashtable_t *table
, char **keys
, size_t n_keys
)
ハッシュ・テーブル table に挿入されているデータ要素をキー文字列 keys, n_keys で検索しそのアドレスを返します。
エラーが起きた場合、NULL を返し errno にエラー内容を出力します。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(make_hash_value 関数が負数を返している場合)
(unfounded の場合に相当)
Hashtable_t *delete_hash_table(Hashtable_t *table
, char **keys
, size_t n_keys
, void *elem
)
ハッシュ・テーブル table に挿入されているデータ要素をキー文字列 keys, n_keys で検索し、elem が NULL でなければそのアドレスが検索したデータ要素と一致することを確認したうえで削除します。正常に削除できた場合、 table を返します。
エラーが起きた場合、NULL を返し errno にエラー内容を出力します。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(make_hash_value 関数が負数を返している場合)
(unfounded の場合に相当)
void *get_elem_by_hashtable_cursor(Hashtable_t *table
, Hashtable_cursor_t **cursor
, Hashtable_cursor_dir_t direct
)
ハッシュ・テーブルに挿入されているすべてのデータ要素を逐次取得するためのトラバース機構を提供します。この機構の実現のために、いわゆるデータ・カーソル方式をとっています。
トラバース機構を使用するには、まず NULL 値を設定したカーソル・ポインタ cursor のアドレスをトラバースするハッシュ・テーブル table とともに渡します。正常終了した場合、カーソル・ポインタに動的メモリが割り当てられ、ハッシュ・テーブルの先頭データ要素位置で初期化され、先頭データ要素のアドレスが返されます。なお、この時は direct の値は無視されます。
次いで、カーソルの位置からハッシュ・テーブルの先頭方向 (あるいは末尾方向) の次のデータ要素アドレスを取得するには、HASHTABLE_CURSOR_DIR_PREVIOUS (あるいは HASHTABLE_CURSOR_NEXT) を direct に渡します。既に先頭 (あるいは末尾) のデータ要素位置にカーソルがある場合に、前方 (あるいは後方) データ要素取得を行うとエラーとなり NULL が返ります (errno は EINVAL)。
direct に HASHTABLE_CURSOR_DIR_CURRENT を渡した場合、現在のカーソル位置にあるデータ要素アドレスが返されます。
カーソルを破棄するには direct に HASHTABLE_CURSOR_DIR_DESTROY を渡します。このとき、カーソルデータ構造のために獲得されたメモリ領域を解放します。また、この操作の正常な戻り値は table になります。
呼び出しのそれぞれの場合でエラーが発生すると NULL が返され errno にエラー内容が出力されます。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
(メモリ不足)
int destroy_hash_table(Hashtable_t *table)
create_hash_table() で生成したハッシュ・テーブル table に割り当てられた動的メモリを解放しハッシュ・テーブルを破棄します。破棄できたらゼロを返します。
エラーが起きた場合は -1 を返し errno にエラー内容を出力します。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(生成されていない/破壊されたハッシュ・テーブル構造のアドレスが渡された場合など)
int bsearch_array(int (*cmp_keys_elements)(char **keys
, size_t n_keys
, void *elem
)
, void **array
, ssize_t n_array
, char **keys
, size_t n_keys
, ssize_t *pos
, int *pos_cmp_result
)
create_hash_table() に渡すものと同等の比較関数 cmp_keys_elements を使用して配列 array, n_array のキー文字列 keys, n_keys による二分探索を行います。
探索に成功した場合はゼロ以外の値を返します。探索に失敗した場合および不正な引数が渡された場合はゼロを返します。
不正な引数が渡されていた場合は errno に EINVAL を出力します。
探索できた (戻り値がゼロ以外の) 場合、pos に配列要素のインデックスを出力します。
一方、探索に失敗した (戻り値がゼロの) 場合は、pos にはデータ要素を挿入する配列要素位置を出力し、pos の直前に挿入を行う場合には pos_cmp_result に負整数を、pos の直後に挿入する場合は正整数を出力します。
エラーが起きた際の errno の値とその意味は以下のとおりです。
(ポインタ引数に NULL が、n_array に負数が、あるいは n_keys に 1 未満の値が渡された場合)