トラック
/
Factor
Factor
/
演習
/
灯台日誌
灯台日誌

灯台日誌

学習演習

はじめに

ハッシュセットは、それぞれの値を最大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つの構造を組み合わせたものになり、外側へとたどりながらその場で書き換えていきます。

説明

ケープ・クロージャー灯台の灯台守として働いています。ランタン室からは、通り過ぎていくすべての船を記録します。それぞれの船は固有のコールサインで識別されます。それに加えて、沿岸のあちこちの灯台と信号の中継を調整するのも仕事です。

1. 新しい航海日誌

empty-logを定義して、コールサインを集めるための、まっさらな空のハッシュセットを返すようにしましょう。

empty-log .
! => HS{ }

2. 目撃を記録する

sightを定義して、航海日誌とコールサインを受け取り、その目撃をその場で記録するようにしましょう。戻り値はありません。

empty-log dup "NS-1024" sight .
! => HS{ "NS-1024" }

3. これは見たことがある?

seen?を定義して、航海日誌とコールサインを受け取り、そのコールサインが記録済みならtを、そうでなければfを返すようにしましょう。

HS{ "NS-1024" "WB-203" } "NS-1024" seen? .   ! => t
HS{ "NS-1024" "WB-203" } "X-99"    seen? .   ! => f

4. 目撃を忘れる

forget-sightingを定義して、航海日誌とコールサインを受け取り、そのコールサインを日誌からその場で取り除くようにしましょう。戻り値はありません。そのコールサインがなければ、何もしません。

HS{ "NS-1024" "WB-203" } clone dup "WB-203" forget-sighting .
! => HS{ "NS-1024" }

5. 別々の船は何隻ある?

unique-countを定義して、日誌にある別々のコールサインの数を返すようにしましょう。

HS{ "NS-1024" "WB-203" "AC-77" } unique-count .   ! => 3
empty-log unique-count .                          ! => 0

6. 到達できる灯台

沿岸警備隊は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の組はそれだけで独立した連結成分なので、どちらも結果には現れません。

GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Factor Exercism

灯台日誌を始める準備はできましたか?

Exercismに登録すれば、47個のコンセプト163個の演習、そして本物の人間によるメンタリングとともに、Factorを学んでマスターできます。すべて無料です。