1. Computing

Using TDictionary - Hash Tables in Delphi

Using And Understanding The TDictionary Class In Delphi

By

TDictionary example in Delphi
TDictionary example in Delphi
Introduced in Delphi 2009, the TDictionary class, defined in Generics.Collections unit, represents a generic hash table type collection of key-value pairs.

Generic types, again introduced in Delphi 2009, allow you to define classes that don't specifically define the type of data members.

A dictionary is, in a way, similar to an array. In an array you would have series (collection) of values indexed by (usually) an integer value (can be any ordinal type value). This index has a lower an an upper bound.

In a dictionary we have keys and values where both keys and values can be of any type.

Hence the declaration of the TDictionary constructor:

  TDictionary<TKey, TValue>.Create;
In Delphi, the TDictionary is defined as a hash table. Hash tables represents a collection of key-and-value pairs that are organized based on the hash code of the key. Hash tables are optimized for lookups (speed). When a key-value pair is added to a hash table, the hash of the key is computed and stored along with the added pair.

The TKey and TValue (since generics) can be of any type. For example, if the information you are to store in the dictionary is coming from some database, your Key can be a GUID (or some other value presenting the unique index) value while the Value can be an object mapped to a row of data in your database tables.

Using TDictionary

For the sake of simplicity the example below uses integers for Keys and Chars for Values. Let's see how to do basic operations with dictionaries:
//
// "log" is a TMemo control placed on a form
//
var
  dict : TDictionary<integer, char>;
  sortedDictKeys : TList<integer>;
  i, rnd : integer;
  c : char;
begin
  log.Clear;
  log.Text := 'TDictionary usage samples';

  Randomize;

  dict := TDictionary<integer, char>.Create;
  try
    //add some key/value pairs (random integers, random characters from A in ASCII)
    for i := 1 to 20 do
    begin
      rnd := Random(30);

      if NOT dict.ContainsKey(rnd) then dict.Add(rnd, Char(65 + rnd));
    end;

    //remove some key/value pairs (random integers, random characters from A in ASCII)
    for i := 1 to 20 do
    begin
      rnd := Random(30);

      dict.Remove(rnd);
    end;

    //loop elements - go through keys
    log.Lines.Add('ELEMENTS:');
    for i in dict.Keys do
      log.Lines.Add(Format('%d, %s', [i, dict.Items[i]]));

    //do we have a "special" key value
    if dict.TryGetValue(80, c) then
      log.Lines.Add(Format('Found "special", value: %s', [c]))
    else
      log.Lines.Add(Format('"Special" key not found', []));


    //sort by keys ascending
    log.Lines.Add('KEYS SORTED ASCENDING:');
    sortedDictKeys := TList.Create(dict.Keys);
    try
      sortedDictKeys.Sort; //default ascending
      for i in sortedDictKeys do
        log.Lines.Add(Format('%d, %s', [i, dict.Items[i]]));
    finally
      sortedDictKeys.Free;
    end;

    //sort by keys descending
    log.Lines.Add('KEYS SORTED DESCENDING:');
    sortedDictKeys := TList.Create(dict.Keys);
    try
      sortedDictKeys.Sort(TComparer.Construct(
       function (const L, R: integer): integer
       begin
         result := R - L;
       end
       )) ;
      for i in sortedDictKeys do
        log.Lines.Add(Format('%d, %s', [i, dict.Items[i]]));
    finally
      sortedDictKeys.Free;
    end;

  finally
    dict.Free;
  end;
end;
First, we declare our dictionary by specifying what the types of the TKey and TValue will be:
dict : TDictionary;
The dictionary is filled in by using the Add method. Since a dictionary cannot have two pair with the same Key value, you can use the ContainsKey method to check if some key valued pair is already inside the dictionary.

To remove a pair from the dictionary, the Remove method can be used. Note that Remove will not cause problems if a pair with a specified key is not a part of the dictionary.

To go through all the pairs by looping through keys you can do a for in loop.

The TryGetValue method can be used to check if some key value pair is included in the dictionary.

Sorting The Dictionary

Since a dictionary is a hash table it does not store items in some sort order. If you would like to iterate through the keys that are sorted (in any way you want) you can take advantage of the TList (again a generic collection type) which supports sorting.

The code above sorts keys ascending (and descending) and grabs values as if they were stored in the sorted order in the dictionary. Note that descending sorting of (integer type) Key values uses TComparer and an anonymous method.

When Keys And Values Are Of TObject Type

As said, the above example is a simple one since both the key and the value are simple types.

Of course, you can have complex dictionaries where both the key and the value would be "complex" types like records or objects.

Here's another example:

type
  TMyRecord = record
    Name, Surname : string
  end;

  TMyObject = class(TObject)
    Year, Value : integer;
  end;

procedure TForm2.logDblClick(Sender: TObject);
var
  dict : TObjectDictionary<TMyRecord, TMyObject>;
  myR : TmyRecord;
  myO : TMyObject;
begin
  dict := TObjectDictionary<TMyRecord, TMyObject>.Create([doOwnsValues]);

  try
    myR.Name := 'Zarko';
    myR.Surname := 'Gajic';

    myO := TMyObject.Create;
    myO.Year := 2012;
    myO.Value := 39;

    dict.Add(myR, myO);

    myR.Name := 'Zarko';
    myR.Surname := '?????';
		
    if NOT dict.ContainsKey(myR) then log.Lines.Add('not found');
  finally
    dict.Free;
  end;
end;
Here a custom record is used for the Key and a custom object/class is used for the value.

Note the usage of a specialized TObjectDictionary class here. TObjectDictionary has the capability to handle objects' lifetime automatically.

The Key value cannot be nil, while the Value value can.

When a TObjectDictionary is instantiated, an Ownerships parameter specifies whether the dictionary owns the keys, values, or both - and therefore helps you not have memory leaks.

©2014 About.com. All rights reserved.