最新版本:1.0.0-beta
PHP 5.4+ 函式庫,用於表示等價關係以及雜湊和排序值的策略。
等價關係可用於建立一種通用方法來比較特定於領域的要求的值,以及表示將值與有界上下文進行比較的自訂標準,特別是在集合中使用。
該庫還涵蓋了散列和排序等補充功能,使其成為您的開發工具帶的寶貴補充。
該 API 在原始程式碼中進行了詳細記錄。此外,還提供 HTML 版本,以便更方便地在瀏覽器中查看。
使用 Composer 安裝套件:
$ composer require phpcommon/comparison
關係是描述集合元素之間關聯的數學工具。關係廣泛應用於電腦科學,特別是資料庫和調度應用程式。
與大多數現代語言不同,PHP 不支援運算符重載,這在歷史上是避免作為設計選擇的。換句話說,無法覆蓋原生運算符的預設行為,例如等於、相同、大於、小於等。
在等價或排序的概念根據比較主題或上下文而變化的情況下,這種概念的重要性變得更加明顯,如以下各節所述。
在數學中,等價關係是一種自反、對稱和傳遞的二元關係。然而,在計算領域,還有一個必須考慮的屬性:一致性。一致性意味著關係對於相同的輸入不應該產生不同的結果。
普遍存在的等價關係是任何集合的元素之間的相等關係。其他範例包括:
就該庫而言,等價關係可以是通用的,也可以是特定於類型的。特定於類型的關係是透過實作Equatable
或Equivalence
介面來定義的,而通用等價必須實現最後一個介面。
Equatable
介面定義了類別實作的通用方法,以建立特定於類型的方法來確定實例的相等性。
為了說明這一點,考慮一個Money
類,它的目的是表示貨幣價值。此類別是實現Equatable
介面的良好候選類,因為Money
是一個值對象,也就是說,這些對象的相等概念不基於身分。相反,如果Money
的兩個實例具有相同的值,則它們是相等的。因此,當Money::USD(5) === Money::USD(5)
傳回false
時, Money::USD(5)->equals(Money::USD(5))
傳回true
。
這是前面提到的類別:
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 ;
}
}
然而,在許多情況下,有必要使用非標準或外部方法來比較兩個值。也許,這些自訂關係最明顯的用例是與集合一起使用,但它對於向標量值或本身無法提供這些功能的現有類別提供這些功能也很有用,因為它屬於第三方套件或建置的進入PHP。
假設您正在開發一款軟體來幫助醫院管理捐血。其中一項要求是,護理師不能從具有相同血型的捐血者那裡採集血液。此場景的關係如下所示:
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 ();
}
}
這種關係決定了兩個人是否有相同的血型:
$ 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)
由於BloodGroupEquivalence
根據血型在人們之間建立了等價關係,因此任何將 John 添加到集合中的嘗試都將被忽略,因為 James 已經存在並且他們具有相同的血型。
對於一個簡單的要求來說,乍看之下可能有點複雜,但在實際情況下,它可以用來比較相容血型之間的等價性,以便將捐贈者分組。
該庫提供了一些通用等價關係作為標準庫的一部分,如下所述。
比較兩個值的同一性。
該關係基於相同運算符。大多數情況下,如果兩個值具有相同的類型和值,則它們被視為等效,但也有一些例外:
NAN
與其他所有值都不相等,包括它本身。下表總結了各種類型的操作數的比較方式:
$A $B | 無效的 | 布林值 | 整數 | 漂浮 | 細繩 | 資源 | 大批 | 目的 |
---|---|---|---|---|---|---|---|---|
無效的 | true | false | false | false | false | false | false | false |
布林值 | false | $A === $B | false | false | false | false | false | false |
整數 | false | false | $A === $B | false | false | false | false | false |
漂浮 | false | false | false | $A === $B | false | false | false | false |
細繩 | false | false | false | false | $A === $B | false | false | false |
資源 | false | false | false | false | false | $A === $B | false | false |
大批 | false | false | false | false | false | false | $A === $B | false |
目的 | false | false | false | false | false | false | false | $A === $B |
比較兩個值是否相等。
值等價的行為與恆等價完全相同,只不過它將Equatable
物件之間的比較委託給正在比較的物件。此外,可以指定外部關係來比較特定類型的值。在需要覆蓋特定類型的預設行為但保留所有其他類型的情況下,它非常有用。它對於定義屬於第三方套件或內建於 PHP 中的類別的物件關係也很有用。
以下規則用於確定兩個值是否被視為等效:
NAN
與其他所有值(包括其自身)都不相等。Equatable
的實例,表達式$left->equals($right)
的計算結果為true
。$relation->equivalent($left, $right)
被評估為true
。下表總結了各種類型的操作數的比較方式:
$A $B | 無效的 | 布林值 | 整數 | 漂浮 | 細繩 | 資源 | 大批 | 目的 | 等值 |
---|---|---|---|---|---|---|---|---|---|
無效的 | true | false | false | false | false | false | false | false | false |
布林值 | false | $A === $B | false | false | false | false | false | false | false |
整數 | false | false | $A === $B | false | false | false | false | false | false |
漂浮 | false | false | false | $A === $B | false | false | false | false | false |
細繩 | false | false | false | false | $A === $B | false | false | false | false |
資源 | false | false | false | false | false | $A === $B | false | false | false |
大批 | false | false | false | false | false | false | eq($A, $B) | false | false |
目的 | false | false | false | false | false | false | false | $A === $B | false |
等值 | false | false | false | false | false | false | false | false | $A‑>equals($B) |
其中eq()
表示根據上述規則遞歸比較每對對應條目的函數。
此關係還提供了一種覆蓋特定類別的等價邏輯的方法,而無需建立新關係。例如,假設您想要根據值比較DateTime
的實例,但保留其他類型的預設行為。可以透過指定每當DateTime
實例與另一個值進行比較時所使用的自訂關係來完成:
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 ));
比較兩個值的語意相等性。
計劃在未來版本中實現語義等效。它將允許比較語義上相似的值 - 即使它們屬於不同類型。它類似於 PHP 中鬆散比較的工作方式,但在更嚴格的條件下,以這樣的方式保持自反性、對稱性和傳遞性的屬性。
根據日期、時間和時區比較兩個DateTime
實例。
如果DateTime
的兩個實例具有相同的日期、時間和時區,則此關係認為它們是等效的:
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 ));
在 PHP 中,陣列鍵只能表示為數字和字串。然而,在某些情況下,將複雜類型儲存為鍵是有幫助的。以表示不同類型的數字或字串的類別為例,例如 GMP 物件、Unicode 字串等。
為了填補這一空白,該程式庫引入了介面Hashable
和Hasher
,它們指定了為值提供雜湊程式碼的協定。這些介面不需要實現者提供完美的雜湊函數。也就是說,不相等的兩個值可能具有相同的雜湊碼。然而,為了確定具有相同雜湊碼的兩個值實際上是否相等,應該以互補的方式組合雜湊和等價的概念。它解釋了為什麼Hasher
和Hashable
分別擴展了Equivalence
和Equatable
。
警告的話
哈希碼旨在基於雜湊表的集合中進行高效插入和查找以及快速不等式檢查。哈希碼不是永久值。為此原因:
- 不要序列化雜湊碼值或將它們儲存在資料庫中。
- 不要使用雜湊碼作為從帶鍵集合中檢索物件的鍵。
- 不要跨應用程式域或進程發送哈希代碼。在某些情況下,可以在每個進程或每個應用程式域的基礎上計算雜湊碼。
- 如果您需要加密強度較高的雜湊值,請勿使用雜湊碼來取代加密雜湊函數傳回的值。
- 不要透過測試雜湊碼是否相等來確定兩個物件是否相等,一旦不相等的值可能具有相同的雜湊碼。
在某些情況下,可能需要為類別定義自訂哈希邏輯以最適合您的要求。例如,假設您有一個 Point 類別來表示 2D 點:
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 ;
}
}
Point
保存點的 x 和 y 座標。根據類別的定義,如果兩個點具有相同的座標,則認為它們相等。但是,如果您打算將Point
的實例儲存在基於雜湊的對應中,例如,因為您想要將座標與標籤相關聯,那麼您必須確保您的類別產生的雜湊程式碼與用於確定何時兩個點被認為是相等的:
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 ;
}
}
這樣, getHash()
方法就按照equals()
方法工作,儘管哈希演算法可能並不理想。哈希碼產生的有效演算法的實作超出了本指南的範圍。但是,建議使用快速演算法,該演算法對於不相等的值會產生相當不同的結果,並將繁重的比較邏輯轉移到Equatable::equals()
。
請注意,可散列物件應該是不可變的,或者您需要遵守紀律,在基於雜湊的結構中使用它們後不要更改它們。
Hasher 為未實作Hashable
的原始型別和類別物件提供雜湊功能。
此介面引入的方法hash()
旨在提供一種在基於雜湊的資料結構中執行快速不等價檢查以及高效插入和查找的方法。此方法總是與equivalent()
一致,這表示對於任何引用$x
和$y
,如果equivalent($x, $y)
,則hash($x) === hash($y)
。但是,如果equivalence($x, $y)
計算結果為false
, hash($x) === hash($y)
可能仍為 true。這就是為什麼hash()
方法適合不等價檢查,但不適合等價檢查。
該庫中包含的所有Equivalence
實作也提供雜湊功能。有關如何對值進行散列的更多資訊可以在相應實現的文檔中找到。
遵循前面討論的概念的相同邏輯, Comparable
和Comparator
是分別提供自然和自訂排序策略的介面。兩個介面都指定了全序關係,即自反、反對稱和傳遞的關係。
此介面對實現它的每個類別的物件強加了總排序。這種排序稱為類別的自然排序,而方法Comparable::compareTo()
稱為其自然比較方法。
以下範例顯示類別如何定義其實例的自然順序:
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 );
}
}
Comparator
的目的是允許您定義一個或多個不是類別的自然比較策略的比較策略。理想情況下, Comparator
必須由與其定義比較策略的類別不同的類別來實現。如果你想為一個類別定義一個自然的比較策略,你可以實作Comparable
。
比較器可以傳遞給集合的排序方法,以允許精確控制其排序順序。它也可用於控制某些資料結構的順序,例如排序集或排序映射。例如,考慮以下比較器,它根據字串的長度對字串進行排序:
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 ' ));
此實作代表了對字串進行排序的多種可能方法中的一種。其他策略包括按字母順序、字典順序排序等。
請參閱變更日誌以了解最近變更的更多資訊。
$ composer test
查看測試文件以了解更多詳細資訊。
隨時歡迎對包做出貢獻!
有關詳細信息,請參閱貢獻和行為。
如果您發現任何與安全相關的問題,請發送電子郵件至 [email protected],而不是使用問題追蹤器。
該軟體包的所有內容均根據 MIT 許可證獲得許可。