Ich habe mal endlich Zeit gefunden meine Map um einen Iterator zu erweitern.
Man kann beliebig viele Iteratoren erzeugen und unabhängig voneinander auf sie zugreifen. Das funktioniert auch mit mehreren Threads, solange man die Map in der Zeit nicht ändert. Ansonsten ist hier noch gar nichts Threadsicher. Hat man einen Iterator erzeugt und ändert die Map, kann es passieren, dass es zu einem Crash kommt.
Außerdem gibt es neue Methoden, die alle Keys oder Values auf einmal in ein Array oder eine LinkedList schreiben.
Code: Alles auswählen
EnableExplicit
DeclareModule MapI2I
EnableExplicit
Interface MapI2I
free.i()
set.i(key.i, value.i)
unset.i(key.i)
get.i(key.i)
is.i(key.i)
newIterator.i()
keysToArray(Array keys.i(1))
valuesToArray(Array keys.i(1))
keysToLinkedList(List keys.i())
valuesToLinkedList(List keys.i())
bucketsUsed.i()
elements.i()
memoryUsed.i()
EndInterface
Interface Iterator
free.i()
isNext.i()
key.i()
value.i()
bucket.i()
bucketIndex.i()
nextElement.i()
EndInterface
Declare newMapI2I(buckets.i)
EndDeclareModule
Module MapI2I
EnableExplicit
Structure entry
key.i
value.i
*next.entry ; Wird zu beginn auf 1 gesetzt um zu signalisieren, dass das Element noch nicht existiert.
EndStructure
Structure entries
entry.entry[0]
EndStructure
Structure table
*vTable
buckets.i
bucketsUsed.i
elements.i
*entries.entries
EndStructure
Structure it
*vTable
*table.table
*entry.entry
bucket.i
iBucket.i
EndStructure
; Siehe https://stackoverflow.com/a/12996028/4239139
CompilerIf SizeOf(Integer) = 4
Macro hash(x, y)
x = ((x >> 16) ! x) * $45d9f3b
x = ((x >> 16) ! x) * $45d9f3b
x = (x >> 16) ! x
x % y
EndMacro
CompilerElse
Macro hash(x, y)
x = (x ! (x >> 30)) * $bf58476d1ce4e5b9
x = (x ! (x >> 27)) * $94d049bb133111eb
x = x ! (x >> 31);
x % y
EndMacro
CompilerEndIf
Procedure newMapI2I(buckets.i)
Protected *this.table, i.i
If buckets < 1
ProcedureReturn #False
EndIf
*this = AllocateStructure(table)
If Not *this
ProcedureReturn #False
EndIf
With *this
\vTable = ?vTable_mapI2I
\buckets = buckets.i
\bucketsUsed = 0
\elements = 0
\entries = AllocateMemory(SizeOf(entry) * buckets, #PB_Memory_NoClear)
If Not \entries
FreeStructure(*this)
ProcedureReturn #False
EndIf
For i = 0 To buckets - 1
\entries\entry[i]\next = 1
Next
EndWith
ProcedureReturn *this
EndProcedure
Procedure.i free(*this.table)
Protected i.i, *entry.entry, *nextEntry.entry
With *this
For i = 0 To *this\buckets - 1
If \entries\entry[i]\next <> 1
*entry = \entries\entry[i]\next
While *entry
*nextEntry = *entry\next
FreeMemory(*entry)
*entry = *nextEntry
Wend
EndIf
Next
FreeMemory(\entries)
FreeStructure(*this)
EndWith
EndProcedure
Procedure.i is(*this.table, key.i)
Protected *entry.entry, bucket.i = key
hash(bucket, *this\buckets)
*entry = @*this\entries\entry[bucket]
If *entry\next = 1
ProcedureReturn #False
EndIf
While *entry
If *entry\key = key
ProcedureReturn #True
EndIf
*entry = *entry\next
Wend
ProcedureReturn #False
EndProcedure
Procedure.i get(*this.table, key.i)
Protected *entry.entry, bucket.i = key
hash(bucket, *this\buckets)
*entry = @*this\entries\entry[bucket]
If *entry\next = 1
ProcedureReturn #False
EndIf
While *entry
If *entry\key = key
ProcedureReturn *entry\value
EndIf
*entry = *entry\next
Wend
ProcedureReturn #False
EndProcedure
Procedure.i set(*this.table, key.i, value.i)
Protected *entry.entry, bucket.i = key, *lastEntry.entry
hash(bucket, *this\buckets)
*entry = @*this\entries\entry[bucket]
*lastEntry = *entry
If *entry\next = 1
*this\bucketsUsed + 1
Else
Repeat
If *entry\key = key
*entry\value = value
ProcedureReturn *entry
EndIf
*lastEntry = *entry
*entry = *entry\next
Until Not *entry
EndIf
If *lastEntry\next = 1
*lastEntry\key = key
*lastEntry\value = value
*lastEntry\next = 0
*this\elements + 1
ProcedureReturn *lastEntry
Else
*lastEntry\next = AllocateMemory(SizeOf(entry), #PB_Memory_NoClear)
If Not *lastEntry\next
ProcedureReturn #False
EndIf
*lastEntry\next\key = key
*lastEntry\next\value = value
*lastEntry\next\next = 0
*this\elements + 1
ProcedureReturn *lastEntry\next
EndIf
EndProcedure
Procedure.i unset(*this.table, key.i)
Protected *entry.entry, bucket.i = key, *firstEntry.entry, *previousEntry.entry
hash(bucket, *this\buckets)
*entry = @*this\entries\entry[bucket]
*firstEntry = *entry
*previousEntry = *entry
If *entry\next = 1
ProcedureReturn #False
EndIf
While *entry
If *entry\key = key
If *entry = *firstEntry
If *entry\next = 0
; Der einzige Eintrag der LinkedList soll gelöscht werden
*this\bucketsUsed - 1
*entry\next = 1
Else
; Der erste Eintrag der LinkedList soll gelöscht werden und es folgt noch mindestens ein weiterer
*entry\key = *entry\next\key
*entry\value = *entry\next\value
*entry\next = *entry\next\next
EndIf
*this\elements - 1
ProcedureReturn #True
Else
; Der zweite oder einer der nachfolgenden Einträge soll gelöscht werden.
*previousEntry\next = *entry\next
FreeMemory(*entry)
*this\elements - 1
ProcedureReturn #True
EndIf
EndIf
*previousEntry = *entry
*entry = *entry\next
Wend
ProcedureReturn #False
EndProcedure
Procedure.i newIterator(*this.table)
Protected *it.it = AllocateStructure(it)
If Not *it
ProcedureReturn #False
EndIf
With *it
\vTable = ?vTable_it
\table = *this
\bucket = -1
\iBucket = 0
EndWith
ProcedureReturn *it
EndProcedure
Procedure.i itFree(*this.it)
FreeStructure(*this)
ProcedureReturn #True
EndProcedure
Procedure.i itIsNext(*this.it)
Protected curIBucket.i
With *this
; Sind wir schon über das Ende?
If \iBucket = \table\buckets
ProcedureReturn #False
EndIf
; Wenn wir noch gar nicht angefangen haben mit dem Iterieren, es aber Elemente in der Map gibt, dann gibt es natürlich ein nächstes Element.
If Not \entry And \table\elements
ProcedureReturn #True
EndIf
; Ab hier gehen wir davon aus, dass das aktuelle Element existiert
; Existiert ein weiteres Element im gleichen Bucket?
If \entry\next
ProcedureReturn #True
EndIf
; Ansonsten gehe zum nächsten nicht leeren Bucket
curIBucket = \iBucket + 1
While curIBucket < \table\buckets And \table\entries\entry[curIBucket]\next = 1
curIBucket + 1
Wend
; Wenn das Ende erreicht wurde, gibt es keinen weiteren.
If curIBucket = \table\buckets
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndWith
EndProcedure
Procedure.i itNextElement(*this.it)
With *this
If \iBucket = \table\buckets
ProcedureReturn #False
EndIf
; Wenn wir noch gar nicht angefangen haben mit dem Iterieren, es aber Elemente in der Map gibt, dann gibt es natürlich ein nächstes Element.
If Not \entry And \table\elements
\iBucket = 0
\bucket = 0
While \iBucket < \table\buckets And \table\entries\entry[\iBucket]\next = 1
\iBucket + 1
Wend
\entry = \table\entries\entry[\iBucket]
ProcedureReturn Bool(\iBucket < \table\buckets)
EndIf
; Ab hier gehen wir davon aus, dass das aktuelle Element existiert
; Existiert ein weiteres Element im gleichen Bucket?
If \entry\next
\entry = \entry\next
\bucket + 1
ProcedureReturn #True
EndIf
\bucket = 0
\iBucket + 1
; Ansonsten gehe zum nächsten nicht leeren Bucket
While \iBucket < \table\buckets And \table\entries\entry[\iBucket]\next = 1
\iBucket + 1
Wend
; Wenn das Ende erreicht wurde, gibt es keinen weiteren.
If \iBucket = \table\buckets
ProcedureReturn #False
EndIf
\entry = \table\entries\entry[\iBucket]
ProcedureReturn #True
EndWith
EndProcedure
Procedure.i itKey(*this.it)
If *this\entry
ProcedureReturn *this\entry\key
EndIf
ProcedureReturn #False
EndProcedure
Procedure.i itValue(*this.it)
If *this\entry
ProcedureReturn *this\entry\value
EndIf
ProcedureReturn #False
EndProcedure
Procedure.i itBucket(*this.it)
ProcedureReturn *this\bucket
EndProcedure
Procedure.i itBucketIndex(*this.it)
ProcedureReturn *this\iBucket
EndProcedure
Procedure.i bucketsUsed(*this.table)
ProcedureReturn *this\bucketsUsed
EndProcedure
Procedure.i elements(*this.table)
ProcedureReturn *this\elements
EndProcedure
Procedure.i memoryUsed(*this.table)
ProcedureReturn SizeOf(table) + SizeOf(entry) * *this\buckets + Bool(*this\elements > *this\bucketsUsed) * (*this\elements - *this\bucketsUsed) * SizeOf(entry)
EndProcedure
Macro _toArray(name, member)
Procedure.i name#ToArray(*this.table, Array arr.i(1))
Protected i.i, *entry.entry, j.i
ReDim arr(*this\elements - 1)
For i = 0 To *this\buckets - 1
*entry = @*this\entries\entry[i]
If *entry\next <> 1
While *entry
arr(j) = *entry\member
j + 1
*entry = *entry\next
Wend
EndIf
Next
EndProcedure
EndMacro
_toArray(keys, key)
_toArray(values, value)
Macro _toLinkedList(name, member)
Procedure.i name#ToLinkedList(*this.table, List ll.i())
Protected i.i, *entry.entry
ClearList(ll())
For i = 0 To *this\buckets - 1
*entry = @*this\entries\entry[i]
If *entry\next <> 1
While *entry
If AddElement(ll())
ll() = *entry\member
EndIf
*entry = *entry\next
Wend
EndIf
Next
EndProcedure
EndMacro
_toLinkedList(keys, key)
_toLinkedList(values, value)
DataSection
vTable_mapI2I:
Data.i @free(), @set(), @unset(), @get(), @is(), @newIterator(), @keysToArray(),
@valuesToArray(), @keysToLinkedList(), @valuesToLinkedList(),
@bucketsUsed(), @elements(), @memoryUsed()
vTable_it:
Data.i @itFree(), @itIsNext(), @itKey(), @itValue(), @itBucket(), @itBucketIndex(), @itNextElement()
EndDataSection
EndModule
Procedure.s formatBytes(bytes.q)
Static suffix.s = "B,KiB,MiB,GiB,TiB,PiT"
Protected index.i = 1, t.d = bytes
While t > 1023
index + 1
t / 1024
Wend
ProcedureReturn StrD(t, 3 * index) + " " + StringField(suffix, index, ",")
EndProcedure
OpenConsole()
Define buckets.i = 10
Define myMap.MapI2I::MapI2I = MapI2I::newMapI2I(buckets)
Define i.i, max = 20, key.i
; Erstelle ganz viele Element
RandomSeed(123)
For i = 1 To max
key.i = Random(1 << 16) + i << 16
PrintN("Set key: " + key + " value: " + i)
If Not myMap\set(key, i)
PrintN("Oh oh. Konnte Element mit key " + key + " nicht anlegen.")
EndIf
Next
PrintN("Momentan genutzte Buckets: " + myMap\bucketsUsed() + " (" + StrD(100.0 * myMap\bucketsUsed() / buckets, 2) + ~"%)\n" +
"Durchschnittliche Elemente pro Bucket: " + StrD(myMap\elements() / myMap\bucketsUsed(), 2) + ~"%\n" +
"Speicherverbrauch: " + formatBytes(myMap\memoryUsed()))
; Überprüfe ihren Inhalt
RandomSeed(123)
For i = 1 To max
key.i = Random(1 << 16) + i << 16
If myMap\get(key) <> i
PrintN("i=" + key + " sollte den Wert " + i + " haben, hat aber " + myMap\get(key))
EndIf
Next
; Lösche die Hälfte
RandomSeed(123)
For i = 1 To max
key.i = Random(1 << 16) + i << 16
If i & 1
If Not myMap\unset(key)
PrintN("Nanu? Wieso existiert der Eintrag " + key + " nicht?")
EndIf
EndIf
Next
PrintN("Momentan genutzte Buckets: " + myMap\bucketsUsed() + " (" + StrD(100.0 * myMap\bucketsUsed() / buckets, 2) + ~"%)\n" +
"Durchschnittliche Elemente pro Bucket: " + StrD(myMap\elements() / myMap\bucketsUsed(), 2) + ~"\n" +
"Speicherverbrauch: " + formatBytes(myMap\memoryUsed()))
; Überprüfe Löschung und die Existenz der restlichen
RandomSeed(123)
For i = 1 To max
key.i = Random(1 << 16) + i << 16
If myMap\is(key) And i & 1
PrintN("oh oh. Key " + key + " sollte nicht mehr existieren.")
ElseIf Not myMap\is(key) And i & 1 = 0
PrintN("oh oh. Key " + key + " sollte noch existieren.")
EndIf
Next
; Alle Schlüssel in einem Array
NewList values()
myMap\valuesToLinkedList(values())
PrintN("Alle Werte in einer LinkedList: (" + ListSize(values()) + ")")
ForEach values()
PrintN(Str(values()))
Next
PrintN("Iterator über alle Werte:")
Define.MapI2I::Iterator it = myMap\newIterator()
While it\NextElement()
PrintN("Key: " + RSet(Str(it\key()), 10) + " Value: " + RSet(Str(it\value()), 10) + " iBucket: " + RSet(Str(it\bucketIndex()), 10) + " Bucket: " + Str(it\bucket()))
Wend
it\free()
myMap\free()
PrintN("Ende Gelände.")
Input()
CloseConsole()