最新版本:1.0.0-beta
PHP 5.4+ 库,用于表示等价关系以及散列和排序值的策略。
等价关系可用于建立一种通用方法来比较特定于域的要求的值,以及表示将值与有界上下文进行比较的自定义标准,特别是在集合中使用。
该库还涵盖了散列和排序等补充功能,使其成为您的开发工具带的宝贵补充。
该 API 在源代码中进行了详细记录。此外,还提供 HTML 版本,以便更方便地在浏览器中查看。
使用 Composer 安装包:
$ composer require phpcommon/comparison
关系是描述集合元素之间关联的数学工具。关系广泛应用于计算机科学,特别是数据库和调度应用程序。
与大多数现代语言不同,PHP 不支持运算符重载,这在历史上是避免作为设计选择的。换句话说,无法覆盖原生运算符的默认行为,例如等于、相同、大于、小于等。例如,Java 提供了 Comparable 接口,而 Python 则提供了一些魔术方法。
在等价或排序的概念根据比较主题或上下文而变化的情况下,这种概念的重要性变得更加明显,如以下各节所述。
在数学中,等价关系是一种自反、对称和传递的二元关系。然而,在计算领域,还有一个必须考虑的属性:一致性。一致性意味着关系对于相同的输入不应该产生不同的结果。
普遍存在的等价关系是任何集合的元素之间的相等关系。其他示例包括:
就该库而言,等价关系可以是通用的,也可以是特定于类型的。特定于类型的关系是通过实现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 许可证获得许可。