如何在Delphi 2009中按键值字母顺序列出TDictionary?

19

我该如何使用TEnumerator按键排序遍历我的TDictionary?

我有类似以下的代码:

  var
    Dic: TDictionary<string, string>;
    Enum: TPair<string, string>;

  begin
    Dic := TDictionary<string, string>.create;
    Dic.Add('Tired', 'I have been working on this too long');
    Dic.Add('Early', 'It is too early in the morning to be working on this');
    Dic.Add('HelpMe', 'I need some help'); 
    Dic.Add('Dumb', 'Yes I know this example is dumb');

   { I want to do the following but do it in sorted order by Enum.Key }
    for Enum in Dic do
      some processing with Enum.Key and Enum.Value;

    Dic.Free;
  end;

所以我希望按照以下顺序处理我的字典:Dumb、Early、HelpMe、Tired。

不幸的是,Delphi帮助文档在描述枚举器(enumerator)和TEnumerator的工作原理方面非常简略,并且没有我能找到的示例。关于在Delphi中使用泛型与枚举器的方法,网络上也很少有相关内容。

而且,我的示例代码甚至没有使用TEnumerator,因此我对这一切的设计方式感到困惑。


谢谢Barry,感谢你的回答。

自从我提出这个问题以来,我开始了对泛型的探索。我想在我的代码中开始实现它们。"排序"问题有点令人困惑,因为泛型似乎具有处理排序的内置方法,但没有好的示例或文档说明如何做到这一点。

最后,我按照Barry的建议,在字典中构建了一个外部索引。不过,这并不感觉很正确。

然而,之后我又有了一个惊喜:我试图用泛型的TDictionary替换GabrGPStringHash。使用泛型使代码更加简洁。但是,TDictionary比Gabr的慢了3倍以上。尝试1,704,667次TryGetValue花费了0.45秒,但是相同的操作在Gabr的程序中只花费了0.12秒。我不确定原因,但也许就是Gabr拥有更快的哈希函数和桶组合。或者泛型必须为每种情况进行泛化,这本质上会减慢速度。

无论如何,也许Barry或其他Delphi开发人员应该看看这个问题,因为3倍的加速最终可能会使每个人受益。如果可以选择,我个人更愿意使用语言内置的内容,而不是第三方包(即使像Gabr这样好)。但现在,我会坚持使用GPStringHash。


1
这是一个后续:今年早些时候(2016年),我升级到了Delphi XE8。我认为Delphi泛型包中的TDictionary可能已经在我5年前提出这个问题时得到改进。因此,我用@gabr的GPStringHash替换了它。我的程序变慢了很多。因此,至少在可预见的未来,我将坚持使用GPStringHash。 - lkessler
2
根据您的需求,与其在字典中维护数据并偶尔对其进行排序,不如反过来:将数据维护在已排序的列表中,但使用字典进行快速“随机”访问。如果您通过SQL进行保存/检索,这种方法更加适合。只需始终清楚地知道谁拥有对象(字符串,您是安全的)。 - Jim Mack
3个回答

21

这个字典是一个哈希表,所以它不会按排序存储项目。TEnumerator很简单-它只是一种遍历项目的方法。

要按顺序获取项目,您需要对其进行排序。一种方法是将它们放入列表中并对列表进行排序,就像这样:

var
  list: TList<string>;
begin
  list := TList<string>.Create(Dic.Keys);
  try
    list.Sort;
    // process sorted list of items now
  finally
    list.Free;
  end;
end;

随着我进一步调查,我发现所有这些比较器都定义在Generics.Defaults中。它们看起来很有用,但是如果我们只是要维护单独的TList索引,那么它们有什么用呢? - lkessler
1
@lkessler:字典的重点是随机访问,而不是排序访问。如果你想枚举集合,那么重点是获取所有项,而不一定按顺序获取它们。正如你所说,TEnumerable被优化为速度。大多数情况下,在枚举字典时,你不需要按顺序获取它们,因此在GetEnumerator的执行时间中增加额外的O(N log N)将是不受欢迎的。如果你想要一个排序列表,那么就创建一个列表并对其进行排序。 - Mason Wheeler
@Mason:我同意你所说的一切。我知道枚举器是无序的,但我认为它们可以通过DoMoveNext等方式提供排序选项,以在需要时显示排序后的结果。也许我的整个概念都是错误的,但我必须归咎于缺乏好的文档,因为像我这样的长期Delphi用户只是在Delphi 2009中首次遇到泛型。 - lkessler
我知道这个答案是针对Delphi 2009的,但很遗憾这在XE2上不再起作用了 :( - TheDude
@TheDude,XE2有哪些变化? - alcalde
显示剩余2条评论

8
在我的情况下,我使用TDictionary < String, String >.TKeyCollection类。
function compareKey(const L, R: String): Integer;
begin
  Result := SysUtils.CompareText(L, R);
end;

function getReverseSortedKeyArray(dictionary: TDictionary<String, String): TArray<String>;
var
  keyArray: TArray<String>;
  keyCollection: TDictionary<String, String>.TKeyCollection;
begin
  keyCollection:= TDictionary<String, String>.TKeyCollection.Create(dictionary);
  try
    keyArray:= keyCollection.ToArray;
    TArray.Sort<String>(keyArray, TComparer<String>.Construct(compareKey));
  finally
    keyCollection.Free;
  end;

  Result := keyArray;
end;

使用示例:

var
  key: String;
  keyArray : TArray<String>;
begin
    keyArray  := getSortedKeyArray (dictionary);
    for key in keyArray  do
    begin
      // ...
    end;
end;

什么是 valueCollecttion?你是指 keyCollecttion 吗? - Delmo
@Delmo 值集合指的是键集合。这是一个错误。我已经更新了答案。谢谢。 - Stéphane B.

5
这里有一个样本代码,可以通过 Array<T>TList<T> 进行排序。它保留了键值对关系,并且也可以进行修改以按值而不是按键排序。此外,它使用匿名方法执行排序。
请务必在你的 uses 子句中包含 Generics.CollectionsGenerics.Defaults
使用 TArray<T> 进行排序的第一种方法:
procedure TestSortDictionaryViaArray;
var
  D: TDictionary<string, Integer>;
  A: TArray<TPair<string, Integer>>;
  P: TPair<string, Integer>;
begin
  D := TDictionary<string, Integer>.Create;

  D.Add('Test - 6', 6);
  D.Add('Test - 1', 1);
  D.Add('Test - 0', 0);
  D.Add('Test - 4', 4);
  D.Add('Test - 3', 3);
  D.Add('Test - 5', 0);
  D.Add('Test - 2', 2);

  A := D.ToArray;

  TArray.Sort<TPair<string, Integer>>(A,
    TComparer<TPair<string, Integer>>.Construct(
      function (const L, R: TPair<string, Integer>): Integer
      begin
        Result := CompareStr(L.Key, R.Key);
      end)
  );

  for P in A do
    ShowMessage(P.Key);
  D.Free;
end;

这里使用的是 TList<T>

procedure TestSortDictionaryViaList;
var
  D: TDictionary<string, Integer>;
  L: TList<TPair<string, Integer>>;
  P: TPair<string, Integer>;
begin
  D := TDictionary<string, Integer>.Create;

  D.Add('Test - 6', 6);
  D.Add('Test - 1', 1);
  D.Add('Test - 0', 0);
  D.Add('Test - 4', 4);
  D.Add('Test - 3', 3);
  D.Add('Test - 5', 0);
  D.Add('Test - 2', 2);

  L := TList<TPair<string, Integer>>.Create(D);

  L.Sort(
    TComparer<TPair<string, Integer>>.Construct(
      function (const L, R: TPair<string, Integer>): Integer
      begin
        Result := CompareStr(L.Key, R.Key);
      end)
  );

  for P in L do
    ShowMessage(P.Key);

  D.Free;
  L.Free;
end;

额外(且不必要)的信息: TList<T> 方法需要释放列表,而 TArray<T> 不需要释放。在内部,TList<T> 使用 TArray<T>(例如,TArray 有一个 BinarySearch() 类方法,而 TList<T> 有一个 BinarySearch 方法)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接