2011-03-15 2 views
2

TDictionary를 사용하여 Delphi에서 Car < -> Owner를 구현하면 IEqualityComparer의 Equals 및 GetHashCode 함수를 어떻게 구현해야합니까? (GetHashCode는 TDictionary의 해시에 사용되는 정수를 반환합니다.)TDictionary에 대한 Equals 및 GetHashCode <TVehicle, TPerson>

TVehicle 클래스의 경우 VIN (차량 식별 번호)이 있다고 가정합니다.

VIN의 해시 코드는 어떻게 구현해야합니까?

업데이트 :이 예에서, 객체 ID는 고유하고 불변 ("이 불변에 따라 '두 개의 객체 포인터의 메모리 위치의 정체성',하지만 '같은 개체의 두 인스턴스의 정체성을 의미하지 않는다 ") 속성의 조합.

지도에서 차량의 메모리 주소로 차량을 검색하는 대신 찾고있는 차량이 필요합니다.

응용 프로그램 시작시 사전에로드 된 차량 소유자 데이터가 포함 된 데이터베이스를 생각해보십시오. 이제 사용자가 애플리케이션 양식에 VIN을 입력하면 애플리케이션이 사전에서 차량을 어떻게 찾을 수 있습니까? 코드가 VehicleFactory.CreateVehicleFromDatabase(Edit1.Text);을 사용하여 새 인스턴스를 만들고 사전에서이 개체를 검색하면 메모리 주소를 찾기 때문에 Equals의 기본 구현은 맵에서 항목을 찾지 않습니다. 차량을 찾으려면 Equals는 VIN을 비교해야합니다.

그래서 사용자 지정 IEqualityComparer를 만들어야합니다. 같음을 구현하는 것은 간단합니다. 그러나 GetHashCode는 어떻습니까? 문자열 속성의 경우 단순히 문자열의 주소를 사용할 수 없습니다 (Berry Kelly Are Delphi strings immutable? : "두 개의 별도 코드 섹션에서 동일한 문자열을 만드는 경우 동일한 백킹 저장소를 공유하지 않습니다"). 따라서 GetHashCode 문자열 속성에 대한 함수는 사용자 정의 된 구현이 필요합니다.

나는 또한 질문 How do I hash a string with Delphi?을 발견 발견 - 가능하면 내가이 일에 KISS 원칙을 던질 거라고 HashValue('Hello World')

+0

문자열의 기본 비교자는 주소를 비교하지 않습니다. –

+0

퍼즐에 누락 된 조각은 문자열에 대한 GetHashCode입니다. Delphi 2009 및 이후 버전에서는 모든 객체에 GetHashCode 메소드가 있습니다. 문자열은 객체가 아니므로 최신 버전의 Delphi에는 시스템 함수가 있어야합니다. – mjn

+0

문자열에는 해시 코드가 있지만 문자열의 주소가 아니라 내용에 기반합니다. Delphi에서 문자열을 해시 할 수 없다는 아이디어는 어디서 얻었습니까? –

답변

2

델파이 문자열에 기본 해시 코드 구현이 제공되지 않는다고 믿어 의심치 않습니다.

그렇지 않습니다. 문자열 값을 키로 사용하여 TDictionary을 만들면 해시는 문자열의 내용을 기반으로 계산됩니다. Value이 문자열 변수 인 경우, 코드는 다음과 같습니다

BobJenkinsHash(Value[1], Length(Value) * SizeOf(Value[1]), 0); 

나는이 문자열의 해시에 관한 질문의 일부를 응답 생각합니다.


다른 답변에 대한 의견과 내가 삭제 한 내용은 고려중인 디자인 문제에 대한 흥미로운 토론이었습니다. 나는 올바른 해결책이 TVehicle 인스턴스와 VIN 사이에 다 대일 관계를 허용하는 것이라는 당신의 믿음에 회의적입니다.

VIN이 같지만 데이터가 다른 여러 TVehicle 인스턴스가 없어야 함을 확인했습니다. 이를 달성하는 가장 좋은 방법은 TVehicle 인스턴스와 VIN간에 일대일 관계를 유지하는 것입니다.

이 일대일 관계는 매우 쉽게 달성 할 수 있습니다. TVehicle 인스턴스의 인스턴스 생성을 팩토리 클래스 전용 함수로 만들 필요가 있습니다. 이 팩토리 클래스는 기존 차량 인스턴스 인 TDictionary<string,TVehicle>을 포함하는 사전을 보유합니다. 차량을 소유해야 할 경우 공장에 요청하십시오. 사전에있는 기존의 것을 반환하거나 새로운 것을 합성합니다.

이 효과를 얻기위한 여러 가지 다른 방법은 의심의 여지가 없지만, VIN 당 하나의 차량 인스턴스만을 발생시키는 방법을 고려해 보길 강력히 권합니다.

+0

UnicodeString의 경우 이것은 Generics.Defaults.pas의 GetHashCode_UString 함수입니다 (필자는 컴파일러 마술로 구현 될까 두렵습니다.). 이것은 누락 된 부분입니다, 감사합니다! – mjn

3

를 포함하는 예제가있다. 실제 키가 차량 자체가 아닌 ID 인 경우 TDictionary<TVehicle, TPerson> 대신 TDictionary<string, TPerson>을 사용해야하는 이유는 무엇입니까? 그렇다면 맞춤 비교 자에 대해 걱정할 필요가 없습니다.

+1

+1 정확하게! 실제 키를 키로 사용하는 것이 가장 좋습니다. –

+0

그러면 두 개의 사전도 필요할 것입니다. 하나는 차량용입니다. 이것은 두 개의 사전을 동기화해야한다는 것을 의미합니다. 그리고 TDictionary 은 더 자체적으로 문서화하고 있습니다. – mjn

+0

@mjn VIN을 알게되면 TVehicle을 종합 할 수 있으므로 TVehicle 인스턴스를 유지해야하는 이유는 무엇입니까? 메이슨 말이 맞아. –

3

디자인 및 기타 사항에 대한 냄새에 대해 조언을 받았기 때문에 객체 키 사전을 만들고 키의 메모리 주소와 다른 값을 기준으로 비교할 수 있으므로 질문에 답변 해 드리겠습니다.

TDictionary 생성시 새 비교자를 만들 수 있습니다. 예를 들어

는 :

type 
    TVehicleOwner = class (TDictionary<TVehicle, TOwner>) 
    end; 

//other code here 

procedure TForm2.Button1Click(Sender: TObject); 
var 
    VehOwner: TVehOwner; 
begin 
    VehOwner := TVehOwner.Create(TEqualityComparer<TVehicle>.Construct(
    //comparer 
    function(const Left, Right: TVehicle): Boolean 
    begin 
     { Make a case insensitive comparison } 
     Result := CompareText(Left.FID, Right.FID) = 0; 
    end, 
    //hasher 
    function(const Value: TVehicle): Integer 
    begin 
     { Generate a hash code. } 
     Result := TheHashAlgorythmOfYourChoice(Value.FID); 
    end) 
); 

    //more code here 

이것은 당신이 동일한 개체를 나타내는 두 개의 인스턴스가있는 경우는 코드의 결함이라고 생각했다. 나에게있어 'ABC'라는 ID를 가진 TVehicle이 있다면,이 차량의 유일한 인스턴스 여야하며 모든 코드에 대해이 동일한 인스턴스를 가져올 수있는 방법을 제공해야합니다. 그런 식으로 사용자 정의 비교자를 작성하지 않고 Dictionary 클래스를 사용할 수 있지만 더 중요한 것은 동일한 객체로 항상 작업하고 있고 응용 프로그램 상태가 코드, UI 또는 다른 인터페이스의 어떤 것과도 일치한다는 것입니다. .

+2

개체의 수정이 불가능한 특정 영역에서 이러한 종류의 개체를 갖는 것은 꽤 정상입니다 - 데이터 전송 이전에는 값 개체로 알려진 개체 (DTO)입니다 (http://en.wikipedia.org/wiki/Data_transfer_object 참조). – mjn

+0

@mjn, 감사합니다. DTO를 알고 있습니다 ...귀하의 접근 방식은 우리가 이것에 대해 이야기하는 것 같지 않습니다. 어쨌든 +1 독자 여러분의 설명에 대한 설명으로 미래의 독자를위한 설명으로 – jachguate

관련 문제