With Delphi 12.2, an exciting new class has been introduced: TOrderedDictionary
. This class provides a useful addition for cases where an ordered dictionary structure was previously missing.
In this post, we will take a look at how TOrderedDictionary
works and how to use a custom IComparer
to perform a specific sorting.
Why TOrderedDictionary
?
Traditional dictionaries like TDictionary
in Delphi provide fast access to key-value pairs, but they do not maintain any specific order. This is also completely analogous to standard references on data structures, where dictionaries are usually defined like sets without order.
TOrderedDictionary
, however, is designed to maintain the order of elements according to their insertion order, meaning they have an order. Moreover, TOrderedDictionary
allows for explicit sorting of both keys and values. You can therefore reorder the collection at will.
Basics of Using TOrderedDictionary
In the following, I have put together a simple example that illustrates how TOrderedDictionary
works. In this case, we initialize a dictionary with some sample pairs, sort by different criteria, and use a custom IComparer
to order the keys by English number words. The example was originally inspired by Marco’s example, where Marco left the question open as to how one could logically sort the number words he chose.
program OrderedDictionaryDemo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Defaults,
System.Generics.Collections,
System.Diagnostics;
const
// String constant for the number words, can be easily extended
NUMBER_WORDS =
'''
one two three four five
six seven eight nine ten
''';
type
TNumberWordComparer = class(TInterfacedObject, IComparer<string>)
private
FNumberWords: TArray<string>;
function GetNumberValue(const Word: string): Integer;
public
constructor Create;
// Compares two strings based on their numeric value as defined in NUMBER_WORDS
function Compare(const Left, Right: string): Integer;
end;
constructor TNumberWordComparer.Create;
begin
// Split the multiline string into individual words
FNumberWords := NUMBER_WORDS.Split([sLineBreak, ' ']);
end;
function TNumberWordComparer.GetNumberValue(const Word: string): Integer;
var
Index: Integer;
begin
// Find the word in the array and return the index as its value
Index := TArray.IndexOf<string>(FNumberWords, LowerCase(Word));
if Index >= 0 then
Result := Index
else
Result := MaxInt; // Return value for words not found
end;
function TNumberWordComparer.Compare(const Left, Right: string): Integer;
begin
// Compare the two words and return a negative value if
// the left word-number is smaller than the right one
Result := GetNumberValue(Left) - GetNumberValue(Right);
end;
begin
var ODict := TOrderedDictionary<string, string>.Create;
ODict.Add('one', 'London');
ODict.Add('two', 'Berlin');
ODict.Add('three', 'Rome');
ODict.Add('four', 'Athens');
ODict.Add('five', 'Madrid');
ODict.Add('six', 'Bruxelles');
ODict.Add('seven', 'Paris');
// Sorting and displaying the elements
Writeln('Default order:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
// Sorting by keys and values
ODict.SortByKeys;
Writeln('Sorted by keys:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
ODict.SortByValues;
Writeln('Sorted by values:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
// Custom sorting by number words
ODict.SortByKeys(TNumberWordComparer.Create);
Writeln('Sorted by number words:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
end.
Features and Flexibility
In our example, we use SortByKeys
and SortByValues
to sort elements by keys and values. TOrderedDictionary
thus offers us a convenient way to change the sorting order as needed.
Of particular interest is the ability to implement a custom IComparer
. In this case, we sort by English number words (one
, two
, three
, etc.) using TNumberWordComparer
.
The GetNumberValue
method searches an array of number words to find the index of a word. This index is used as the numeric value for sorting. If a word is not found, the function returns MaxInt
, placing it at the end of the list. The array can be extended as needed. The use of a multi-line string introduced in Delphi 12 makes initializing the array via the Split
function easy to maintain.
Important: The comparer is only used when explicitly sorting, not implicitly when new pairs are inserted. Therefore, the dictionary remains a static data structure.
Conclusion
The new TOrderedDictionary
class is an interesting addition to the System.Generics.Collections
unit in Delphi 12.2. With its ability to maintain the order of insertions and perform flexible sorting, it offers developers a versatile and practical solution for managing key-value pairs.
No Comments