최신 릴리스: 1.0.0-베타
값을 해싱하고 정렬하는 등가 관계와 전략을 나타내는 PHP 5.4+ 라이브러리입니다.
등가 관계는 도메인별 요구 사항과 관련하여 값을 비교하는 일반화된 방법을 설정하는 데 유용할 뿐만 아니라 특히 컬렉션에서 사용하기 위해 값을 제한된 컨텍스트와 비교하기 위한 사용자 지정 기준을 나타내는 데 유용합니다.
해싱 및 정렬과 같은 보완 기능도 이 라이브러리에서 다루므로 개발 도구 벨트에 귀중한 추가 기능이 됩니다.
API는 소스 코드에 광범위하게 문서화되어 있습니다. 또한 브라우저에서 보다 편리하게 볼 수 있도록 HTML 버전도 제공됩니다.
Composer를 사용하여 패키지를 설치합니다.
$ composer require phpcommon/comparison
관계는 집합의 요소 간의 연관성을 설명하기 위한 수학적 도구입니다. 관계는 컴퓨터 과학, 특히 데이터베이스 및 일정 관리 응용 프로그램에서 널리 사용됩니다.
대부분의 현대 언어와 달리 PHP는 역사적으로 설계 선택으로 피했던 연산자 오버로딩을 지원하지 않습니다. 즉, 같음, 동일함, 보다 큼, 보다 작음 등과 같은 기본 연산자의 기본 동작을 재정의하는 것은 불가능합니다. 예를 들어 Java는 Comparable 인터페이스를 제공하는 반면 Python은 몇 가지 매직 메서드를 제공합니다.
이러한 개념의 중요성은 다음 섹션에서 설명하는 것처럼 동등성 또는 순서 지정의 개념이 비교 대상이나 문맥에 따라 달라지는 상황에서 더욱 분명해집니다.
수학에서 등가 관계는 반사적 , 대칭적 , 추이적인 이항 관계입니다. 그러나 컴퓨팅 분야에서는 고려해야 할 또 다른 속성이 있습니다. 바로 일관성입니다 . 일관성이란 관계가 동일한 입력에 대해 다른 결과를 생성해서는 안 된다는 것을 의미합니다.
유비쿼터스 동등 관계는 모든 집합의 요소 간의 동등 관계입니다. 다른 예는 다음과 같습니다.
이 라이브러리의 목적에 따라 동등 관계는 일반적이거나 유형별일 수 있습니다. 유형별 관계는 Equatable
또는 Equivalence
인터페이스를 구현하여 정의되는 반면 일반 동등성은 마지막 인터페이스를 구현해야 합니다.
Equatable
인터페이스는 클래스가 인스턴스의 동등성을 결정하기 위한 유형별 메서드를 생성하기 위해 구현하는 일반화된 메서드를 정의합니다.
설명을 위해 금전적 가치를 표현하는 것을 목표로 하는 Money
클래스를 고려해보세요. 이 클래스는 Equatable
인터페이스를 구현하기 위한 좋은 후보입니다. 왜냐하면 Money
는 값 객체이기 때문입니다. 즉, 해당 객체의 동등성 개념은 ID를 기반으로 하지 않습니다. 대신, 동일한 값을 갖는 두 개의 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가 이미 존재하고 혈액형이 동일하기 때문입니다.
처음에는 간단한 요구 사항으로 인해 다소 복잡해 보일 수 있지만 실제 사례에서는 기증자를 그룹으로 나누기 위해 호환 가능한 혈액형 간의 동등성을 비교하는 데 사용할 수 있습니다.
이 라이브러리는 아래 설명된 대로 표준 라이브러리의 일부로 몇 가지 일반적인 등가 관계를 제공합니다.
ID에 대한 두 값을 비교합니다.
이 관계는 동일한 연산자를 기반으로 합니다. 대부분의 경우 두 값의 유형과 값이 동일하면 동일한 것으로 간주되지만 몇 가지 예외가 있습니다.
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 |
두 값이 같은지 비교합니다.
값 동등성은 Equatable
개체 간의 비교를 비교 대상 개체에 위임한다는 점을 제외하면 동일성 동등성과 동일하게 작동합니다. 또한 특정 유형의 값을 비교하기 위해 외부 관계를 지정할 수 있습니다. 특정 유형의 기본 동작을 재정의하고 다른 모든 동작을 유지하는 것이 바람직한 경우에 유용합니다. 또한 타사 패키지에 속하거나 PHP에 내장된 클래스 개체에 대한 관계를 정의하는 데 유용합니다.
두 값이 동일한 것으로 간주되는지 여부를 결정하는 데 다음 규칙이 사용됩니다.
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 ));
의미론적 동등성을 위해 두 값을 비교합니다.
향후 버전에서는 의미론적 동등성이 계획되어 있습니다. 유형이 다르더라도 의미상 유사해 보이는 값을 비교할 수 있습니다. 이는 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 개체, 유니코드 문자열 등과 같은 다양한 종류의 숫자나 문자열을 나타내는 클래스를 예로 들어 보겠습니다. 이러한 개체를 배열 키로 사용할 수도 있으면 편리할 것입니다.
이러한 격차를 메우기 위해 이 라이브러리는 값에 대한 해시 코드를 제공하기 위한 프로토콜을 지정하는 Hashable
및 Hasher
인터페이스를 도입합니다. 이러한 인터페이스에서는 구현자가 완벽한 해싱 기능을 제공할 필요가 없습니다. 즉, 동일하지 않은 두 값이 동일한 해시 코드를 가질 수 있습니다. 그러나 동일한 해시 코드를 가진 두 값이 실제로 동일한지 여부를 확인하려면 해싱과 동등성의 개념을 상호보완적으로 결합해야 합니다. Hasher
와 Hashable
각각 Equivalence
와 Equatable
확장하는 이유를 설명합니다.
경고의 말씀
해시 코드는 해시 테이블을 기반으로 하는 컬렉션에서 효율적인 삽입 및 조회와 빠른 불평등 검사를 위해 만들어졌습니다. 해시 코드는 영구적인 값이 아닙니다. 이러한 이유로:
- 해시 코드 값을 직렬화하거나 데이터베이스에 저장하지 마십시오.
- 키가 지정된 컬렉션에서 객체를 검색하기 위해 해시 코드를 키로 사용하지 마십시오.
- 애플리케이션 도메인이나 프로세스 전체에 해시 코드를 보내지 마세요. 어떤 경우에는 해시 코드가 프로세스별 또는 애플리케이션 도메인별로 계산될 수 있습니다.
- 강력한 암호화 해시가 필요한 경우 암호화 해싱 함수에서 반환된 값 대신 해시 코드를 사용하지 마세요.
- 서로 다른 값이 동일한 해시 코드를 가질 수 있으면 두 개체가 동일한지 확인하기 위해 해시 코드의 동일성을 테스트하지 마세요.
요구 사항에 가장 적합하도록 클래스에 대한 사용자 지정 해싱 논리를 정의하는 것이 바람직한 경우가 있습니다. 예를 들어 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 좌표를 보유합니다. 클래스 정의에 따르면 두 점의 좌표가 동일하면 동일한 것으로 간주됩니다. 그러나 해시 기반 지도에 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 ' ));
이 구현은 문자열을 정렬하는 여러 가지 가능한 방법 중 하나를 나타냅니다. 다른 전략에는 알파벳순, 사전순 등의 정렬이 포함됩니다.
최근 변경된 사항에 대한 자세한 내용은 CHANGELOG를 참조하세요.
$ composer test
자세한 내용은 테스트 문서를 확인하세요.
패키지에 대한 기여는 언제나 환영합니다!
자세한 내용은 기여 및 실행을 참조하세요.
보안 관련 문제를 발견한 경우 문제 추적기를 사용하는 대신 [email protected]으로 이메일을 보내주세요.
이 패키지의 모든 콘텐츠는 MIT 라이선스에 따라 라이선스가 부여됩니다.