Assoziative Arrays: (Vorläufig abgeschlossen!)
Unter dem Begriff
Assoziatives Datenfeld liefert die deutsche Wikipedia folgende Erläuterung (Auszug):
Das assoziative Datenfeld (englisch map, dictionary oder associative array) ist eine Datenstruktur, die – anders als ein gewöhnliches Feld (engl. array) – nichtnumerische (oder nicht fortlaufende) Schlüssel (zumeist Zeichenketten) verwendet, um die enthaltenen Elemente zu adressieren; ...
und die
AHK-Hilfe sagt dazu:
Ein assoziatives Array ist ein Objekt, das mehrere eindeutige Keys (Schlüssel) und mehrere Values (Werte) enthält, die jeweils miteinander verbunden sind. Keys können Strings, Integer oder Objekte sein. Values beliebige Typen.
Wer den Anfang des Tutorials bereits gelesen hat, kann sich vielleicht vorstellen, was das in Bezug auf AHK-Arrays heißt:
- Für den Zugriff auf die Elemente wird nicht eine absolute Positionsangabe (Index) sondern ein Schlüsselbegriff (Schlüssel / key) verwendet, der allerdings auch numerisch sein darf.
- Das Array ist in der Regel nicht lückenlos (im Sinne einer linearen Folge von Indices) bzw. kann es bei nichtnumerischen Schlüsseln gar nicht sein.
- Auch Arrays mit auschließlich numerischen Schlüsseln beginnen oft nicht mit dem Index 1.
- Letzlich und entscheidend: Das Array ist viel mehr ein Objekt als ein Array im klassischen Sinne.
Für die Arbeit mit assoziativen Arrays wird deshalb oft und gern auf die Objektsyntax mit den geschweiften Klammern
{...} zurückgegriffen, es geht aber notfalls auch mit der Arraysyntax
[...], nur oft nicht ganz so bequem.
Die Vermischung mit den Objekten hat aber auch andere weitreichende Folgen.
So liefert
MeinArray.Length() in diesem Fall nicht die Anzahl der Elemente, sondern nur den Index des größten numerischen Schlüssels zurück. Ist gar kein numerischer Schlüssel vorhanden, wird
0 zurückgegeben, ebenso wenn der größte numerische Schlüssel tatsächlich 0 ist. Ist er negativ, wird sogar ein negatives Ergebnis geliefert. Deshalb ist diese Methode für assoziative Arrays nicht bzw. kaum zu gebrauchen. Das liegt u.a. daran, dass AHK die numerischen (Index) und die nichtnumerischen Schlüssel (Key) bzw. deren Inhalte strikt getrennt verwaltet.
Wenn das Array auch numerische Schlüssel enthält, die nicht lückenlos sind, muss man auch mit dem Gebrauch der Methoden
MeinArray.RemoveAt(Index) und
MeinArray.InsertAt(Index) äußerst vorsichtig sein, weil beide den Wert anderer numerischer Schlüssel verändern können. Will man das nicht, muss man die Finger davon lassen. Eine Ersatzmethode zum Einfügen gibt es nicht, weil das gezielte Einfügen eines Schlüssels an einer bestimmten Position nicht möglich ist. Die 'Position' wird allein durch den Wert des Schlüssels bestimmt. Zum Löschen von Schlüsseln gibt es aber die Methode
MeinArray.Delete("Schlüssel"), die auch numerische Schlüssel entfernt, ohne andere anzutasten.
Für numerische oder gemischte assoziative Arrays sind auch die Methoden
MeinArray.MinIndex() bzw. MeinArray.MaxIndex() brauchbar, die den kleinsten bzw. größten numerischen Schlüssel liefern. Außerdem gibt es die nützliche Methode
MeinArray.HasKey("Schlüssel"), mit der man prüfen kann, ob ein bestimmter Schlüssel bereits in das Array aufgenommen wurde.
So, und wozu ist das nun gut? Ich will mal versuchen, das am Beipiel der AHK
HTML Farbnamen zu verdeutlichen. Diese Tabelle enthält sowohl die Namen einiger Farben, wie sie in einigen Gui-Anweisungen genutzt werden können, als auch ihre hexadezimalen numerischen Werte. Wenn man nun eine Funktion erstellt, die mit Farben arbeitet, ohne dass die Namen der Farben verwendet werden können, und die dann im Forum anderen Usern zur Verfügung stellt, kann man auf die Frage warten: Kann ich da auch HTML-Farbnamen übergeben? Ich habe das in der Vergangenheit so gelöst, dass ich innerhalb der Funktion ein assoziatives Array vorhalte, das die Namen mit den numerischen Werten verbindet. Das kann im einfachsten Fall unter Verwendung der Arraysyntax so aufgebaut werden:
Code: Select all
HTML := []
HTML["AQUA"] := 0x00FFFF
HTML["BLACK"] := 0x000000
HTML["BLUE"] := 0x0000FF
HTML["FUCHSIA"] := 0xFF00FF
HTML["GRAY"] := 0x808080
HTML["GREEN"] := 0x008000
HTML["LIME"] := 0x00FF00
HTML["MAROON"] := 0x800000
HTML["NAVY"] := 0x000080
HTML["OLIVE"] := 0x808000
HTML["PURPLE"] := 0x800080
HTML["RED"] := 0xFF0000
HTML["SILVER"] := 0xC0C0C0
HTML["TEAL"] := 0x008080
HTML["WHITE"] := 0xFFFFFF
HTML["YELLOW"] := 0xFFFF00
Durch die Verwendung der Objektsyntax
{...} kann man das drastisch abkürzen:
Code: Select all
HTML := {AQUA: 0x00FFFF, BLACK: 0x000000, BLUE: 0x0000FF, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
, LIME: 0x00FF00, MAROON: 0x800000, NAVY: 0x000080, OLIVE: 0x808000, PURPLE: 0x800080, RED: 0xFF0000
, SILVER: 0xC0C0C0, TEAL: 0x008080, WHITE: 0xFFFFFF, YELLOW: 0xFFFF00}
Der Teil vor dem Doppelpunkt ist der Schlüssel, danach folgt der Wert. Die Schlüssel/Wert Paare werden durch ein Komma getrennt. Wenn der Schlüssel nur aus Buchstaben, Ziffern und dem Unterstrich (
_) besteht, braucht er nicht in Anführungszeichen eingeschlossen zu werden. Und weil das für AHK nur eine Zeile ist, kann man so auch
statische Variablen in Funktionen initialisieren.
Nun nutzen wir das für eine (zugegebenermaßen recht nutzlose) Funktion. Der wird ein HTML-Farbname übergeben und sie liefert dafür den RGB-Integerwert:
Code: Select all
#NoEnv
Farbe := "Maroon"
RGB := NameToRGB(Farbe)
HexRGB := Format("0x{:06X}", RGB)
MsgBox, 0, Array Test, Der RGB Integerwert von`n`n`t%Farbe%`n`nist %RGB% bzw. %HexRGB%
Farbe := "Bingo"
RGB := NameToRGB(Farbe)
HexRGB := Format("0x{:06X}", RGB)
MsgBox, 0, Array Test, Der RGB Integerwert von`n`n`t%Farbe%`n`nist %RGB% bzw. %HexRGB%
ExitApp
NameToRGB(Name) {
Static HTML := {AQUA: 0x00FFFF, BLACK: 0x000000, BLUE: 0x0000FF, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
, LIME: 0x00FF00, MAROON: 0x800000, NAVY: 0x000080, OLIVE: 0x808000, PURPLE: 0x800080, RED: 0xFF0000
, SILVER: 0xC0C0C0, TEAL: 0x008080, WHITE: 0xFFFFFF, YELLOW: 0xFFFF00}
If HTML.HasKey(Name) ; ist der übergebene Wert ein gültiger Name / im Array enthalten?
Return HTML[Name] ; ja, RGB-Wert zurückgeben
Return "'Das war mal nix!'"
}
Das Ganze funktioniert aber auch umgekehrt. Dafür nehmen wir den (numerischen) Farbwert als Schlüssel und verbinden ihn mit dem Namen. Die Definition in Objektsyntax sähe dann so aus:
Code: Select all
HTML := {0x00FFFF: "AQUA", 0x000000: "BLACK", 0x0000FF: "BLUE", 0xFF00FF: "FUCHSIA", 0x808080: "GRAY", 0x008000: "GREEN"
, 0x00FF00: "LIME", 0x800000: "MAROON", 0x000080: "NAVY", 0x808000: "OLIVE", 0x800080: "PURPLE", 0xFF0000: "RED"
, 0xC0C0C0: "SILVER", 0x008080: "TEAL", 0xFFFFFF: "WHITE", 0xFFFF00: "YELLOW"}
Weil die Schlüssel numerisch sind, brauchen sie auch hier keine umschließenden Anführungszeichen, dafür muss aber der Namenstext in Anführungszeichen eingeschlossen werden, weil AHK sonst annimmt, es sei der Name einer Variablen deren Inhalt verwendet werden soll. In der Arraysyntax sähe das so aus:
Code: Select all
HTML := []
HTML[0x00FFFF] := "AQUA"
...
Wie man unschwer erkennen kann, haben wir hier ein lückenhaftes Array mit numerischen Schlüsseln, also ein assoziatives Array. Die numerischen Indices werden von AHK immer als Dezimalzahlen behandelt. Die Methode
HTML.MinIndex() liefert deshalb
0 (= 0x000000). Die Methode
HTML.MaxIndex() den Wert
16777215 (=0xFFFFFF) und ebenso die Methode
HTML.Length(). Man sollte deshalb nicht versuchen, das Ergebnis von
HTML.Length() bei assoziativen Arrays als 'Anzahl der Elemente' in einer Schleifenanweisung wie
Loop, % HTML.Length() zu nutzen.
Im folgenden Beispiel gibt die
MsgBox den Farbnamen für einen RGB-Wert zurück, so vorhanden:
Code: Select all
#NoEnv
HTML := {0x00FFFF: "AQUA", 0x000000: "BLACK", 0x0000FF: "BLUE", 0xFF00FF: "FUCHSIA", 0x808080: "GRAY", 0x008000: "GREEN"
, 0x00FF00: "LIME", 0x800000: "MAROON", 0x000080: "NAVY", 0x808000: "OLIVE", 0x800080: "PURPLE", 0xFF0000: "RED"
, 0xC0C0C0: "SILVER", 0x008080: "TEAL", 0xFFFFFF: "WHITE", 0xFFFF00: "YELLOW"}
FarbWert := 0xC0C0C0
If HTML.HasKey(FarbWert)
FarbName := HTML[FarbWert]
Else
FarbName := "Ham wa nich!"
MsgBox, 0, RGB %FarbWert%, Name: %FarbName%
Alternativ kann man den Namen für einen RGB-Wert auch über das zuerst erstellte Array ermitteln. Dazu muss man allerdings in einer Schleife über das Array laufen, bis man den Namen gefunden hat:
Code: Select all
#NoEnv
HTML := {AQUA: 0x00FFFF, BLACK: 0x000000, BLUE: 0x0000FF, FUCHSIA: 0xFF00FF, GRAY: 0x808080, GREEN: 0x008000
, LIME: 0x00FF00, MAROON: 0x800000, NAVY: 0x000080, OLIVE: 0x808000, PURPLE: 0x800080, RED: 0xFF0000
, SILVER: 0xC0C0C0, TEAL: 0x008080, WHITE: 0xFFFFFF, YELLOW: 0xFFFF00}
FarbWert := 0xC0C0C0
FarbName := "Ham wa nich!"
For Name, RGB In HTML {
If (RGB = FarbWert) {
FarbName := Name
Break ; wenn der Name / die Farbe gefunden wurde, wird die Schleife beendet
}
}
MsgBox, 0, RGB %FarbWert%, Name: %FarbName%