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 без написания пользовательского компаратора, но, что более важно, вы знаете, что все время работаете с одним и тем же объектом, и состояние вашего приложения будет и будет соответствовать чему-либо в коде, пользовательском интерфейсе или других интерфейсах,

Другие вопросы по тегам