Equals и GetHashCode для TDictionary<TVehicle, TPerson>
Если я реализую отношение Car <-> Owner в Delphi с использованием TDictionary, как мне следует реализовать функцию Equals и GetHashCode в IEqualityComparer? (GetHashCode возвращает целое число, которое используется для хеширования в TDictionary.)
Для класса TVehicle предположим, что он имеет VIN (идентификационный номер транспортного средства).
Как мне реализовать хеш-код для VIN?
Обновление: в этом примере идентичность объекта означает не "идентичность областей памяти двух указателей объекта", а "идентичность двух экземпляров одного и того же объекта на основе уникальной и неизменной ("неизменяемой") комбинации его свойств".,
Таким образом, вместо того, чтобы искать автомобиль по его адресу в памяти на карте, мне нужен автомобиль с идентификатором, который я ищу.
Подумайте о базе данных, которая содержит данные о владельце автомобиля, загруженные в словарь при запуске приложения. Теперь, если пользователь вводит VIN в форму заявки, как приложение может найти транспортное средство в словаре? Если код создает новый экземпляр, используя VehicleFactory.CreateVehicleFromDatabase(Edit1.Text);
и поиск этого объекта в словаре, реализация Equals по умолчанию не найдет никаких записей на карте, потому что ищет адрес памяти. Чтобы найти транспортное средство, Равным необходимо сравнить VIN.
Поэтому я должен создать собственный IEqualityComparer. Реализация Равных тривиальна. Но как насчет GetHashCode? Для строкового свойства я не могу просто использовать адрес строки (см. Берри Келли в " Являются ли строки Delphi неизменяемыми?": "Если вы создадите одну и ту же строку из двух отдельных разделов кода, они не будут использовать одно и то же хранилище") поэтому функция GetHashCode для строкового свойства требует индивидуальной реализации.
Я также обнаружил, что нашел вопрос Как мне хэшировать строку с Delphi? - есть пример, который содержит HashValue('Hello World')
3 ответа
Вы, похоже, ошиблись, полагая, что строки Delphi не содержат реализацию хеш-кода по умолчанию.
Это не вариант. Когда вы создаете TDictionary
со строковым значением в качестве ключа хеш рассчитывается на основе содержимого строки. Если Value
является строковой переменной, тогда код выглядит так:
BobJenkinsHash(Value[1], Length(Value) * SizeOf(Value[1]), 0);
Я думаю, что это отвечает на часть вашего вопроса о хешировании строк.
Комментарии к другим ответам, и те, которые я удалил, были интересным обсуждением проблемы дизайна, которую вы рассматриваете. Я по-прежнему скептически отношусь к вашей вере в то, что правильное решение состоит в том, чтобы разрешить отношения "многие к одному" между экземплярами TVehicle и VIN.
Вы подтвердили, что у вас не должно быть нескольких экземпляров TVehicle с одним и тем же VIN, но разными данными. Мне кажется, что лучший способ добиться этого - это убедиться, что у вас есть личные отношения между экземплярами TVehicle и VIN.
Эти личные отношения довольно легко достижимы. Вы должны сделать создание экземпляров TVehicle функцией, частной для фабричного класса. Этот фабричный класс содержит словарь, содержащий существующие экземпляры транспортных средств, TDictionary<string,TVehicle>
, Если вам нужно завладеть транспортным средством, вы просите об этом завод. Он возвращает либо существующий, который был расположен в его словаре, либо синтезирует новый.
Несомненно, существует ряд других способов достижения этого эффекта, но я настоятельно призываю вас рассмотреть подход, который приводит только к одному экземпляру транспортного средства на VIN.
Я бы бросил принцип KISS на этот, если это возможно. Если ваш фактический ключ - это идентификационный номер, а не само транспортное средство, то почему бы не использовать TDictionary<string, TPerson>
вместо TDictionary<TVehicle, TPerson>
? Тогда вам не придется беспокоиться о пользовательских компараторах.
Получив информацию о запахе в вашем дизайне и других вещах, я отвечу на ваш вопрос, так как действительно создать словарь с ключами объекта и сравнить его на основе чего-либо, отличного от адреса памяти ключа:
Вы можете создать новый компаратор во время создания 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", то для меня это должен быть единственный экземпляр этого транспортного средства, и вы должны предоставить какой-то способ получить этот же экземпляр для всего своего кода. Таким образом, вы можете использовать класс Dictionary без написания пользовательского компаратора, но, что более важно, вы знаете, что все время работаете с одним и тем же объектом, и состояние вашего приложения будет и будет соответствовать чему-либо в коде, пользовательском интерфейсе или других интерфейсах,