Tie::Hash::{Array,Abbrev{,::Smart}} und andere Tie::Hash::*-Module

Martin H. Sluka <martin@sluka.de>

2003-01-31

  1. Inhalt dieses Beitrags
  2. Perl bietet die Möglichkeit, das Verhalten seiner vordefinierten Datentypen zu modifizieren, indem man eine Klasse implementiert, in der die verschiedenen Funktionen des jeweiligen Datentyps als Methoden definiert sind, und an die dann einzelne Variablen des jeweiligen Typs mittels tie() gebunden werden können, vgl. auch die man-Page perltie(1).

    Im CPAN sind bereits etliche solcher Klassen zu finden, die viele Anwendungsszenarien abdecken sollten. Dieser Beitrag soll einen kurzen Überblick über die derzeit verfügbaren Klassen speziell für assoziative Arrays (Hashes) geben, mit Schwerpunkt auf drei neue, von mir selbst entwickelte einschlägige Module.

  3. Tie::Hash::{Array,Abbrev{,::Smart}}
    1. Allgemeines

      Diese drei von mir entwickelten Module bauen aufeinander auf, d. h. Tie::Hash::Abbrev::Smart ist eine Sub-Klasse von Tie::Hash::Abbrev, das seinerseits eine Sub-Klase von Tie::Hash::Array darstellt. Ursprünglich wollte ich primär die Funktionalität des erstgenannten Moduls implementieren; es kristallisierte sich während der Entwicklung allerdings heraus, dass es sinnvoll sein könnte, die verschiedenen Teil-Funktionalitäten, aus denen sich das Verhalten von Tie::Hash::Array::Smart letztlich zusammensetzt, in eigene Module aufzuspalten, um sie so auch einzeln zugänglich zu machen, und sei es zur Entwicklung weiterer Sub-Klassen.

    2. Tie::Hash::Array

      Hashes, die die an dieses Modul gebunden werden, werden intern nicht als solche, sondern als nach ihren Schlüsseln (keys) asciibetisch (d. h. mittels cmp) sortierte Arrays gespeichert; die Suche nach einzelnen Elementen erfolgt somit als binäre Suche, so dass Hash mit n Einträgen in maximal log2n Schritten nach einem bestimmten Element durchsucht werden kann.

      Im Vergleich zu normalen Hashes bietet die Verwendung dieses Moduls folgende Vorteile:

      • each() und entsprechend auch keys() und values() liefern den Inhalt des Hashes in der oben beschriebenen deterministischen Reihenfolge.
      • Es können Objekte als Schlüssel verwendet werden, die dabei als solche erhalten bleiben (wohingegen sie bei normalen Hashes in Strings umgewandelt werden.)
      • Die oben erwähnte Sortierreihenfolge lässt sich beeinflussen, indem man den cmp-Operator für diese Objekte mittels Overloading entsprechend umdefiniert.

      Wesentlicher Nachteil gegenüber normalen Hashes ist, dass insbesondere das Einfügen neuer Elemente potenziell "teuer" ist, da es mittels splice() geschieht. In der Praxis erwies sich dieser Ansatz jedoch auch für relativ große Hashes (mit mehr als 105 Elementen) als ausreichend schnell und jedenfalls schneller als eine Implementation unter Verwendung von binären Bäumen oder ähnlich komplexen und von Perl nicht nativ unterstützen Datenstrukturen.

    3. Tie::Hash::Abbrev

      Aufbauend auf Tie::Hash::Array ermöglicht dieses Modul es, auf Elemente eines Hashes auch mit abgekürzten Schlüsselnamen zuzugreifen.

      Beispielsweise könnte bei einem Hash

      	use Tie::Hash::Abbrev;
      
      	tie my %hash, 'Tie::Hash::Abbrev';
      
      	%hash = ( sonntag   =>0, montag =>1, dienstag=>2, mittwoch =>3,
      	          donnerstag=>4, freitag=>5, samstag =>6,
      	          sunday    =>0, monday =>1, tuesday =>2, wednesday=>3,
      	          thursday  =>4, friday =>5, saturday=>6 );

      auf den Wert des Elements $hash{sonntag} auch als $hash{so} zugegriffen werden.

      Die verwendete Abkürzung muss allerdings bezogen auf alle Schlüssel des Hashes eindeutig sein; bei $hash{fr} wäre dies beispielsweise nicht der Fall, da sowohl das Element $hash{freitag} als auch $hash{friday} gemeint sein könnte. In solchen Fällen wird der Zugriff wie der auf ein nicht existierendes Element behandelt, d. h. eine Abfrage des Werts gibt undef, eine mittels exists() false zurück.

      Zu beachten ist, dass das Löschen eines Elements nur mittels des komplett ausgeschriebenen Schlüssels möglich ist; zum Löschen über abgekürzte Schlüsselwörter steht jedoch eine Zusatzmethode zur Verfügung:

      	my @deleted = tied(%hash)->delete_abbrev('foo','bar');

      Diese löscht alle im Sinne der jeweiligen Klasse eindeutig abgekürzten Elemente und gibt eine Liste der zugehörigen Werte zurück.

      Zwar ließe sich ein ähnliches Verhalten, wie diese Klasse es zur Verfügung stellt, auch unter Zuhilfenahme des Standard-Moduls Text::Abbrev erzielen, doch geht's mit diesem Modul ungleich einfacher, abgesehen davon müssen nicht alle potenziellen Abkürzungen bei jeder Änderung des Hashes im Voraus berechnet und im Speicher gehalten werden.

      Die bereits bei Tie::Hash::Array erläuterten Features bleiben freilich ebenfalls erhalten.

    4. Tie::Hash::Abbrev::Smart

      Auch die Grundidee dieser Klasse ist, Hash-Elemente mit abgekürzten Schlüsselnamen ansprechen zu können. Anders als bei Tie::Hash::Abbrev gilt eine Abkürzung dabei allerdings auch dann noch als eindeutig, wenn mehrere "passende" Elemente gefunden werden, solange diese Elemente alle den gleichen (String-)Wert haben (oder alle undef sind). Bezogen auf das obige Beispiel würde ein Zugriff auf $hash{fr} oder sogar $hash{f} also 5 ergeben, da sowohl $hash{freitag} als auch $hash{friday} diesen Wert haben.

      Disclaimer: Nein, besonders toll finde ich den Namen dieses Moduls auch nicht, aber leider hatte niemand eine bessere Idee geäußert.

  4. andere Tie::Hash::*-Module
  5. Disclaimer: Die folgenden Informationen basieren im Wesentlichen auf den Dokumentationen des jeweiligen Moduls; die Module wurden von mir nicht oder zumindest nicht ausgiebig getestet.

      Hash::Case
      ist eine Basisklasse für die folgenden Module.
      Hash::Case::Lower
      wandelt alle übergebenen Schlüsselnamen automatisch in Kleinbuchstaben um.
      Hash::Case::Preserve
      ermöglicht — wie auch Tie::CPHash — den Zugriff auf Hash-Elemente ohne Beachtung der Groß-/Kleinschreibung
      Hash::Case::Upper
      wandelt alle übergebenen Schlüsselnamen automatisch in Großbuchstaben um.
      Hash::WithDefaults
      verfolgt einen ähnlichen Ansatz wie Tie::Hash::Layered, Tie::Hash::Stack, Tie::HashDefaults etc., bietet allerdings zusätzlich diverse Optionen hinsichtlich der Beachtung und Erhaltung der Groß-/Kleinschreibung von Schlüsselnamen.
      Tie::Alias::Hash
      wird laut Autor von Tie::Alias::TIEHASH benötigt, das ich im CPAN jedoch nicht finden konnte, und ist ansonsten leider undokumentiert.
      Tie::AliasHash
      ermöglicht es, Werte ünter mehreren alternativen Schlüsselnamen abzulegen.
      Tie::AppendHash
      implementiert einen Hash, bei dem pro Schlüssel beliebig viele Werte gespeichert werden können; jeder Wert wird dazu automatisch als Array gespeichert; eine Zuweisung stellt ein implizites push() auf dieses Array dar.
      Tie::CPHash
      ermöglicht — wie auch Hash::Case::Preserve — den Zugriff auf Hash-Elemente ohne Beachtung der Groß-/Kleinschreibung
      Tie::CacheHash
      versucht, (unter Zuhilfenahme von DB_File) möglichst effizient einen Hash zu implementieren, der sich automatisch nur eine vordefinierte Zahl von "Top"-Elementen (also z. B. die hundert mit den zahlenmäßig größten Werten) merkt.
      Tie::DB_File::SplitHash
      ist ein Wrapper um DB_File, der transparent die Verwaltung großer Datenmengen ermöglicht, die normalerweise an den Grenzen des verwendeten Dateisystems (z. B. 2 GB pro Datei) scheitern würde, indem es die Daten auf mehrere Dateien aufteilt.
      Tie::DxHash
      erlaubt es, mehrere Werte unter identischen Schlüsselnamen abzulegen und erhält gleichzeitig die Reihenfolge der Schlüssel und Werte.
      Tie::EncryptedHash
      speichert Werte in verschlüsselter Form.
      Tie::GHash
      verwendet statt der Perl-eigenenen Hash-Bibliothek eine im Rahmen des Gnome-Projekts entwickelte und verspricht sich davon insbesondere Speicherplatzersparnis bei großen Datenmengen, allerdings auf Kosten der Zugriffsgeschwindigkeit.
      Tie::Hash, Tie::StdHash, Tie::ExtraHash
      sind bei Perl mitgelieferte Standard-Module, die Implementation eigener Tie::Hash::*-Klassen erleichtern sollen, indem sie die benötigten Methoden bereits so implementieren, dass sie wie bei normalen Hashes funktionieren, so dass in einer Sub-Klasse nur noch diejenigen definiert werden müssen, die vom Normalverhalten abweichen sollen.
      Tie::Hash::Approx
      ermöglicht den Zugriff auf Elemente auch mit ähnlich geschriebenen Schlüsseln; es verwendet dazu die Funktion amatch() aus String::Approx.
      Tie::Hash::Cannabinol
      implementiert einen Hash, der ein Viertel von dem, was man in ihm ablegt, sofort wieder vergisst und in einem weiteren Viertel der Fälle nicht in der Lage ist, irgendwelche sinnvollen Informationen zu liefern; jegliche Werte, die man ihm entlockt, werden zufällig aus den enthaltenen Schlüsseln ausgewählt. Dem Modulautor selbst zufolge handelt es sich somit um eine vollkommen sinnfreie Demonstration, wie man unter Zuhilfenahme von Tie::StdHash das Verhalten von Hashes pervertieren kann.
      Tie::Hash::FixedKeys
      ermöglicht es, im Voraus zu definieren, welche Schlüssel ein Hash enthält, nach meinem Verständnis also etwas Ähnliches, wie sich mittlerweile mit dem fields-Pragma erzielen lassen sollte.
      Tie::Hash::Layered
      dient zum "Übereinanderlegen" mehrerer Hashes; ein Zugriff auf einen bestimmten Schlüssel bezieht sich dann auf den jeweils "obersten" Hash, der ein entsprechendes Element enthält.
      Tie::Hash::Rank
      implementiert einen Hash, bei dem Zugriffe auf Elemente nicht den ursprünglich für das jeweilige Element eingegebenen (Zahlen-)Wert, sondern den relativen "Rang" dieses Zahlenwerts in Bezug auf alle Elemente liefert; mögliche Anwendungsgebiete wären also z. B. Highscores.
      Tie::Hash::Regex
      ermöglicht den Zugriff auf Elemente auch mittels regulärer Ausdrücke (anstelle exakter Schlüsselnamen)
      Tie::Hash::Stack
      ist — ähnlich wie Tie::Hash::Layered dazu gedacht, mehrere Schichten übereinanderzulegen.
      Tie::Hash::Test
      scheint nur ein (undokumentiertes) Abfallprodukt aus der Test-Suite des Moduls Attribute::TieClasses zu sein.
      Tie::Hash::Transactional
      ermöglicht es — in Anlehnung an entsprechende Funktionalität von Datenbanken — den Zustand von Hashes zwischenzuspeichern, um ihn auch nach Veränderungen später bei Bedarf wiederherstellen zu können.
      Tie::Hash::TwoWay
      erzeugt Hashes, auf die nicht nur über die Namen der Schlüssel, sondern auch über die der Werte zugegriffen werden kann, wobei pro Schlüssel ggf. auch mehrere Werte in Form von Array-Referenzen übergeben werden können.
      Tie::HashDefaults
      ermöglicht es — ähnlich wie Tie::Hash::Layered, mehrere Hashes übereinanderzulegen, wobei allerdings die weiter unten liegenden Hashes durch "normale" Operationen nicht beeinflusst werden.
      Tie::HashHistory
      kann als Zwischenschicht zwischen einem Hash und einem weiteren Tie::Hash::*-Modul eingebunden werden und dient dazu, alle an jedem Eintrag vorgenommenen Veränderungen später in chronologischer Reihenfolge nachzuvollziehen.
      Tie::InsertOrderHash
      stellt sicher, dass einer Iteration über den Hash mittels each(), keys() oder values() die Reihenfolge der zurückgegeben Werte der entspricht, in der die Schlüssel ursprünglich angelegt worden. Was ich nicht ganz verstanden habe und worauf leider auch der Autor mir eine Antwort schuldig geblieben ist, ist, welchen Vorteil dieses Modul gegenüber dem Standard-Modul Tie::IxHash haben soll.
      Tie::IxHash
      ist ein Standard-Modul und verfolgt den gleichen Ansatz wie Tie::InsertOrderHash.
      Tie::LLHash
      hat im Großen und Ganzen den gleichen Nutzen wie Tie::IxHash, verwendet intern jedoch eine andere Datenstruktur, nämlich eine doppelt verlinkte Liste.
      Tie::ListKeyedHash
      erlaubt die Verwendung anonymer Arrays als Schlüsselnamen.
      Tie::PerfectHash
      implementiert einen "minimalen perfekten Hash", was immer das auch sein mag. Man kann dabei einige Parameter des Hashing-Algorithmus, wie z. B. die maximale Schlüssellänge, explizit festlegen, was laut Autor unter gewissen Umständen zu Performance-Gewinnen führen kann.
      Tie::RangeHash
      dient dazu, statt normaler Schlüsselnamen Bereiche zu definieren und auf die korrespondierenden Werte dann mit beliebigen Schlüsseln zuzugreifen, die im jeweiligen Bereich liegen.
      Tie::RefHash
      erlaubt die Verwendung von Referenzen als Schlüssel; mit Hilfe des mitgelieferten Tie::RefHash::Nestable soll dies darüber hinaus auch für verschachtelte Datenstrukturen funktionieren.
      Tie::RegexpHash
      ermöglicht es, reguläre Ausdrücke als Schlüssel zu verwenden; bei einem späteren Zugriff wird dann der übergebene Wert mit dem regulären Ausdruck verglichen; wenn man so will, also der umgekehrte Ansatz wie bei Tie::Hash::Regex.
      Tie::RevHash
      implementiert eien Hash, bei dem jedes Schlüssel-Wert-Paar gleichzeitig auch umgekehrt abgelegt wird, so dass über beide Daten auf das jeweils andere Datum zugegriffen werden kann.
      Tie::RevRefHash
      ist eine Sub-Klasse zu Tie::RevHash, bei der wohl auch Referenzen als Schlüssel funktionieren.
      Tie::RndHash
      soll dazu dienen, einen beliebigen Schlüssel in O(1) auszuwählen, existiert aber noch gar nicht (im CPAN).
      Tie::SecureHash
      ermöglicht es, den Zugriff auf bestimmte Einträge auf bestimmte Packages zu einzuschränken.
      Tie::SentientHash
      erlaubt es, bestimmte Hash-Elemente als "nur lesbar" zu deklarieren und/oder mit eigenen Funktionen zu assoziieren, die aufgerufen werden, wenn die entsprechenden Elemente abgerufen oder verändert werden sowie einige andere abstrakte Schweinereien.
      Tie::ShadowHash
      verfolgt einen ähnlichen Ansatz wie Hash::WithDefaults, Tie::Hash::Layered, Tie::Hash::Stack und Tie::HashDefaults, wobei allerdings auch auf DBM-Dateien und normale Textdateien als Datenquellen unterstützt werden.
      Tie::SortHash
      erlaubt es, die Reihenfolge, in der der Inhalt eines Hashes von each(), keys() oder values() wiedergegeben wird, durch Angabe einer Sortier-Routine (wie bei sort()) festzulegen.
      Tie::StrictHash
      sieht sich als "use strict;" für Hashes; das Anlegen und Löschen von Elementen wird nur über spezielle Funktionen erlaubt. Hintergedanke dabei ist primär, Fehler durch Tippfehler bei Hash-Schlüsselnamen vermeiden zu helfen.
      Tie::SubstrHash
      versucht, die Daten eines Hashes möglichst platzsparend in Form eines Strings abzulegen, wozu allerdings die maximale Länge der Schlüsselnamen und Werte sowie die maximale Anzahl von Elementen, die unterstützt werden soll, vorher festgelegt werden muss.
      Tie::TransactHash
      implementiert ein Transaktions-System für DBM-Dateien.
      Tie::TwoLevelHash
      ermöglicht den transparenten Zugriff auf einen Hash von Hashes (HoH), der als Datei gespeichert wird.