views:

322

answers:

1

How can I use a TEnumerator to go through my TDictionary in sorted order by key?

I've got something like this:

  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;

So I would like to process my dictionary in the order: Dumb, Early, HelpMe, Tired.

Unfortunately the Delphi help is very minimal in describing how enumerators in general and TEnumerator specifically works and gives no examples that I can find. There is also very little written on the web about using Enumerators with Generics in Delphi.

And my sample code above doesn't even use TEnumerator, so I'm confused as to how this is all designed to be used.


Thanks Barry, for your answer.

My venture into Generics since I asked the question was interesting. I wanted to start implementing them in my code. The "sorting" problem was somewhat perplexing, since it appears that Generics seem to have methods dealing with sorting built in, but there's no good examples or documentation on how to do it.

In the end I did what Barry suggested and built an external index into Dictionary. Still, it doesn't feel right.

However, then I had another surprise: I was attempting to replace Gabr's GPStringHash with the Generic's TDictionary. The code was a little cleaner with the generics. But the bottom line was that TDictionary was over 3 times slower than Gabr's. 1,704,667 calls to TryGetValue took .45 seconds, but the same operation to Gabr's routines took .12 seconds. I'm not sure why, but maybe its as simple as Gabr having a faster Hash function and bucketing combination. Or maybe the generics had to generalize for every case and that inherently slows it down.

Never-the-less, maybe Barry or the other Delphi developers should look at this, because a 3 times speedup could ultimately benefit everyone. I would personally sooner use what's built into the language than a 3rd party package (even one as good as Gabr's) if given the choice. But for now, I'll stick to GPStringHash.

+6  A: 

The dictionary is a hash table, so it doesn't store items in sorted order. TEnumerator is simple - it just a means of iterating over items.

To get items in an order, you need to sort them. One way would be to put them into a list and sort the list, like this:

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;
Barry Kelly
Well yes, it is easy enough to maintain a separate TList to be a sorted index of the keys. But in Generics.Collections, there's the DoMoveNext method of TEnumerator that is supposed to define the sort order, and MoveNext and DoMoveNext (what's the difference?) to supposedly allow Enumeration of Collections. Wouldn't these allow me to enumerate the collection directly without needing to create and maintain a separate index? It's these that are very poorly explained everywhere.
lkessler
@Barry: p.s. I have seen your answer in: http://stackoverflow.com/questions/1230054/why-does-tenumerablet-use-pass-through-methods and I'm glad you wrote TEnumerable for performance first. I'm one of those whose program needs every bit of speed it can get. I'm trying now to move to the hash-table based generics of Delphi 2009.
lkessler
... and as I investigate more, I see all those comparitors defined in Generics.Defaults. They look useful, but what are they for if we are just to maintain a separate TList index?
lkessler
@lkessler: The point of a dictionary is random access, not sorted access. If you want to enumerate the collection, the point is to get all the items, not necessarily to get them in order. As you said, TEnumerable was optimized for speed. Most of the time, when enumerating a dictionary, you don't need them in order, so thowing an extra O(N log N) into the execution time of GetEnumerator would be unwelcome. If you want a sorted list, then make a list and sort it.
Mason Wheeler
@Mason: I agree with everything you say. And I know Enumerators are unsorted, but I thought they gave you the option of sorting (e.g. via DoMoveNext) to display them sorted when required. Maybe my concept of this whole thing is mistaken, but I have to blame that on the lack of good documentation for long time Delphi users like me who only first encountered Generics with Delphi 2009.
lkessler