Chapter 20. 永続化ハッシュテーブル

ハッシュテーブルは有限写像の実装でよく使われます。 ATSLIB/libats には、linear chaining と linear probing を用いたハッシュテーブル実装があります。 永続化ハッシュテーブルだけでなく線形ハッシュテーブルのサポートもあります。 線形ハッシュテーブルはプログラマの手で安全に解放できます。 (線形ハッシュテーブルを用いた) 永続化ハッシュテーブルはガベージコレクション (GC) によって安全に回収されます。 この章では、永続化ハッシュテーブルの生成と操作方法を示します。

key_t のキーから型 itm_t の要素へマップする写像が必要だと考えます。 次のコードは本質的に、ATSLIB/libats でのハッシュテーブル実装を用いて、写像の生成と操作を表わすインターフェイスを作っています:

local typedef key = key_t and itm = itm_t in (* in-of-local *) #include "libats/ML/HATS/myhashtblref.hats" end // end of [local]

ハッシュテーブルを用いたプログラミングを単純化するためのラッパーである HATS ファイルは オンライン から入手できます。

key_tstringitm_tint であると仮定します。 次のコードの行は初期容量が 1000 のハッシュテーブルを生成しています:

val mymap = myhashtbl_make_nil(1000)

この場合の容量とは、生成されたハッシュテーブルに関連する配列のサイズであることに注意してください。 ハッシュテーブル実装の根底は linear chaining に基づいていて、このハッシュテーブルは 5000 (5*1000) 要素までリサイズなしに格納できます。 実際にリサイズが必要な場合も、自動的に行なわれます。 次の数行では、いくつかのキー/要素ペアを mymap に挿入します:

// val-~None_vt() = mymap.insert("a", 0) val-~Some_vt(0) = mymap.insert("a", 1) // val-~None_vt() = mymap.insert("b", 1) val-~Some_vt(1) = mymap.insert("b", 2) // val-~None_vt() = mymap.insert("c", 2) val-~Some_vt(2) = mymap.insert("c", 3) //

ドット表記 .insert は関数 myhashtbl_insert でオーバーロードされています。 キーと要素が与えられたとき、mymap.insert はそのキー/要素ペアを mymap に挿入します。 もし、そのキーが挿入前の写像 mymap の定義域内である場合、そのキーに関連した元の要素が返ります。 そうでない場合、要素は何も返りません。 予想されることですが、mymap のサイズはこの時点で 3 になります:

val () = assertloc (mymap.size = 3)

ドット表記 .size は関数 myhashtbl_get_size によってオーバーロードされており、 この関数は与えれられたハッシュテーブル内に保管されているキー/要素ペアの個数をを返します。 デバッグのために、与えられたハッシュテーブル中のキー/要素ペアを印字したいと思うかもしれません:

// val () = fprintln! (stdout_ref, "mymap = ", mymap) //

このとき、シンボル fprintfprint_myhashtbl でオーバーロードされています。 次の2行のコードは、ハッシュテーブルに対して与えられたキーを検索する方法を示しています:

val-~None_vt() = mymap.search("") val-~Some_vt(1) = mymap.search("a")

ドット表記 .search は関数 myhashtbl_search でオーバーロードされていて、この関数はもし見つかれば与えられたキーに関連した要素を返します。 次の数行のコードは mymap からキー/要素を削除しています:

// val-true = mymap.remove("a") val-false = mymap.remove("a") // val-~Some_vt(2) = mymap.takeout("b") val-~Some_vt(3) = mymap.takeout("c") //

ドット表記 .remove は、与えられたキーのキー/要素ペアを削除する関数 myhashtbl_remove でオーバーロードされています。 もしキー/要素ペアが削除された場合、この関数は true を返します。 そうでない場合、この関数は false を返し、これは与えられたキーに対応するキー/要素ペアが操作するハッシュテーブルに保管されていないことを示しています。 ドット表記 .takeout は関数 myhashtbl_takeout でオーバーロードされていて、この関数は削除した要素を返すことを除いて myhashtbl_remove と同じです。 次の数行のコードは、ハッシュテーブルに対するあまり一般的に使われない関数の使用例です:

// val () = mymap.insert_any("a", 0) val () = mymap.insert_any("b", 1) val () = mymap.insert_any("c", 2) val kxs = mymap.listize1((*void*)) val ((*void*)) = fprintln! (stdout_ref, "kxs = ", kxs) val kxs = mymap.takeout_all((*void*)) val ((*void*)) = fprintln! (stdout_ref, "kxs = ", kxs) // val () = assertloc (mymap.size = 0) //

ドット表記 .insert_any は関数 myhashtbl_insert_any でオーバーロードされていて、この関数は、キーが使用中かどうかにかかわらず、与えられたキー/要素ペアを 常に 挿入します。 この関数は使用をさけるか、与えられたキーが使用中でない確証があるときのみ呼び出すべきでしょう。 そうでなければ、ハッシュテーブルが破損してしまうかもしれません。 ドット表記 .listize1.takeout_all はそれぞれ2つの関数 myhashtbl_listize1myhashtbl_takeout_all でオーバーロードされています。 これらは両方とも、与えられたハッシュテーブル中の全てのキー/要素ペアから成るリストを返します; 前者はハッシュテーブルを無変更で保持するのに対して、後者はハッシュテーブルを空にします。 最後に、与えられたハッシュテーブル内の全てのキー/要素ペアに対するイテレータのインターフェイスを次に示します:

// extern fun myhashtbl_foreach_cloref ( tbl: myhashtbl , fwork: (key, &(itm) >> _) -<cloref1> void ) : void // end-of-function //

例として、次のコードは与えられたハッシュテーブル内のキー/要素ペア全てを印字します:

// val () = myhashtbl_foreach_cloref ( mymap , lam (k, x) => fprintln! (stdout_ref, "k=", k, " and ", "x=", x) ) (* myhashtbl_foreach_cloref *) //

この章で示したコード全体は オンライン から入手できます。 また、ハッシュテーブルを用いた symbol table の実装も オンライン から入手できます。