最新リリース: 1.0.0 ベータ版
値のハッシュ化とソートのための等価関係と戦略を表す PHP 5.4+ ライブラリ。
同値関係は、ドメイン固有の要件に関して値を比較するための一般化された方法を確立したり、特にコレクションで使用する場合に、値を境界付きコンテキストに比較するためのカスタム基準を表すのに役立ちます。
ハッシュやソートなどの補完的な機能もこのライブラリでカバーされているため、開発ツールに追加する価値があります。
API はソース コードに詳しく文書化されています。さらに、ブラウザでより便利に閲覧できる HTML バージョンも用意されています。
Composer を使用してパッケージをインストールします。
$ composer require phpcommon/comparison
リレーションは、セットの要素間の関連性を記述するための数学的ツールです。リレーションは、コンピュータ サイエンス、特にデータベースやスケジューリング アプリケーションで広く使用されています。
ほとんどの最新の言語とは異なり、PHP は演算子のオーバーロードをサポートしていません。これは歴史的に設計上の選択肢として避けられてきました。つまり、等しい、同一、より大きい、より小さいなどのネイティブ演算子のデフォルトの動作をオーバーライドすることはできません。たとえば、Java は Comparable インターフェイスを提供し、Python はいくつかのマジック メソッドを提供します。
このような概念の重要性は、以下のセクションで説明するように、等価性や順序付けの概念が比較の対象や文脈に応じて変化する状況でより明らかになります。
数学における同値関係は、再帰的、対称的、推移的な二項関係です。ただし、コンピューティングの分野では、考慮する必要があるもう 1 つのプロパティがあります。それは、一貫性です。一貫性とは、リレーションが同じ入力に対して異なる結果を生成しないことを意味します。
ユビキタス等価関係とは、任意の集合の要素間の等価関係です。その他の例としては次のようなものがあります。
このライブラリの目的では、等価関係は汎用または型固有にすることができます。型固有の関係はEquatable
インターフェイスまたはEquivalence
インターフェイスのいずれかを実装することによって定義されますが、汎用等価性は最後のインターフェイスを実装する必要があります。
Equatable
インターフェイスは、インスタンスの同等性を判断するための型固有のメソッドを作成するためにクラスが実装する一般化されたメソッドを定義します。
説明のために、金銭的価値を表すことを目的としたクラスMoney
を考えます。 Money
は値オブジェクトであるため、このクラスはEquatable
インターフェイスを実装するための良い候補です。つまり、これらのオブジェクトの平等の概念はアイデンティティに基づいていないからです。代わりに、 Money
の 2 つのインスタンスは、同じ値を持つ場合は同等です。したがって、 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 ;
}
}
ただし、2 つの値を比較するための非標準または外部の方法が必要になる場合が多くあります。おそらく、これらのカスタム リレーションの最も明白な使用例はコレクションで使用することですが、サードパーティのパッケージに属しているかビルドされているため、それ自体を提供できないスカラー値や既存のクラスにこれらの機能を提供する場合にも役立ちます。 PHPに。
病院での献血管理を支援するソフトウェアを開発しているとします。要件の 1 つは、看護師が同じ血液型のドナーから血液を採取できないことです。このシナリオの関係は次のようになります。
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 ();
}
}
この関係により、2 人の人物が同じ血液型であるかどうかが決まります。
$ 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
、血液型に基づいて人々間の等価関係を確立します。James がすでに存在しており、血液型が同じであるため、John をコレクションに追加しようとしても無視されます。
最初は単純な要件であるため少し複雑に見えるかもしれませんが、実際のケースでは、ドナーをグループに分けるために、互換性のある血液型間の同等性を比較するために使用できます。
このライブラリは、以下で説明するように、標準ライブラリの一部としていくつかの一般的な等価関係を提供します。
2 つの値を比較して同一性を確認します。
この関係は同一の演算子に基づいています。ほとんどの場合、2 つの値は型と値が同じであれば同等とみなされますが、いくつかの例外があります。
NAN
、それ自体を含め、他のすべての値と等しくありません。次の表は、さまざまなタイプのオペランドを比較する方法をまとめたものです。
$A $B | NULL | ブール値 | 整数 | フロート | 弦 | リソース | 配列 | 物体 |
---|---|---|---|---|---|---|---|---|
NULL | 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 |
2 つの値が等しいかどうかを比較します。
値の等価性は、 Equatable
オブジェクト間の比較を比較対象のオブジェクトに委任することを除いて、同一性の等価性とまったく同じように動作します。さらに、特定のタイプの値を比較するために外部関係を指定できます。これは、特定のタイプのデフォルトの動作をオーバーライドし、その他のタイプはすべて保持することが望ましい場合に便利です。また、サードパーティのパッケージに属するクラスや PHP に組み込まれたクラスのオブジェクトのリレーションを定義する場合にも役立ちます。
次のルールは、2 つの値が同等であると見なされるかどうかを決定するために使用されます。
NAN
、それ自体を含め、他のすべての値と等しくありません。Equatable
のインスタンスであり、式$left->equals($right)
はtrue
と評価されます。$relation->equivalent($left, $right)
はtrue
と評価されます。次の表は、さまざまなタイプのオペランドを比較する方法をまとめたものです。
$A $B | NULL | ブール値 | 整数 | フロート | 弦 | リソース | 配列 | 物体 | 平等な |
---|---|---|---|---|---|---|---|---|---|
NULL | 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 ));
2 つの値を比較して意味が等しいかどうかを確認します。
意味上の同等性は将来のバージョンで計画されています。これにより、型が異なっていても、意味的に類似しているように見える値を比較できるようになります。これは PHP での緩い比較の仕組みに似ていますが、より限定的な条件下で、再帰性、対称性、推移性の特性が維持されるようになります。
日付、時刻、タイムゾーンに基づいて 2 つのDateTime
インスタンスを比較します。
この関係では、 DateTime
の 2 つのインスタンスが同じ日付、時刻、タイム ゾーンを持つ場合、それらのインスタンスは同等であると見なされます。
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
導入しています。これらのインターフェイスでは、実装者が完全なハッシュ関数を提供する必要はありません。つまり、等価ではない 2 つの値が同じハッシュ コードを持つ可能性があります。ただし、同じハッシュ コードを持つ 2 つの値が実際に等しいかどうかを判断するには、ハッシュと等価性の概念を補完的な方法で組み合わせる必要があります。 Hasher
とHashable
それぞれEquivalence
とEquatable
拡張する理由を説明します。
警告の言葉
ハッシュ コードは、ハッシュ テーブルに基づくコレクションへの効率的な挿入と検索、および高速な不平等チェックを目的としています。ハッシュ コードは永続的な値ではありません。このため:
- ハッシュ コード値をシリアル化したり、データベースに保存したりしないでください。
- キー付きコレクションからオブジェクトを取得するためのキーとしてハッシュ コードを使用しないでください。
- アプリケーション ドメインまたはプロセス間でハッシュ コードを送信しないでください。場合によっては、ハッシュ コードはプロセスごとまたはアプリケーション ドメインごとに計算されることがあります。
- 暗号的に強力なハッシュが必要な場合は、暗号ハッシュ関数によって返される値の代わりにハッシュ コードを使用しないでください。
- 等しくない値が同一のハッシュ コードを持つ可能性がある場合は、2 つのオブジェクトが等しいかどうかを判断するためにハッシュ コードの等価性をテストしないでください。
要件に最適なクラスのカスタム ハッシュ ロジックを定義することが望ましい場合があります。たとえば、2D 点を表す Point クラスがあるとします。
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 座標を保持します。クラスの定義によれば、2 つの点が同じ座標を持つ場合、それらは等しいとみなされます。ただし、座標をラベルに関連付けるなどの理由で、 Point
のインスタンスをハッシュ ベースのマップに格納する場合は、クラスが 2 つのタイミングを決定するために使用されるロジックと一貫性のあるハッシュ コードを生成することを確認する必要があります。ポイントは等しいとみなされます:
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
の目的は、クラスの自然な比較戦略ではない 1 つ以上の比較戦略を定義できるようにすることです。理想的には、 Comparator
比較戦略を定義するクラスとは異なるクラスによって実装される必要があります。クラスの自然な比較戦略を定義したい場合は、代わりにComparable
を実装できます。
コンパレータをコレクションのsortメソッドに渡すことで、ソート順序を正確に制御できます。また、ソートされたセットやソートされたマップなど、特定のデータ構造の順序を制御するために使用することもできます。たとえば、文字列を長さに応じて並べ替える次のコンパレータについて考えてみましょう。
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 ' ));
この実装は、文字列をソートする多くの可能な方法のうちの 1 つを表します。他の戦略には、アルファベット順、辞書順などの並べ替えが含まれます。
最近の変更点の詳細については、CHANGELOG を参照してください。
$ composer test
詳細については、テスト ドキュメントを参照してください。
パッケージへの貢献はいつでも大歓迎です!
詳細については、「貢献と実施」を参照してください。
セキュリティ関連の問題を発見した場合は、問題トラッカーを使用する代わりに、[email protected] に電子メールを送信してください。
このパッケージのすべての内容は、MIT ライセンスに基づいてライセンスされています。