Quiq::Hash - Zugriffssicherer Hash mit automatisch generierten Attributmethoden
Quiq::Object
Klasse laden:
use Quiq::Hash;
Hash-Objekt instantiieren:
my $h = Quiq::Hash->new(a=>1,b=>1,c=>3);
Werte abfragen oder setzen:
my $v = $h->get('a'); # oder: $v = $h->{'a'}; $h->set(b=>2); # oder: $h->{'b'} = 2;
Unerlaubte Zugriffe:
$v = $h->get('d'); # Exception! $h->set(d=>4); # Exception!
Erlaubte Zugriffe;
$v = $h->try('d'); # undef $h->add(d=>4);
Ein Objekt dieser Klasse repräsentiert einen zugriffssicheren Hash, d.h. einen Hash, dessen Schlüsselvorrat bei der Instantiierung festgelegt wird. Ein lesender oder schreibender Zugriff mit einem Schlüssel, der nicht zum Schlüsselvorrat gehört, ist nicht erlaubt und führt zu einer Exception.
Der Zugriffsschutz beruht auf der Funktionalität des Restricted Hash.
Abgesehen vom Zugriffsschutz verhält sich ein Hash-Objekt der Klasse wie einer normaler Perl-Hash und kann auch so angesprochen werden. Bei den Methoden ist der konventionelle Zugriff als Alternative Formulierung angegeben.
Alternative Formulierung
$h = $class->new; # [1] $h = $class->new(@keyVal); # [2] $h = $class->new(\@keys,\@vals[,$val]); # [3] $h = $class->new(\@keys[,$val]); # [4] $h = $class->new(\%hash); # [5] $h = $class->new(\%hash,@keyVal); # [6]
Instantiiere ein Hash-Objekt, setze die Schlüssel/Wert-Paare und liefere eine Referenz auf dieses Objekt zurück.
Leerer Hash.
Die Argumentliste ist eine Aufzählung von Schlüssel/Wert-Paaren.
Schlüssel und Werte befinden sich in getrennten Arrays. Ist ein Wert undef, wird $val gesetzt, falls angegeben.
undef
Nur die Schlüssel sind angegeben. Ist $val angegeben, werden alle Werte auf diesen Wert gesetzt. Ist $val nicht angegeben, werden alle Werte auf undef gesetzt.
Blesse den Hash %hash auf Klasse $class.
Instantiiere den Hash aus den Schlüssel/Wert-Paaren @keyVal und weise dem (restricted) Hash alle Komponenten aus %hash zu. Dieser Aufruf ist nützlich, um einen anonymen Hash zu einem Hash-Objekt mit vorgegebenen Attributen zu machen. (Wenn der anonyme Hash ein nicht-vorgesehenes Attribut enthält, wird eine Exception geworfen.)
$h = $class->fabricate($subClass,...);
Wie new(), nur dass der Hash als Instanz der Subklasse $subClass erzeugt wird. Die Subklasse wird on-the-fly erzeugt, falls sie noch nicht existiert.
$val = $h->get($key); @vals = $h->get(@keys);
Liefere die Werte zu den angegebenen Schlüsseln. In skalarem Kontext liefere keine Liste, sondern den Wert des ersten Schlüssels.
Alternative Formulierung:
$val = $h->{$key}; # ein Schlüssel @vals = @{$h}{@keys}; # mehrere Schlüssel
$valS = $h->getRef($key);
Liefere nicht den Wert zum Schlüssel $key, sondern eine Referenz auf den Wert.
Dies kann praktisch sein, wenn der Wert manipuliert werden soll. Die Manipulation kann dann über die Referenz erfolgen und der Wert muss nicht erneut zugewiesen werden.
$valS = \$h->{$key};
Newline an Wert anhängen mit getRef():
$valS = $h->getRef('x'); $$valS .= "\n";
Dasselbe ohne getRef():
$val = $h->get('x'); $val .= "\n"; $val->set(x=>$val);
@arr|$arr = $h->getArray($key);
Liefere die Liste von Werten des Schlüssels $key. Im Skalarkontext liefere eine Referenz auf die Liste (der Aufruf hat dann die gleiche Wirkung wie der Aufruf von $h->get()). Der Wert von $key muss eine Array-Referenz sein.
$val = $h->try($key); @vals = $h->try(@keys);
Wie get(), nur dass im Falle eines unerlaubten Schlüssels keine Exception geworfen, sondern undef geliefert wird.
$h->set(@keyVal);
Setze die angegebenen Schlüssel/Wert-Paare.
$h->{$key} = $val; # ein Schlüssel/Wert-Paar @{$h}{@keys} = @vals; # mehrere Schlüssel/Wert-Paare
$val = $h->add($key=>$val); @vals = $h->add(@keyVal);
Wie set(), nur dass im Falle eines unerlaubten Schlüssels keine Exception generiert, sondern der Hash um das Schlüssel/Wert-Paar erweitert wird.
$val = $h->memoize($key,$sub);
Besitzt das Attribut $key einen Wert, liefere ihn. Andernfalls berechne den Wert mittels der Subroutine $sub und cache ihn auf dem Attribut.
Die Methode ist nützlich, um in Objektmethoden eingebettet zu werden, die einen berechneten Wert liefern, der nicht immer wieder neu gerechnet werden soll.
Alternative Formulierungen:
$val = $h->{$key} //= $h->$sub($key);
oder
$val = $h->{$key} //= do { # Implementierung der Subroutine };
$ref = $h->memoizeWeaken($key,$sub);
Wie memozize(), nur dass $sub eine Referenz liefert, die von der Methode automatisch zu einer schwachen Referenz gemacht wird.
Bei nicht-existenter Referenz kann die Methode $sub einen Leerstring liefern. Dieser wird auf undef abgebildet.
$val = $h->compute($key,$sub);
Wende Subroutine $sub auf den Wert des Schlüssels $key an. Die Subroutine hat die Struktur:
sub { my ($h,$key) = @_; ... return $val; }
Der Rückgabewert der Subroutine wird an Schlüssel $key zugewiesen.
Methode increment() mit apply() realisiert:
$val = $h->compute($key,sub { my ($h,$key) = @_; return $h->{$key}+1; # nicht $h->{$key}++! });
$val = $h->AUTOLOAD; $val = $h->AUTOLOAD($val);
Erzeuge eine Akzessor-Methode für eine Hash-Komponente. Die Methode AUTOLOAD() wird für jede Hash-Komponente einmal aufgerufen. Danach gehen alle Aufrufe für die Komponente direkt an die erzeugte Akzessor-Methode.
Die Methode AUTOLOAD() erweitert ihre Klasse um automatisch generierte Akzessor-Methoden. D.h. für jede Komponente des Hash wird bei Bedarf eine Methode erzeugt, durch die der Wert der Komponente manipuliert werden kann. Dadurch ist es möglich, die Manipulation von Attributen ohne Programmieraufwand nahtlos in die Methodenschnittstelle einer Klasse zu integrieren.
Gegenüberstellung:
Hash-Zugriff get()/set() Methoden-Zugriff -------------------- ----------------------- -------------------- $name = $h->{'name'} $name = $h->get('name') $name = $h->name $h->{'name'} = $name $h->set(name=>$name) $h->name($name) -or- $h->name = $name
In der letzten Spalte ("Methoden-Zugriff") steht die Syntax der automatisch generierten Akzessor-Methoden.
Die Akzessor-Methode wird als lvalue-Methode generiert, d.h. die Hash-Komponente kann per Akzessor-Aufruf manipuliert werden. Beispiele:
$h->name = $name; $h->name =~ s/-//g;
Die Erzeugung einer Akzessor-Methode erfolgt (vom Aufrufer unbemerkt) beim ersten Aufruf. Danach wird die Methode unmittelbar gerufen.
Der Zugriff über eine automatisch generierte Attributmethode ist ca. 30% schneller als über $h->get().
@keys|$keyA = $h->keys;
Liefere die Liste aller Schlüssel. Die Liste ist unsortiert. Im Skalarkontext liefere eine Referenz auf die Liste.
Die Reihenfolge der Schlüssel ist undefiniert.
@keys = keys %$h;
$n = $h->hashSize;
Liefere die Anzahl der Schlüssel/Wert-Paare des Hash.
$n = keys %$h;
$class->validate(\%hash,\@keys); $class->validate(\%hash,\%keys);
Prüfe die Schlüssel des Hash %hash gegen die Schlüssel in Array @keys bzw. Hash %keys. Enthält %hash einen Schlüssel, der nicht in @keys bzw. %keys vorkommt, wird eine Exception geworfen.
$h2 = $h->copy; $h2 = $h->copy(@keyVal);
Kopiere Hash, d.h. instantiiere einen neuen Hash mit den gleichen Schlüssel/Wert-Paaren. Es wird nicht rekursiv kopiert, sondern eine "shallow copy" erzeugt.
Sind Schlüssel/Wert-Paare @keyVal angegeben, werden diese nach dem Kopieren per set() auf dem neuen Hash gesetzt.
$h = $h->join(\%hash);
Hash (für Method Chaining)
Überschreibe die Schlüssel/Wert-Paare in Hash $h mit den Schlüssel/Wert-Paaren aus Hash %hash. Schlüssel/Wert-Paare in Hash $h, die in Hash %hash nicht vorkommen, bleiben bestehen. Enthält %hash einen Schlüssel, der in $h nicht vorkommt, wird eine Exception geworfen.
Ein Hash-Objekt mit vorgegebenen Attributen aus einem anoymen Hash erzeugen. Der anonyme Hash darf weniger, aber nicht mehr Attribute enthalten:
$h = Quiq::Hash->new([qw/ name label width height /])->join(\%hash);
$h->delete(@keys);
Entferne die Schlüssel @keys (und ihre Werte) aus dem Hash. An der Menge der zulässigen Schlüssel ändert sich dadurch nichts!
delete $h->{$key}; # einzelner Schlüssel delete @{$h}{@keys}; # mehrere Schlüssel
$h->clear;
Leere Hash, d.h. entferne alle Schlüssel/Wert-Paare.
%$h = ();
$bool = $h->exists($key);
Prüfe, ob der angegebene Schlüssel im Hash existiert. Wenn ja, liefere wahr, andernfalls falsch.
$bool = exists $self->{$key};
$bool = $h->defined($key);
Prüfe, ob der angegebene Schlüssel im Hash einen Wert hat. Wenn ja, liefere wahr, andernfalls falsch.
$bool = defined $h->{$key};
$bool = $h->isEmpty;
Prüfe, ob der Hash leer ist. Wenn ja, liefere wahr, andernfalls falsch.
$bool = %$h;
$bool = $h->isLocked;
Prüfe, ob der Hash gelockt ist. Wenn ja, liefere wahr, andernfalls falsch.
$h = $h->lockKeys;
Sperre den Hash. Anschließend kann kein weiterer Schlüssel zugegriffen werden. Wird dies versucht, wird eine Exception geworfen.
Hash::Util::lock_keys(%$h);
Die Methode liefert eine Referenz auf den Hash zurück.
$h = $h->unlockKeys;
Entsperre den Hash. Anschließend kann der Hash uneingeschränkt manipuliert werden. Die Methode liefert eine Referenz auf den Hash zurück. Damit kann der Hash gleich nach der Instantiierung entsperrt werden:
return Quiq::Hash->new(...)->unlockKeys;
Hash::Util::unlock_keys(%$h);
$n = $h->arraySize($key);
$h->setOrPush($key=>$arg);
Skalare oder Array-Komponente des Hash
Skalarer Wert oder Array-Referenz
Ist $key eine skalare Komponente des Hash, setze das Attribut auf $arg. Ist $key eine Array-Komponente des Hash, pushe $arg (wenn skalarer Wert) oder @$arg (wenn Array-Referenz) auf das Array.
$h->push($key,@values);
Arraykomponente.
Werte, die zum Array hinzugefügt werden.
Füge Werte @values zur Arraykomponente $key hinzu. Die Methode liefert keinen Wert zurück.
$h->unshift($key,$val);
Wert, der zum Array hinzugefügt wird.
Füge Wert $val am Anfang zur Arraykomponente $key hinzu. Die Methode liefert keinen Wert zurück.
$n = $h->increment($key);
Inkrementiere (Integer-)Wert zu Schlüssel $key und liefere das Resultat zurück.
$n = ++$h->{$key};
$y = $h->addNumber($key,$x);
Addiere numerischen Wert $x zum Wert des Schlüssels $key hinzu und liefere das Resultat zurück.
$y = $h->{$key} += $x;
$ref = $h->weaken($key); $ref = $h->weaken($key=>$ref);
Mache die Referenz von Schlüssel $key zu einer schwachen Referenz und liefere sie zurück. Ist eine Referenz $ref als Parameter angegeben, setze die Referenz zuvor.
$n = $h->buckets;
Liefere die Anzahl der Hash-Buckets.
$n = $h->bucketsUsed;
Liefere die Anzahl der genutzten Hash-Buckets.
$n = $this->getCount;
Liefere die Anzahl der get-Aufrufe seit Start des Programms.
$n = $this->setCount;
Liefere die Anzahl der set-Aufrufe seit Start des Programms.
Anzahl Zugriffe pro CPU-Sekunde im Vergleich zwischen verschiedenen Zugriffsmethoden:
A - Hash: $h->{$k} B - Hash: eval{$h->{$k}} C - Restricted Hash: $h->{$k} D - Restricted Hash: eval{$h->{$k}} E - Quiq::Hash: $h->{$k} F - Quiq::Hash: $h->get($k) Rate F D B E C A F 1401111/s -- -71% -74% -82% -83% -84% D 4879104/s 248% -- -8% -37% -40% -44% B 5297295/s 278% 9% -- -32% -35% -39% E 7803910/s 457% 60% 47% -- -4% -11% C 8104988/s 478% 66% 53% 4% -- -7% A 8745272/s 524% 79% 65% 12% 8% --
Den Hash via $h->get() zuzugreifen (F) ist ca. 85% langsamer als der einfachste Hash-Lookup (A). Wird auf den Methodenaufruf verzichtet und per $h->{$key} zugegriffen (E), ist der Zugriff nur 11% langsamer. Es ist also ratsam, intern per $h->{$key} zuzugreifen. Per $h->get() können immerhin 1.400.000 Lookups pro CPU-Sekunde ausgeführt werden. Bei nicht-zugriffsintensiven Anwendungen ist das sicherlich schnell genug. Die Anzahl der Aufrufe von $h->get() und $h->set() wird intern gezählt und kann per $class->getCount() und $class->setCount() abgefragt werden.
Das Benchmark-Programm (bench-hash):
#!/usr/bin/env perl use strict; use warnings; use Benchmark; use Hash::Util; use Quiq::Hash; my $h1 = {0=>'a',1=>'b',2=>'c',3=>'d',4=>'e',5=>'f'}; my $h2 = Hash::Util::lock_ref_keys({0=>'a',1=>'b',2=>'c',3=>'d',4=>'e',5=>'f'}); my $h3 = Quiq::Hash->new({0=>'a',1=>'b',2=>'c',3=>'d',4=>'e',5=>'f'}); my $i = 0; Benchmark::cmpthese(-10,{ A => sub { $h1->{$i++%5}; }, B => sub { eval{$h1->{$i++%5}}; }, C => sub { $h2->{$i++%5}; }, D => sub { eval{$h2->{$i++%5}}; }, E => sub { $h3->{$i++%5}; }, F => sub { $h3->get($i++%5); }, });
1.212
Frank Seitz, http://fseitz.de/
Copyright (C) 2023 Frank Seitz
This code is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install Quiq, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Quiq
CPAN shell
perl -MCPAN -e shell install Quiq
For more information on module installation, please visit the detailed CPAN module installation guide.