【问题标题】:TDictionary Hashing is broken for records of strings字符串记录的 TDictionary Hashing 被破坏
【发布时间】:2014-11-27 10:45:30
【问题描述】:

这重现了问题:

program Project1;

{$APPTYPE CONSOLE}

uses
  Generics.Collections;
type
  TStringRec = record
    s1 : string;
    s2 : string;
  end;
  TGetHash<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>)
    public
    type
      TItem = record
        HashCode: Integer;
        Key: TKey;
        Value: TValue;
      end;
      TItemArray = array of TItem;
    public
    FItems: TItemArray;
  end;
var
  LCrossRef : TDictionary<TStringRec, integer>;
  LRec : TStringRec;
  i : integer;
begin
  LCrossRef := TDictionary<TStringRec, integer>.Create();
  LRec.s1 := 'test1';
  LRec.s2 := 'test2';
  LCrossRef.Add(LRec, 1);
  LRec.s1 := 'test1';
  LRec.s2 := 'test2';
  if LCrossRef.TryGetValue(LRec, i) then begin
    writeln('ok');
  end else begin
    LCrossRef.Add(LRec, 1);
    for i := Low(TGetHash<TStringRec, integer>
                (LCrossRef).FItems)
          to High(TGetHash<TStringRec, integer>
                (LCrossRef).FItems) do
      WriteLn(TGetHash<TStringRec, integer>(LCrossRef).FItems[i].HashCode);
    WriteLn('not ok');
  end;
  ReadLn;
end.

字典无法检索项目并为包含相同字符串的记录生成不同的HashCode

这在QC-#122791 中有部分说明,但使用打包记录的解决方法不适用于字符串记录(至少当TStringRec 声明为packed record 时,上述示例也会失败)。

是否有合理的解决方法?

我目前的策略是将原本会进入记录的字符串连接起来,并改用TDictionary&lt;string, TValue&gt;,但这自然不能令人满意。

【问题讨论】:

  • 如何将 TObjectDictionary 与为您的特定类型实现 GetHashCode 的自定义 IEqualityComparer 一起使用?
  • @VilleKrumlinde 你不是说TObjectDictionary。这里没有对象所有权。
  • 实现IEqualityComparer 绝对是一条路。
  • 这个答案可能有用并为您节省一些代码:stackoverflow.com/questions/27820171/…

标签: delphi generics dictionary hash delphi-xe2


【解决方案1】:

这是一个已知的限制,这是设计使然。记录的默认比较器和散列器仅适用于纯值类型记录以及没有填充的记录。

设计者本可以选择使用 RTTI 来比较/散列记录。然而,他们选择不这样做。这种选择的一些明显合理的原因是:

  1. 他们不希望强迫不情愿的人使用 RTTI。
  2. 使用 RTTI 会严重影响性能。

处理这个问题的方法是在使用泛型集合时提供您自己的比较器和哈希器。

您当前连接字符串的策略不起作用。考虑'a''aa',然后是'aa''a'。要使用基于文本的方法,您需要将记录序列化为 JSON。

【讨论】:

  • 我同意这对于一般的引用类型是有意义的。但是,作为特殊的和编译器管理的字符串,我希望至少默认情况下它们会以一种合理的方式被散列(这似乎并不特别或不可能)。它是自定义比较器和哈希器。同意串联是一种垃圾策略 - 在我的情况下,它是一种 hack,根据特定问题的性质,几乎可以保证不会以您所指出的方式发生冲突。在我确定一个更好的策略之前,这是一个临时的解决方法。
  • 这将如何实现?系统必须能够生成基于 RTTI 的比较器。我判断这不是微不足道的。
  • 也许吧。诚然,我没有认真考虑过。无论如何,这一点是没有意义的。这需要另一种解决方案。
  • 我的问题的答案在这里会很有用:stackoverflow.com/questions/11294686/… 这个答案比我一个人的答案值得更多的支持。
  • 当我刚刚读到那个答案时,我真的很困惑,因为我无法理解 mjn 是如何引导我的编码风格的!但事实证明,我在之前的编辑中添加了代码。我的代码库中的版本有这样的注释:见stackoverflow.com/questions/1646807stackoverflow.com/questions/11294686 Jon Skeet 对第一个问题的回答也很有用
【解决方案2】:

使用我的代码库中的示例扩展 David 的答案。我有一本字典

  Records: TDictionary<TGazetteerRecord,TGazetteerRecord>

实例化的

  Records := TDictionary<TGazetteerRecord,TGazetteerRecord>.Create(InitCapacity, TGazRecordComparer.Create);

使这项工作有效的原因是在字典的构造中具有自定义比较器。

TGazRecordComparer = class(TEqualityComparer<TGazetteerRecord>)
private
public
  function Equals(const Left, Right: TGazetteerRecord): Boolean; override;
  function GetHashCode(const Value: TGazetteerRecord): Integer;  override;
end;

这个实现然后替换记录类型的默认代码。我的示例实际上使用了一个类而不是记录,但我不明白为什么这不应该与记录类型完美配合。请注意,比较器类是引用计数的,因此会在字典被销毁时自动丢弃。

【讨论】:

  • 我不明白这一点。执行在哪里。您似乎只是在告诉我们您已经实现了一个比较器。 FWIW 这样做的正常方法是调用 Construct 类方法。
猜你喜欢
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 2021-04-22
  • 1970-01-01
  • 2021-10-26
  • 2011-07-10
  • 2017-12-11
  • 2017-09-03
相关资源
最近更新 更多