Mit Delphi 12.2 wurde eine spannende neue Klasse eingeführt: TOrderedDictionary
. Diese Klasse bringt eine sinnvolle Erweiterung, für Fälle wo man bisher eine geordnete Dictionary-Struktur vermisst hat.
In diesem Beitrag schauen wir uns an, wie TOrderedDictionary
funktioniert und wie man mithilfe eines benutzerdefinierten IComparer
eine spezielle Sortierung vornimmt.
Warum TOrderedDictionary
?
Traditionelle Dictionaries wie TDictionary
in Delphi bieten schnellen Zugriff auf Schlüssel-Wert-Paare, aber sie behalten keine bestimmte Reihenfolge bei. Dies ist auch völlig analog zu Standardwerken über Datenstrukturen, wo Dictionaries üblicherweise wie Mengen ohne Ordnung definiert werden.
TOrderedDictionary
hingegen ist darauf ausgelegt, die Reihenfolge der Elemente nach ihrer Einfügereihenfolge beizubehalten, sie haben also eine Ordnung. Darüber hinaus erlaubt TOrderedDictionary
eine explizite Sortierung sowohl der Schlüssel als auch der Werte. Man kann also die Ordnung beliebig umsortieren.
Grundlagen der TOrderedDictionary
-Verwendung
Im Folgenden habe ich ein einfaches Beispiel zusammengestellt, das die Funktionsweise von TOrderedDictionary
illustriert. In diesem Fall initialisieren wir ein Dictionary mit einigen Beispielpaaren, sortieren nach verschiedenen Kriterien und verwenden einen benutzerdefinierten IComparer
, um die Schlüssel nach englischen Zahlenwörtern zu ordnen. Das Beispiel ist ursprünglich von Marcos Beispiel inspiriert, in dem Marco aber die Frage offen lässt, wie man dann tatsächlich die von ihm gewählten Zahlenworte logisch sortieren kann.
program OrderedDictionaryDemo;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Defaults,
System.Generics.Collections,
System.Diagnostics;
const
// Stringkonstante für die Zahlenwörter, kann einfach beliebig erweitert werden
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;
// Vergleicht zwei Zeichenfolgen basierend auf ihrem numerischen Wert, wie in NUMBER_WORDS definiert
function Compare(const Left, Right: string): Integer;
end;
constructor TNumberWordComparer.Create;
begin
// Zerlege den Multiline-String in einzelne Wörter
FNumberWords := NUMBER_WORDS.Split([sLineBreak, ' ']);
end;
function TNumberWordComparer.GetNumberValue(const Word: string): Integer;
var
Index: Integer;
begin
// Suche das Wort im Array und gebe den Index als Wert zurück
Index := TArray.IndexOf<string>(FNumberWords, LowerCase(Word));
if Index >= 0 then
Result := Index
else
Result := MaxInt; // Rückgabewert für nicht gefundene Wörter
end;
function TNumberWordComparer.Compare(const Left, Right: string): Integer;
begin
Vergleiche die beiden Worte und gebe einen negativen Wert zurück, wenn
die linke Wort-Zahl kleiner als die rechte ist.
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');
// Sortieren und Ausgabe der Elemente
Writeln('Standardreihenfolge:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
// Sortierung nach Schlüsseln und Werten
ODict.SortByKeys;
Writeln('Nach Schlüsseln sortiert:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
ODict.SortByValues;
Writeln('Nach Werten sortiert:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
// Benutzerdefinierte Sortierung nach Zahl-Worten
// Wichtig: In-Place-Create vermeiden. Stattdessen Variable verwenden
var GNumberComparer: IComparer<string> := TNumberWordComparer.Create;
ODict.SortByKeys(GNumberComparer);
Writeln('Sortiert nach Zahl-Worten:');
for var APair in ODict do
Writeln(APair.Key + ' - ' + APair.Value);
end.
Besonderheiten und Flexibilität
In unserem Beispiel nutzen wir SortByKeys
und SortByValues
, um Elemente nach Schlüsseln und Werten zu sortieren. TOrderedDictionary
bietet uns somit eine bequeme Möglichkeit, die Sortierreihenfolge nach Bedarf zu ändern.
Besonders interessant ist die Möglichkeit, einen benutzerdefinierten IComparer
zu implementieren. In diesem Fall sortieren wir nach englischen Zahlenwörtern (one
, two
, three
usw.) mithilfe von TNumberWordComparer
.
Dessen Methode GetNumberValue
durchsucht ein Array mit Zahlenwörtern, um den Index eines Worts zu finden. Dieser Index wird als numerischer Wert für die Sortierung verwendet. Falls ein Wort nicht gefunden wird, gibt die Funktion MaxInt
zurück, wodurch es am Ende der Liste landet. Das Array lässt sich beliebig erweitern. Die Verwendung eines in Delphi 12 eingeführten Multi-Line-Strings macht das Intialisieren des Arrays über die Split-Funktion leicht wartbar.
Wichtig: Der Comparer wird nur beim konkreten Sortieren verwendet und nicht etwa implizit, wenn neue Paare eingefügt werden. Das Dictionary bleibt also weiterhin eine statische Datenstruktur.
Fazit
Die neue TOrderedDictionary
-Klasse ist eine interessante Erweiterung der System.Generics.Collections
-Unit in Delphi 12.2. Mit ihrer Fähigkeit, die Reihenfolge von Einfügungen beizubehalten und eine flexible Sortierung durchzuführen, bietet sie Entwicklern eine vielseitige und praktische Lösung für die Verwaltung von Schlüssel-Wert-Paaren.