Последняя версия: 1.0.0-бета.
Библиотека 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
устанавливает отношение эквивалентности между людьми на основе их группы крови, любая попытка добавить Джона в коллекцию будет игнорироваться, поскольку Джеймс уже присутствует и они принадлежат к одной группе крови.
На первый взгляд это может показаться немного сложным для простого требования, но в реальных случаях его можно использовать для сравнения эквивалентности совместимых групп крови, чтобы разделить доноров на группы.
Эта библиотека предоставляет некоторые общие отношения эквивалентности как часть стандартной библиотеки, как описано ниже.
Сравнивает два значения для идентификации.
Это отношение основано на одном и том же операторе. В большинстве случаев два значения считаются эквивалентными, если они имеют одинаковый тип и значение, но есть несколько исключений:
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()
.
Обратите внимание, что хешируемые объекты должны быть либо неизменяемыми, либо вам необходимо проявлять дисциплину и не изменять их после того, как они были использованы в хеш-структурах.
Хэшер обеспечивает функциональность хеширования для примитивных типов и объектов классов, которые не реализуют Hashable
.
Метод hash()
представленный в этом интерфейсе, предназначен для предоставления средств для выполнения быстрых проверок на неэквивалентность , а также эффективной вставки и поиска в структурах данных на основе хеша. Этот метод всегда связан с equivalent()
, что означает, что для любых ссылок $x
и $y
, если equivalent($x, $y)
, то hash($x) === hash($y)
. Однако если equivalence($x, $y)
оценивается как false
, hash($x) === hash($y)
все равно может быть истинным. Вот почему метод 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 ' ));
Эта реализация представляет собой один из многих возможных способов сортировки строк. Другие стратегии включают сортировку по алфавиту, лексикографически и т. д.
Пожалуйста, посетите CHANGELOG для получения дополнительной информации о том, что изменилось за последнее время.
$ composer test
Для получения более подробной информации ознакомьтесь с тестовой документацией.
Вклады в пакет всегда приветствуются!
Подробную информацию см. в разделах ВКЛАД и ПОВЕДЕНИЕ.
Если вы обнаружите какие-либо проблемы, связанные с безопасностью, отправьте электронное письмо по адресу [email protected] вместо использования системы отслеживания проблем.
Все содержимое этого пакета лицензируется по лицензии MIT.