2009-08-17 2 views
3

Delphi 2009 добавила функцию GetHashCode в TObject. GetHashCode возвращает целое число, которое используется для хэширования в TDictionary.Преобразование двоичного кода в целое число для GetHashCode в Delphi

Если вы хотите, чтобы объект работал хорошо в TDictionary, вам необходимо соответствующим образом переопределить GetHashCode таким образом, чтобы в целом разные объекты возвращали разные целые хэш-коды.

Но что вы делаете для объектов, содержащих двойные поля? Как вы превращаете эти двойные значения в целые числа для GetHashCode?

Как обычно это делается на Java, скажем, использовать метод Double.doubleToLongBits или Float.floatToIntBits. Последняя имеет документацию, которая описывает ее следующим образом: «Возвращает представление указанного значения с плавающей запятой в соответствии с форматом бит« одиночный формат »с плавающей точкой IEEE 754». Это связано с некоторыми побитовыми операциями с разными масками для разных бит значения с плавающей запятой.

Есть ли функция, которая делает это в Delphi?

+0

Зачем это необходимо изменить? По умолчанию GetHashCode возвращает адрес памяти объекта, который уникален для каждого объекта по определению. –

+1

Я думаю, вам нужно переопределить GetHashCode, если вы переопределяете Equals, если вы хотите, чтобы объекты работали как ключи в словаре. Иногда вы хотите переопределить Equals, чтобы сравнить поля объекта, чтобы проверить, равны ли два объекта, а не просто тестирование, чтобы увидеть, являются ли они одним и тем же экземпляром. –

ответ

5

Я хотел бы предложить следующее усовершенствование по сравнению с кодом Gamecat:

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1, FInt2 : Integer;) 
     1: (FDouble : Double;) 
    end; 

function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt1 xor arec.FInt2; 
end; 

Это учитывает все биты Дабл значение.

(комментарии не работают хорошо с кодом)

+2

Отлично - кажется, отлично работает, спасибо :) –

+0

Спасибо за улучшение ;-). –

2

Если вы хотите отобразить двойной в целое число, вы можете использовать вариант запись:

type 
    TVarRec = record 
    case Integer of 
     0: (FInt : Integer;) 
     1: (FDouble : Double;) 
    end; 


function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt; 
end; 

Берегись, что это делает побитовую копию без интерпретации значений.

Другой (вид подвоха, использует абсолютные переменные:

function Convert(const ADouble: Double): Integer; 
var 
    tempDouble : Double; 
    tempInt : Integer absolute tempDouble; // tempInt is at the same memory position as tempDouble. 
begin 
    tempDouble := ADouble; 
    Result := tempInt; 
end; 
+0

Спасибо Gamecat. Эти методы, похоже, хорошо работают для некоторых двойных чисел, но вы получаете много двойных чисел, которые дают одинаковые цифры. Например, кажется, что все целые числа дают целое значение нуля. Возможно ли это улучшить, чтобы уменьшить вероятность того, что хэш-коды идентичны? Или это только потому, что я тестирую регулярные шаблоны чисел? –

+0

Это не сработает, потому что sizeof (double) = 8, тогда как sizeof (integer) = 4. Вы могли бы сопоставить два целых числа на двойном, если хотите, хотя ... –

0

Нет необходимости делать что-то подобное, потому что значение по умолчанию GetHashCode уже возвращает число, которое гарантировано будет уникальным для каждого объекта: адрес памяти объекта. Кроме того, значение хэша по умолчанию не изменится, если вы измените данные, которые содержит ваш объект.

Предположим, у вас есть объект, содержащий Double со значением 3.5, и вы его используете и помещаете в словарь, и получаете хеш-код 12345678. У вас также есть что-то еще, содержащее ссылку на него , и это двойное поле изменяется, и теперь оно имеет значение 5,21. В следующий раз, когда вы попытаетесь вычислить его значение хеша, ваш хеш-код теперь будет 23456789, и ваш поиск не удастся.

Если вы не можете гарантировать, что это никогда не произойдет, и у вас есть действительно веская причина не использовать адрес памяти, лучше всего оставить GetHashCode таким, какой он есть. (Если он не сломался, не исправить.)

+0

Действительно, я думаю, что это вызовет проблемы, если вы использовали изменяемые объекты в качестве ключей в хэш-таблице, а затем изменили их, когда они были в хэш-таблице. Но я думаю, вам нужно переопределить GetHashCode, если вы переопределите Equals. Так оно и работает в Java. Есть ли что-то другое в Delphi в этом отношении (я не все так понял, когда дело доходит до Delphi)? Насколько я понимаю, hashtables (и предположительно TDictionary) обычно полагаются как на GetHashCode, так и на Equals, чтобы найти определенный элемент. –

+0

Он полагается на оба из них, но полагается на них самостоятельно. Нет необходимости менять его, когда вы меняете другой. См. TDictionary .ContainsValue и TDictionary .GetBucketIndex для получения подробных сведений о том, как используются значения. –

+0

Хм, да, я думаю, что ты там. GetBucketIndex использует FComparer, внутренний для TDictionary, но по умолчанию это похоже на то, что сравнение идентичности. Итак, в то время как в Java «равные объекты должны иметь одинаковые хэш-коды» - это правило, которое означает, что вам нужно переопределить hashCode много, похоже, что это не так в Delphi ... Это хорошо :) –

0

Я думаю, Java, что может быть реализовано в Delphi, как это:

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1: Integer;) 
     1: (FSingle: Single;) 
    end; 

function GetHashCode(Value: Double): Integer; 
var 
    arec: TVarRec; 
begin 
    arec.FSingle := Value; 
    Result := arec.FInt1; 
end; 

Идея заключается в снижении точности Double значение, соответствующее бинарному размеру целого (Sizeof (Single) = Sizeof (Integer)). Если ваши значения могут быть представлены в Одиночной точности без столкновения, это даст хорошее хэш-значение.

Редактировать: Поскольку типный тип не будет компилироваться в моем D2009, я адаптировал решение для записи вариантов.

+0

Неплохая идея, но разве это не вызовет проблемы, если значение больше максимального размера, которое позволяет Single? Также, если у вас много парных чисел, которые округляют до того же значения Integer, вы получите много дублированных хэш-кодов. –

+0

Ну, я не знаю ваших ценностей, но MaxSingle = 3.4e + 38, что в значительной степени. Вероятность столкновений не так высока, потому что значения не округлены до целых чисел, а отлиты от целых. Однократное преобразование в Integer имеет одно и то же представление бит, но значение Integer не имеет никакого значения. –

0

Используйте CRC32 по двойным данным, потому что xor is evil.

program Project1; 

{$APPTYPE CONSOLE} 

uses 
    SysUtils; 

type 
    TVarRec = record 
    case Integer of 
     0: (FInt1, FInt2 : Integer;); 
     1: (FDouble : Double;); 
    end; 

function Convert(const ADouble: Double): Integer; 
var 
    arec : TVarRec; 
begin 
    arec.FDouble := ADouble; 
    Result := arec.FInt1 xor arec.FInt2; 
end; 

var 
    FDoubleVar1, FDoubleVar2: TVarRec; 
    HashCode1, HashCode2: Integer; 
begin 
    // Make a Double 
    FDoubleVar1.FInt1 := $DEADC0DE; 
    FDoubleVar1.FInt2 := $0C0DEF00; 

    // Make another Double 
    FDoubleVar2.FInt1 := $0C0DEF00; 
    FDoubleVar2.FInt2 := $DEADC0DE; 

    WriteLn('1rst Double : ', FDoubleVar1.FDouble); 
    WriteLn('2nd Double : ', FDoubleVar2.FDouble); 

    HashCode1 := Convert(FDoubleVar1.FDouble); 
    HashCode2 := Convert(FDoubleVar2.FDouble); 

    WriteLn('1rst HashCode : ', HashCode1); 
    WriteLn('2nd HashCode : ', HashCode2); 

    if HashCode1 = HashCode2 then 
    begin 
    WriteLn('Warning: Same HashCode!'); 
    end; 
    ReadLn; 
end. 
+0

Любые рассуждения здесь? – jpfollenius

+0

Я добавил пример выше, где разные парные разряды дают один и тот же хэш-код из-за xor. – pani

+1

Таблицы хэшей всегда будут приводить к столкновениям, особенно для удвоений - гораздо больший набор, чем набор всех возможных значений хэш-функции. – jpfollenius