Hash-sets are mutable, unordered collections that store each
value at most once. Lookup, insert, and delete are all O(1)
average. They live in hash-sets and implement the
sets protocol.
HS{ "NS-1024" "WB-203" } .
! => HS{ "NS-1024" "WB-203" }
HS{ } is the empty literal. Like other Factor literals it's a
shared object — every reference to HS{ } in source points to
the same set. HS{ } clone gives you a fresh independent copy
each call.
>hash-set ( seq -- set ) ! build a fresh set from a sequence
When you already have the values in a sequence, >hash-set
converts them in one step, dropping any duplicates.
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
All three mutate the set; none returns anything on the stack.
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 is the bulk form: it adjoins each element of a
sequence, skipping duplicates just like adjoin does one at a time.
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? is the set-protocol membership test. It's distinct from
member? (from sequences), which does a linear scan over a
sequence. For a hash-set, in? is a hash lookup — O(1) average,
the whole point of using a hash-set.
null? reports whether the set has no elements — the direct way
to ask "is this empty?" rather than comparing cardinality to
zero.
: 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 is "all elements from either"; intersect is "elements
in both"; diff is "in set1 but not set2". Each returns a
new set without mutating its inputs.
set-like coerces set to the type of exemplar. The default
implementations of union/intersect/diff use it to make the
result match the type of the receiver — <my-set> <hash-set> union returns a my-set, not a hash-set.
Hash-sets pair naturally with hashtables for graph traversal: a hashtable maps each node to its neighbours, and a hash-set records which nodes have already been visited so the search doesn't loop or repeat work. The traversal is then a queue plus the two structures, mutated in place as you sweep outwards.
You are the keeper at Cape Crozier lighthouse. From the lantern room you log every vessel that passes by — each is identified by a unique callsign — and you also coordinate signal relays with the other lighthouses up and down the coast.
Define empty-log to return a fresh empty hash-set, ready to
collect callsigns.
empty-log .
! => HS{ }
Define sight to take a logbook and a callsign, and record the
sighting in place. Returns nothing.
empty-log dup "NS-1024" sight .
! => HS{ "NS-1024" }
Define seen? to take a logbook and a callsign, returning t
if the callsign has been recorded and f otherwise.
HS{ "NS-1024" "WB-203" } "NS-1024" seen? . ! => t
HS{ "NS-1024" "WB-203" } "X-99" seen? . ! => f
Define forget-sighting to take a logbook and a callsign and
remove the callsign from the log in place. Returns nothing. If
the callsign isn't there, do nothing.
HS{ "NS-1024" "WB-203" } clone dup "WB-203" forget-sighting .
! => HS{ "NS-1024" }
Define unique-count to return the number of distinct
callsigns in the log.
HS{ "NS-1024" "WB-203" "AC-77" } unique-count . ! => 3
empty-log unique-count . ! => 0
The coast guard maintains a relay-map: a hashtable keyed by
lighthouse name, with each value being an array of the
lighthouses that the keyed one can directly relay to.
Define reachable to take a start lighthouse and a
relay-map, and return a hash-set of every lighthouse reachable
from start (including start itself) by repeated relays.
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" }
The Far-Isle/Lonely pair is its own connected component, so
neither appears in the result.
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.