ハッシュセットは、それぞれの値を最大1回だけ保持する、変更可能で順序のないコレクションです。検索・挿入・削除はどれも平均O(1)です。hash-setsの中にあり、setsのプロトコルを実装しています。
HS{ "NS-1024" "WB-203" } .
! => HS{ "NS-1024" "WB-203" }
HS{ }は空のリテラルです。他のFactorのリテラルと同じく、これは共有されるオブジェクトであり、ソース中でHS{ }を参照する箇所はどれも同じセットを指します。HS{ } cloneを使うと、呼び出すたびに新しい独立したコピーが得られます。
>hash-set ( seq -- set ) ! build a fresh set from a sequence
値がすでにシーケンスに入っているなら、>hash-setを使えば一度に変換でき、重複は取り除かれます。
USING: hash-sets prettyprint ;
{ "NS-1024" "WB-203" "NS-1024" } >hash-set .
! => HS{ "NS-1024" "WB-203" }
adjoin ( elt set -- ) ! insert in place; no-op if already present
adjoin-all ( seq set -- ) ! insert every element of seq in place
delete ( elt set -- ) ! remove in place; no-op if absent
3つともセットをその場で変更し、スタックには何も返しません。
USING: hash-sets kernel sets ;
HS{ } clone
"NS-1024" over adjoin
"WB-203" over adjoin
"NS-1024" over adjoin ! duplicate — no effect
. ! => HS{ "NS-1024" "WB-203" }
adjoin-allはまとめて行う形です。シーケンスの各要素を追加し、adjoinが1つずつ行うのと同じように重複を飛ばします。
USING: hash-sets kernel sets ;
HS{ "NS-1024" } clone
{ "WB-203" "NS-1024" "QR-7" } over adjoin-all
. ! => HS{ "QR-7" "NS-1024" "WB-203" } (order not guaranteed)
in? ( elt set -- ? ) ! is elt in the set?
null? ( set -- ? ) ! is the set empty?
cardinality ( set -- n ) ! number of elements
members ( set -- seq ) ! enumerate as a sequence
in?は、setプロトコルが定める所属テストです。sequencesのmember?とは別物で、こちらはシーケンスを線形に走査します。ハッシュセットの場合、in?はハッシュの検索であり、平均O(1)です。これこそがハッシュセットを使う意味です。
null?は、セットが要素を持たないかどうかを報告します。cardinalityを0と比べるのではなく、「これは空か?」と直接尋ねる方法です。
: my-log ( -- set ) HS{ "NS-1024" "WB-203" } ;
"NS-1024" my-log in? . ! => t
"X-99" my-log in? . ! => f
my-log null? . ! => f
HS{ } clone null? . ! => t
my-log cardinality . ! => 2
union ( set1 set2 -- set )
intersect ( set1 set2 -- set )
diff ( set1 set2 -- set )
set-like ( set exemplar -- set' )
unionは「どちらかにあるすべての要素」、intersectは「両方にある要素」、diffは「set1にあってset2にない要素」です。どれも入力を変更せず、新しいセットを返します。
set-likeは、setをexemplarの型に変換します。union/intersect/diffの既定の実装はこれを使い、結果をレシーバーの型に合わせます。<my-set> <hash-set> unionはハッシュセットではなくmy-setを返すのです。
ハッシュセットは、グラフ探索でハッシュテーブルと自然に組み合わせられます。ハッシュテーブルは各ノードをその隣接ノードに対応づけ、ハッシュセットは、探索がループしたり同じ作業を繰り返したりしないよう、すでに訪問したノードを記録します。探索は、キューとこの2つの構造を組み合わせたものになり、外側へとたどりながらその場で書き換えていきます。
ケープ・クロージャー灯台の灯台守として働いています。ランタン室からは、通り過ぎていくすべての船を記録します。それぞれの船は固有のコールサインで識別されます。それに加えて、沿岸のあちこちの灯台と信号の中継を調整するのも仕事です。
empty-logを定義して、コールサインを集めるための、まっさらな空のハッシュセットを返すようにしましょう。
empty-log .
! => HS{ }
sightを定義して、航海日誌とコールサインを受け取り、その目撃をその場で記録するようにしましょう。戻り値はありません。
empty-log dup "NS-1024" sight .
! => HS{ "NS-1024" }
seen?を定義して、航海日誌とコールサインを受け取り、そのコールサインが記録済みならtを、そうでなければfを返すようにしましょう。
HS{ "NS-1024" "WB-203" } "NS-1024" seen? . ! => t
HS{ "NS-1024" "WB-203" } "X-99" seen? . ! => f
forget-sightingを定義して、航海日誌とコールサインを受け取り、そのコールサインを日誌からその場で取り除くようにしましょう。戻り値はありません。そのコールサインがなければ、何もしません。
HS{ "NS-1024" "WB-203" } clone dup "WB-203" forget-sighting .
! => HS{ "NS-1024" }
unique-countを定義して、日誌にある別々のコールサインの数を返すようにしましょう。
HS{ "NS-1024" "WB-203" "AC-77" } unique-count . ! => 3
empty-log unique-count . ! => 0
沿岸警備隊はrelay-mapを管理しています。これは灯台の名前をキーとするハッシュテーブルで、それぞれの値は、その灯台が直接中継できる灯台の配列です。
reachableを定義して、startの灯台とrelay-mapを受け取り、startから中継を繰り返して到達できるすべての灯台(start自身も含む)のハッシュセットを返すようにしましょう。
H{
{ "Crozier" { "Beacon" "Hadley" } }
{ "Beacon" { "Crozier" "Spiral" } }
{ "Hadley" { "Crozier" } }
{ "Spiral" { "Beacon" "Outpost" } }
{ "Outpost" { "Spiral" } }
{ "Far-Isle" { "Lonely" } }
{ "Lonely" { "Far-Isle" } }
}
"Crozier" swap reachable .
! => HS{ "Crozier" "Beacon" "Hadley" "Spiral" "Outpost" }
Far-IsleとLonelyの組はそれだけで独立した連結成分なので、どちらも結果には現れません。