Neueste Version: 1.0.0-Beta
PHP 5.4+-Bibliothek zur Darstellung von Äquivalenzbeziehungen und Strategien zum Hashing und Sortieren von Werten.
Äquivalenzbeziehungen sind nützlich, um eine allgemeine Methode zum Vergleichen von Werten im Hinblick auf domänenspezifische Anforderungen zu etablieren und um benutzerdefinierte Kriterien zum Vergleichen von Werten in begrenzten Kontexten darzustellen, insbesondere für die Verwendung in Sammlungen.
Ergänzende Funktionen wie Hashing und Sortieren werden ebenfalls von dieser Bibliothek abgedeckt, was sie zu einer wertvollen Ergänzung Ihrer Entwicklungstools macht.
Die API ist ausführlich im Quellcode dokumentiert. Darüber hinaus steht auch eine HTML-Version zur komfortableren Anzeige im Browser zur Verfügung.
Verwenden Sie Composer, um das Paket zu installieren:
$ composer require phpcommon/comparison
Eine Relation ist ein mathematisches Werkzeug zur Beschreibung von Zusammenhängen zwischen Elementen von Mengen. Beziehungen werden in der Informatik häufig verwendet, insbesondere in Datenbanken und Planungsanwendungen.
Im Gegensatz zu den meisten modernen Sprachen unterstützt PHP keine Operatorüberladung, die in der Vergangenheit als Designoption vermieden wurde. Mit anderen Worten: Es ist nicht möglich, das Standardverhalten nativer Operatoren wie „gleich“, „identisch“, „größer als“, „kleiner als“ usw. zu überschreiben. Beispielsweise stellt Java die Schnittstelle „Comparable“ bereit, während Python einige magische Methoden bereitstellt.
Die Bedeutung eines solchen Konzepts wird in Situationen deutlicher, in denen der Begriff der Äquivalenz oder Ordnung je nach Vergleichsgegenstand oder Kontext variiert, wie in den folgenden Abschnitten erläutert wird.
Als Äquivalenzrelation bezeichnet man in der Mathematik eine binäre Relation, die reflexiv , symmetrisch und transitiv ist. Im Computerbereich gibt es jedoch noch eine weitere Eigenschaft, die berücksichtigt werden muss: Konsistenz . Konsistenz bedeutet, dass eine Beziehung für dieselbe Eingabe keine unterschiedlichen Ergebnisse liefern sollte.
Eine allgegenwärtige Äquivalenzrelation ist die Gleichheitsrelation zwischen Elementen einer beliebigen Menge. Weitere Beispiele sind:
Für die Zwecke dieser Bibliothek kann eine Äquivalenzbeziehung generisch oder typspezifisch sein. Typspezifische Beziehungen werden durch die Implementierung von Equatable
oder Equivalence
Schnittstellen definiert, während generische Äquivalenzen die letzte Schnittstelle implementieren müssen.
Die Equatable
-Schnittstelle definiert eine verallgemeinerte Methode, die eine Klasse implementiert, um eine typspezifische Methode zur Bestimmung der Gleichheit von Instanzen zu erstellen.
Zur Veranschaulichung betrachten wir eine Klasse Money
, die darauf abzielt, Geldwerte darzustellen. Diese Klasse ist ein guter Kandidat für die Implementierung der Equatable
Schnittstelle, da Money
ein Wertobjekt ist, das heißt, die Vorstellung der Gleichheit dieser Objekte basiert nicht auf Identität. Stattdessen sind zwei Instanzen von Money
gleich, wenn sie die gleichen Werte haben. Während also Money::USD(5) === Money::USD(5)
false
zurückgibt, Money::USD(5)->equals(Money::USD(5))
true
zurück.
Hier ist die zuvor erwähnte Klasse:
final class Money implements Equatable
{
private $ amount ;
private $ currency ;
public function __construct ( $ amount , $ currency )
{
$ this -> amount = ( int ) $ amount ;
$ this -> currency = ( string ) $ currency ;
}
public function equals ( Equatable $ other )
{
if (! $ other instanceof self) {
return false ;
}
return $ this -> amount === $ other -> amount && $ this -> currency === $ other -> currency ;
}
}
Es gibt jedoch viele Fälle, in denen eine nicht standardmäßige oder externe Möglichkeit zum Vergleichen zweier Werte erforderlich ist. Der offensichtlichste Anwendungsfall für diese benutzerdefinierten Beziehungen ist vielleicht die Verwendung mit Sammlungen, aber sie sind auch nützlich, um diese Funktionen für Skalarwerte oder eine vorhandene Klasse bereitzustellen, die sie selbst nicht bereitstellen kann, weil sie zu einem Paket eines Drittanbieters gehört oder erstellt wurde in PHP.
Angenommen, Sie entwickeln eine Software, die Krankenhäuser bei der Verwaltung von Blutspenden unterstützt. Eine der Anforderungen besagt, dass eine Krankenschwester kein Blut von Spendern entnehmen darf, die dieselbe Blutgruppe haben. Eine Beziehung für dieses Szenario würde wie folgt aussehen:
use PhpCommon Comparison Equivalence ;
class BloodGroupEquivalence implements Equivalence
{
public function equals ( Equatable $ other )
{
return get_class ( $ other ) === static ::class;
}
public function equivalent ( $ left , $ right )
{
if (! $ left instanceof Person) {
UnexpectedTypeException:: forType (Person::class, $ left );
}
if (! $ right instanceof Person) {
return false ;
}
return $ left -> getBloodType () === $ right -> getBloodType ();
}
}
Diese Beziehung bestimmt, ob zwei Personen dieselbe Blutgruppe haben:
$ equivalence = new BloodGroupEquivalence ();
$ donors = new BloodDonors ( $ equivalence );
$ james = new Person ( ' James ' , ' A ' );
$ john = new Person ( ' John ' , ' A ' );
// James and John are considered equivalent once they are of the same blood group
var_dump ( $ equivalence -> equivalent ( $ james , $ john )); // Outputs bool(true)
// Initially, none of them are present in the collection
var_dump ( $ volunteers -> contains ( $ james )); // Outputs bool(false)
var_dump ( $ volunteers -> contains ( $ john )); // Outputs bool(false)
// Add James to the set of volunteers
$ donors -> add ( $ james );
// Now, considering only the blood group of each donor for equality, both of
// them are considered present in the collection
$ donors -> contains ( $ james ); // Outputs bool(true)
$ donors -> contains ( $ john ); // Outputs bool(true)
Da BloodGroupEquivalence
eine Äquivalenzbeziehung zwischen Personen basierend auf ihrer Blutgruppe herstellt, wird jeder Versuch, John zur Sammlung hinzuzufügen, ignoriert, da James bereits vorhanden ist und sie dieselbe Blutgruppe haben.
Für eine einfache Anforderung mag es auf den ersten Blick etwas kompliziert erscheinen, aber in realen Fällen kann es zum Vergleich der Äquivalenz zwischen kompatiblen Blutgruppen verwendet werden, um Spender in Gruppen einzuteilen.
Diese Bibliothek stellt einige generische Äquivalenzbeziehungen als Teil der Standardbibliothek bereit, wie unten beschrieben.
Vergleicht zwei Werte auf Identität.
Diese Beziehung basiert auf dem identischen Operator. In den meisten Fällen gelten zwei Werte als gleichwertig, wenn sie denselben Typ und denselben Wert haben. Es gibt jedoch einige Ausnahmen:
NAN
ist ungleich zu jedem anderen Wert, einschließlich sich selbst.Die folgende Tabelle fasst zusammen, wie Operanden der verschiedenen Typen verglichen werden:
$A $B | NULL | Boolescher Wert | Ganze Zahl | Schweben | Zeichenfolge | Ressource | Array | Objekt |
---|---|---|---|---|---|---|---|---|
NULL | true | false | false | false | false | false | false | false |
Boolescher Wert | false | $A === $B | false | false | false | false | false | false |
Ganze Zahl | false | false | $A === $B | false | false | false | false | false |
Schweben | false | false | false | $A === $B | false | false | false | false |
Zeichenfolge | false | false | false | false | $A === $B | false | false | false |
Ressource | false | false | false | false | false | $A === $B | false | false |
Array | false | false | false | false | false | false | $A === $B | false |
Objekt | false | false | false | false | false | false | false | $A === $B |
Vergleicht zwei Werte auf Gleichheit.
Die Wertäquivalenz verhält sich genauso wie die Identitätsäquivalenz, außer dass sie den Vergleich zwischen Equatable
Objekten an die verglichenen Objekte delegiert. Zusätzlich können externe Beziehungen zum Vergleich von Werten eines bestimmten Typs angegeben werden. Dies ist in Fällen nützlich, in denen es wünschenswert ist, das Standardverhalten für einen bestimmten Typ zu überschreiben, alle anderen jedoch beizubehalten. Es ist auch nützlich, um eine Beziehung für Objekte von Klassen zu definieren, die zu Paketen von Drittanbietern gehören oder in PHP integriert sind.
Die folgenden Regeln werden verwendet, um zu bestimmen, ob zwei Werte als gleichwertig angesehen werden:
NAN
ist ungleich zu jedem anderen Wert, einschließlich sich selbst.Equatable
und der Ausdruck $left->equals($right)
wird als true
ausgewertet.$relation->equivalent($left, $right)
wird als true
ausgewertet.Die folgende Tabelle fasst zusammen, wie Operanden der verschiedenen Typen verglichen werden:
$A $B | NULL | Boolescher Wert | Ganze Zahl | Schweben | Zeichenfolge | Ressource | Array | Objekt | Gleichwertig |
---|---|---|---|---|---|---|---|---|---|
NULL | true | false | false | false | false | false | false | false | false |
Boolescher Wert | false | $A === $B | false | false | false | false | false | false | false |
Ganze Zahl | false | false | $A === $B | false | false | false | false | false | false |
Schweben | false | false | false | $A === $B | false | false | false | false | false |
Zeichenfolge | false | false | false | false | $A === $B | false | false | false | false |
Ressource | false | false | false | false | false | $A === $B | false | false | false |
Array | false | false | false | false | false | false | eq($A, $B) | false | false |
Objekt | false | false | false | false | false | false | false | $A === $B | false |
Gleichwertig | false | false | false | false | false | false | false | false | $A‑>equals($B) |
Wobei eq()
eine Funktion bezeichnet, die jedes Paar entsprechender Einträge rekursiv gemäß den oben beschriebenen Regeln vergleicht.
Diese Beziehung bietet auch eine Möglichkeit, die Äquivalenzlogik für eine bestimmte Klasse zu überschreiben, ohne dass eine neue Beziehung erstellt werden muss. Angenommen, Sie möchten Instanzen von DateTime
anhand ihrer Werte vergleichen, aber das Standardverhalten für die anderen Typen beibehalten. Dies kann erreicht werden, indem eine benutzerdefinierte Beziehung angegeben wird, die immer dann verwendet wird, wenn eine Instanz von DateTime
mit einem anderen Wert verglichen wird:
use PhpCommon Comparison Hasher ValueHasher as ValueEquivalence ;
use PhpCommon Comparison Hasher DateTimeHasher as DateTimeEquivalence ;
use DateTime ;
$ relation = new ValueEquivalence ([
DateTime::class => new DateTimeEquivalence ()
]);
$ date = ' 2017-01-01 ' ;
$ timezone = new DateTimeZone ( ' Pacific/Nauru ' );
$ left = new DateTime ( $ date , $ timezone );
$ right = new DateTime ( $ date , $ timezone );
// Outputs bool(true)
var_dump ( $ relation -> equivalent ( $ left , $ right ));
Vergleicht zwei Werte auf semantische Gleichheit.
Für zukünftige Versionen ist eine semantische Äquivalenz geplant. Es würde den Vergleich von Werten ermöglichen, die semantisch ähnlich aussehen – auch wenn sie unterschiedlichen Typs sind. Es ähnelt der Funktionsweise des losen Vergleichs in PHP, jedoch unter restriktiveren Bedingungen, sodass die Eigenschaften Reflexivität, Symmetrie und Transitivität gelten.
Vergleicht zwei DateTime
Instanzen anhand ihres Datums, ihrer Uhrzeit und ihrer Zeitzone.
Diese Beziehung betrachtet zwei Instanzen von DateTime
als gleichwertig, wenn sie dasselbe Datum, dieselbe Uhrzeit und dieselbe Zeitzone haben:
use PhpCommon Comparison Hasher IdentityHasher as IdentityEquivalence ;
use PhpCommon Comparison Hasher DateTimeHasher as DateTimeEquivalence ;
use DateTime ;
$ identity = new IdentityEquivalence ();
$ value = new DateTimeEquivalence ();
$ date = ' 2017-01-01 ' ;
$ timezone = new DateTimeZone ( ' Pacific/Nauru ' );
$ left = new DateTime ( $ date , $ timezone );
$ right = new DateTime ( $ date , $ timezone );
// Outputs bool(false)
var_dump ( $ identity -> equivalent ( $ left , $ right ));
// Outputs bool(true)
var_dump ( $ value -> equivalent ( $ left , $ right ));
In PHP können Array-Schlüssel nur als Zahlen und Zeichenfolgen dargestellt werden. Es gibt jedoch mehrere Fälle, in denen das Speichern komplexer Typen als Schlüssel hilfreich ist. Nehmen Sie als Beispiel Klassen, die verschiedene Arten von Zahlen oder Zeichenfolgen darstellen, wie z. B. GMP-Objekte, Unicode-Zeichenfolgen usw. Es wäre praktisch, solche Objekte auch als Array-Schlüssel verwenden zu können.
Um diese Lücke zu schließen, führt diese Bibliothek die Schnittstellen Hashable
und Hasher
ein, die ein Protokoll zur Bereitstellung von Hash-Codes für Werte spezifizieren. Für diese Schnittstellen ist es nicht erforderlich, dass Implementierer perfekte Hashing-Funktionen bereitstellen. Das heißt, zwei Werte, die nicht äquivalent sind, können denselben Hash-Code haben. Um jedoch festzustellen, ob zwei Werte mit demselben Hash-Code tatsächlich gleich sind, sollten die Konzepte Hashing und Äquivalenz komplementär kombiniert werden. Es erklärt, warum Hasher
und Hashable
Equivalence
bzw. Equatable
erweitern.
Ein Wort der Warnung
Ein Hash-Code dient zum effizienten Einfügen und Suchen in Sammlungen, die auf einer Hash-Tabelle basieren, und zur schnellen Ungleichheitsprüfung. Ein Hash-Code ist kein dauerhafter Wert. Aus diesem Grund:
- Serialisieren Sie Hash-Codewerte nicht und speichern Sie sie nicht in Datenbanken.
- Verwenden Sie den Hash-Code nicht als Schlüssel zum Abrufen eines Objekts aus einer verschlüsselten Sammlung.
- Senden Sie keine Hash-Codes über Anwendungsdomänen oder Prozesse hinweg. In einigen Fällen können Hash-Codes pro Prozess oder pro Anwendungsdomäne berechnet werden.
- Verwenden Sie den Hash-Code nicht anstelle eines von einer kryptografischen Hashing-Funktion zurückgegebenen Werts, wenn Sie einen kryptografisch starken Hash benötigen.
- Testen Sie nicht auf Gleichheit von Hash-Codes, um festzustellen, ob zwei Objekte gleich sind, da ungleiche Werte identische Hash-Codes haben können.
Es gibt Fälle, in denen es wünschenswert sein könnte, eine benutzerdefinierte Hashing-Logik für eine Klasse zu definieren, die Ihren Anforderungen am besten entspricht. Angenommen, Sie haben eine Point-Klasse zur Darstellung eines 2D-Punkts:
namespace PhpCommon Comparison Equatable ;
final class Point implements Equatable
{
private $ x ;
private $ y ;
public function __construct ( $ x , $ y )
{
$ this -> x = ( int ) $ x ;
$ this -> y = ( int ) $ y ;
}
public function equals ( Equatable $ point )
{
if (! $ point instanceof Point) {
return false ;
}
return $ this -> x === $ point -> x && $ this -> y === $ point -> y ;
}
}
Ein Point
enthält die x- und y-Koordinaten eines Punktes. Gemäß der Definition der Klasse gelten zwei Punkte als gleich, wenn sie die gleichen Koordinaten haben. Wenn Sie jedoch beabsichtigen, Instanzen von Point
in einer Hash-basierten Karte zu speichern, beispielsweise weil Sie Koordinaten mit Beschriftungen verknüpfen möchten, müssen Sie sicherstellen, dass Ihre Klasse Hash-Codes erzeugt, die mit der Logik zur Bestimmung von zwei übereinstimmen Punkte gelten als gleich:
namespace PhpCommon Comparison Equatable ;
final class Point implements Hashable
{
private $ x ;
private $ y ;
public function __construct ( $ x , $ y )
{
$ this -> x = ( int ) $ x ;
$ this -> y = ( int ) $ y ;
}
public function equals ( Equatable $ point )
{
if (! $ point instanceof Point) {
return false ;
}
return $ this -> x === $ point -> x && $ this -> y === $ point -> y ;
}
public function getHash ()
{
return 37 * ( 31 + $ this -> $ x ) + $ this -> $ x ;
}
}
Auf diese Weise funktioniert die Methode getHash()
in Übereinstimmung mit der Methode equals()
, obwohl der Hashing-Algorithmus möglicherweise nicht ideal ist. Die Implementierung effizienter Algorithmen zur Hash-Code-Generierung geht über den Rahmen dieses Leitfadens hinaus. Es wird jedoch empfohlen, einen schnellen Algorithmus zu verwenden, der einigermaßen unterschiedliche Ergebnisse für ungleiche Werte liefert, und die umfangreiche Vergleichslogik auf Equatable::equals()
zu verlagern.
Beachten Sie, dass hashbare Objekte entweder unveränderlich sein sollten oder dass Sie Disziplin walten lassen müssen, um sie nicht zu ändern, nachdem sie in hashbasierten Strukturen verwendet wurden.
Ein Hasher bietet Hashing-Funktionalität für primitive Typen und Objekte von Klassen, die Hashable
nicht implementieren.
Die von dieser Schnittstelle eingeführte Methode hash()
soll ein Mittel zur Durchführung schneller Inäquivalenzprüfungen und effizienter Einfügung und Suche in Hash-basierten Datenstrukturen bieten. Diese Methode ist immer kohärent mit equivalent()
, was bedeutet, dass für alle Referenzen $x
und $y
, wenn equivalent($x, $y)
, dann hash($x) === hash($y)
. Wenn jedoch equivalence($x, $y)
false
ergibt, kann hash($x) === hash($y)
immer noch wahr sein. Daher eignet sich die Methode hash()
für Inäquivalenzprüfungen , nicht jedoch für Äquivalenzprüfungen .
Alle in dieser Bibliothek enthaltenen Implementierungen von Equivalence
bieten auch Hashing-Funktionalität. Weitere Informationen zum Hashing von Werten finden Sie in der Dokumentation der jeweiligen Implementierung.
Comparable
und Comparator
folgen der gleichen Logik wie die zuvor besprochenen Konzepte und sind Schnittstellen zur Bereitstellung natürlicher bzw. benutzerdefinierter Sortierstrategien. Beide Schnittstellen spezifizieren eine Gesamtordnungsrelation, eine Relation, die reflexiv , antisymmetrisch und transitiv ist.
Diese Schnittstelle erlegt den Objekten jeder Klasse, die sie implementiert, eine Gesamtordnung auf. Diese Reihenfolge wird als natürliche Reihenfolge der Klasse bezeichnet, und die Methode Comparable::compareTo()
wird als ihre natürliche Vergleichsmethode bezeichnet.
Das folgende Beispiel zeigt, wie eine Klasse die natürliche Reihenfolge ihrer Instanzen definieren kann:
use PhpCommon Comparison UnexpectedTypeException ;
final class BigInteger implements Comparable
{
private $ value ;
public function __construct ( $ value )
{
$ this -> value = ( string ) $ value ;
}
public function compareTo ( Comparable $ other )
{
if (! $ other instanceof self) {
throw UnexpectedTypeException:: forType (BigInteger::class, $ other );
}
return bccomp ( $ this -> value , $ other -> value );
}
}
Der Zweck eines Comparator
besteht darin, Ihnen die Definition einer oder mehrerer Vergleichsstrategien zu ermöglichen, die nicht die natürliche Vergleichsstrategie für eine Klasse sind. Im Idealfall muss ein Comparator
von einer anderen Klasse implementiert werden als der, für die er die Vergleichsstrategie definiert. Wenn Sie eine natürliche Vergleichsstrategie für eine Klasse definieren möchten, können Sie stattdessen Comparable
implementieren.
Komparatoren können an eine Sortiermethode einer Sammlung übergeben werden, um eine genaue Kontrolle über deren Sortierreihenfolge zu ermöglichen. Es kann auch verwendet werden, um die Reihenfolge bestimmter Datenstrukturen zu steuern, beispielsweise sortierter Mengen oder sortierter Karten. Betrachten Sie beispielsweise den folgenden Komparator, der Zeichenfolgen nach ihrer Länge sortiert:
use PhpCommon Comparison Comparator ;
class StringLengthComparator implements Comparator
{
public function compare ( $ left , $ right )
{
return strlen ( $ left ) <=> strlen ( $ right );
}
}
$ comparator = new StringLengthComparator ();
// Outputs int(-1)
var_dump ( $ comparator -> compare ( ' ab ' , ' a ' ));
Diese Implementierung stellt eine von vielen Möglichkeiten zum Sortieren von Zeichenfolgen dar. Andere Strategien umfassen die alphabetische, lexikografische Sortierung usw.
Weitere Informationen zu den letzten Änderungen finden Sie im CHANGELOG.
$ composer test
Weitere Einzelheiten finden Sie in der Testdokumentation.
Beiträge zum Paket sind jederzeit willkommen!
Weitere Informationen finden Sie unter BEITRAG und VERHALTEN.
Wenn Sie sicherheitsrelevante Probleme entdecken, senden Sie bitte eine E-Mail an [email protected], anstatt den Issue-Tracker zu verwenden.
Alle Inhalte dieses Pakets stehen unter der MIT-Lizenz.