Hash-Sets sind veränderbare, ungeordnete Sammlungen, die jeden Wert höchstens einmal speichern. Nachschlagen, Einfügen und Löschen sind im Durchschnitt alle O(1). Sie sind im Vokabular hash-sets definiert und implementieren das sets-Protokoll.
HS{ "NS-1024" "WB-203" } .
! => HS{ "NS-1024" "WB-203" }
HS{ } ist das leere Literal. Wie andere Factor-Literale ist es ein geteiltes Objekt: Jede Referenz auf HS{ } im Quelltext zeigt auf dasselbe Set. HS{ } clone gibt dir bei jedem Aufruf eine frische, unabhängige Kopie.
>hash-set ( seq -- set ) ! build a fresh set from a sequence
Wenn du die Werte bereits in einer Sequenz hast, wandelt >hash-set sie in einem Schritt um und lässt dabei Duplikate weg.
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
Alle drei verändern das Set an Ort und Stelle; keine von ihnen legt einen Wert auf den 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 ist die Variante für mehrere Elemente auf einmal: Sie fügt jedes Element einer Sequenz hinzu und überspringt Duplikate, genau wie adjoin es einzeln tut.
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? ist der Zugehörigkeitstest des Set-Protokolls. Es unterscheidet sich von member? (aus sequences), das eine lineare Suche über eine Sequenz durchführt. Bei einem Hash-Set ist in? ein Hash-Lookup mit durchschnittlich O(1), und genau das ist der Sinn eines Hash-Sets.
null? gibt an, ob das Set keine Elemente hat. Das ist der direkte Weg, um zu fragen, ob es leer ist, statt cardinality mit null zu vergleichen.
: 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 bedeutet „alle Elemente aus beiden"; intersect bedeutet „Elemente, die in beiden vorkommen"; diff bedeutet „in set1, aber nicht in set2". Jede von ihnen gibt ein neues Set zurück, ohne ihre Eingaben zu verändern.
set-like wandelt set in den Typ von exemplar um. Die Standardimplementierungen von union/intersect/diff nutzen es, damit das Ergebnis zum Typ des Empfängers passt: <my-set> <hash-set> union gibt ein my-set zurück, kein Hash-Set.
Hash-Sets passen natürlich zu Hashtabellen, wenn es um Graphdurchläufe geht: Eine Hashtabelle ordnet jedem Knoten seine Nachbarn zu, und ein Hash-Set merkt sich, welche Knoten bereits besucht wurden, damit die Suche nicht in Schleifen gerät oder Arbeit wiederholt. Der Durchlauf ist dann eine Warteschlange plus die beiden Strukturen, die verändert werden, während du dich nach außen vorarbeitest.
Du bist der Wärter am Leuchtturm von Cape Crozier. Von der Laternenkammer aus protokollierst du jedes Schiff, das vorbeikommt und durch ein eindeutiges Rufzeichen identifiziert wird. Außerdem koordinierst du die Signalweiterleitung mit den anderen Leuchttürmen entlang der Küste.
Definiere empty-log so, dass die Funktion ein frisches, leeres Hash-Set zurückgibt, bereit, Rufzeichen aufzunehmen.
empty-log .
! => HS{ }
Definiere sight, um ein Logbuch und ein Rufzeichen zu übernehmen und die Sichtung direkt im Logbuch festzuhalten. Gibt nichts zurück.
empty-log dup "NS-1024" sight .
! => HS{ "NS-1024" }
Definiere seen?, um ein Logbuch und ein Rufzeichen zu übernehmen und t zurückzugeben, wenn das Rufzeichen aufgezeichnet wurde, andernfalls f.
HS{ "NS-1024" "WB-203" } "NS-1024" seen? . ! => t
HS{ "NS-1024" "WB-203" } "X-99" seen? . ! => f
Definiere forget-sighting, um ein Logbuch und ein Rufzeichen zu übernehmen und das Rufzeichen direkt aus dem Logbuch zu entfernen. Gibt nichts zurück. Wenn das Rufzeichen nicht vorhanden ist, unternimm nichts.
HS{ "NS-1024" "WB-203" } clone dup "WB-203" forget-sighting .
! => HS{ "NS-1024" }
Definiere unique-count, um die Anzahl der verschiedenen Rufzeichen im Logbuch zurückzugeben.
HS{ "NS-1024" "WB-203" "AC-77" } unique-count . ! => 3
empty-log unique-count . ! => 0
Die Küstenwache führt eine relay-map: eine Hashtabelle, deren Schlüssel Leuchtturmnamen sind und deren Werte jeweils ein Array der Leuchttürme enthalten, an die der betreffende Leuchtturm direkt weiterleiten kann.
Definiere reachable, um einen Leuchtturm start und eine relay-map zu übernehmen und ein Hash-Set aller Leuchttürme zurückzugeben, die von start aus durch wiederholte Weiterleitung erreichbar sind (start selbst eingeschlossen).
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" }
Das Paar Far-Isle/Lonely bildet eine eigene Zusammenhangskomponente, deshalb taucht keiner von beiden im Ergebnis auf.
Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.