Difference between revisions of "User:Nhollm"

From Free Pascal wiki
Jump to navigationJump to search
(added classes example)
 
(27 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{Warning|All of this is subject to change. Do '''NOT''' use it until its finished. For any Questions or Input write me an E-Mail ich@nhollm.de or write me at the forums 'nhollm'.}}
+
{{Warning|All of this is subject to change. Do '''NOT''' use it until its finished. For any Questions or Input write me an E-Mail nhollm@gmx.de or write me at the forums 'nhollm'.}}
  
Todo:
 
(TObjectDictionary)
 
  
 
==TDictionary==
 
==TDictionary==
TDictionary is a generic Data-Container for Key-value pairs (associative array). <br>
+
TDictionary is a generic data-container for key-value pairs (associative array). <br>
  
It was introduced with Generics.Collections in FPC 3.2.0 (June 2020). It's part of the FPC Runtime Library and fully Delphi compatible.<br>
+
It was introduced with Generics.Collections (rtl-generics) in FPC 3.1.1+ and published in major release 3.2 (June 2020). It's part of the FPC Runtime Library and fully Delphi compatible.<br>
  
 +
(Like in Delphi) The TDictionary is implemented as a hash table. This means the key-value pairs are not sorted in a defined order. (in memory / = not iterable by index??)<br>
 +
 +
Both key and value can be of primitive type (e.g. integer, string, bool..) or rather complex (e.g. classes, records, arrays)
  
 
==Basic syntax (FPC)==
 
==Basic syntax (FPC)==
Line 17: Line 18:
  
 
uses
 
uses
Generics.Collections; //import the generics-library of the RTL
+
Generics.Collections;
  
 
type
 
type
TStrStrDictionary =  specialize TDictionary<string, string>; //specialize a new dictionary-object and set the desired data-type of key and value
+
TStrStrDictionary =  specialize TDictionary<string, string>;
  
 
var
 
var
Line 34: Line 35:
  
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
See also [[#Delphi]] syntax
  
 
==Usage:==
 
==Usage:==
Line 41: Line 44:
  
 
===Add===
 
===Add===
Add Entry or update its current value (key, value)
+
'''AddOrSetValue()''' is recommended, because '''Add()''' will fail, if the key already exists: 'Dublicate not allowed in Dictionary'.<br>
 +
 
 +
====Add====
 +
<syntaxhighlight lang="pascal">
 +
MyDictionary.Add('Key1', 'Value1');
 +
</syntaxhighlight>
 +
 
 +
====AddOrSetValue====
 +
recommended
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 
MyDictionary.AddOrSetValue('Key1', 'Value1');
 
MyDictionary.AddOrSetValue('Key1', 'Value1');
 
</syntaxhighlight>
 
</syntaxhighlight>
 
The function AddOrSetValue is recommended.<br>
 
Alternatively there is the Function '''Add()'''.<br>
 
'''Add()''' will throw an error if the Key already exists: 'Dublicate Not Allowed in Dictionary'.<br>
 
If you only want to add new Items and not overwrite them in the case they exist, take a look at TryAdd() below. It provides a feedback too.
 
  
 
===Remove===
 
===Remove===
Line 66: Line 72:
 
WriteLn(MyDictionary.Items['Key1']);
 
WriteLn(MyDictionary.Items['Key1']);
 
</syntaxhighlight>
 
</syntaxhighlight>
If the provided key does not exist, this will throw an Error: 'Dictionary Key does not Exist'. You can use 'TryGetValue' instead.
+
If the provided key does not exist, this will throw an Error: 'Dictionary Key does not Exist'. You can use [[#TryGetValue|TryGetValue()]] instead.
  
 
===Item Count===
 
===Item Count===
 
Total number of Items in dictionary (count)
 
Total number of Items in dictionary (count)
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
WriteLn('Items in Dictionary: ' + IntToStr(MyDictionary.Count));
+
WriteLn('Items in Dictionary: ', IntToStr(MyDictionary.Count));
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
===Loops===
 
===Loops===
 +
 +
====Loop Keys====
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
//Loop Keys
 
 
for KeyStr in MyDictionary.Keys do
 
for KeyStr in MyDictionary.Keys do
   WriteLn('Found key:' + KeyStr);
+
   WriteLn('Found key:', KeyStr);
 +
</syntaxhighlight>
  
 
+
====Loop Values====
//Loop Values
+
<syntaxhighlight lang="pascal">
 
for ValueStr in MyDictionary.Values do
 
for ValueStr in MyDictionary.Values do
   WriteLn('Found Value: ' + ValueStr);
+
   WriteLn('Found value: ', ValueStr);
 +
</syntaxhighlight>
  
 
+
====Loop Key-Value-Pairs====
//Loop Key-Value-Pairs
+
<syntaxhighlight lang="pascal">
 
//var KeyValuePair : TStrStrDictionary.TDictionaryPair;
 
//var KeyValuePair : TStrStrDictionary.TDictionaryPair;
 
for KeyValuePair in MyDictionary do
 
for KeyValuePair in MyDictionary do
   WriteLn('Found a Pair: Key:' + KeyValuePair.Key + ' Value:' + KeyValuePair.Value);
+
   WriteLn('Found a Pair: Key:' , KeyValuePair.Key , ' Value:' , KeyValuePair.Value);
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 110: Line 119:
  
 
===Trying===
 
===Trying===
If you want to get a feedback, use these functions. They return a boolean.<br>
+
These functions return a boolean.<br>
True if sucessfull, False if not. No Error is thrown, so they are a good alternative to GetValue or Add.
+
True if sucessfull, False if not. No error is thrown, so they are a good alternative to [[#Get Value|Get Value]] or [[#Add|Add()]].
  
 
====TryAdd====
 
====TryAdd====
Line 120: Line 129:
 
   writeln('not sucessfull');
 
   writeln('not sucessfull');
 
</syntaxhighlight>
 
</syntaxhighlight>
TryAdd does not update an existing keys value. Use AddOrSetValue instead. (Even tho there is no feedback provided)
+
Limitation: TryAdd does '''not''' update an existing keys value. Use [[#AddOrSetValue|AddOrSetValue()]] instead. (Even tho there is no feedback provided)
 +
 
 
====TryGetValue====
 
====TryGetValue====
 
TryGetValue takes two Arguments ->(key, value) and returns a boolean.<br>
 
TryGetValue takes two Arguments ->(key, value) and returns a boolean.<br>
 
If found, it returns '''True''' and the provided 'value' variable becomes the value.<br>
 
If found, it returns '''True''' and the provided 'value' variable becomes the value.<br>
If not found, the function returns '''False''' and the provided 'value'-variable is nil.
+
If not found, the function returns '''False''' and the provided 'value'-variable is '''nil'''.
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 
+
//var SearchedValue: string;
 
if MyDictionary.TryGetValue('Key', SearchedValue) then
 
if MyDictionary.TryGetValue('Key', SearchedValue) then
 
   WriteLn('Key found. Its value is: ' + SearchedValue)
 
   WriteLn('Key found. Its value is: ' + SearchedValue)
Line 133: Line 143:
 
   WriteLn('Could not get value');
 
   WriteLn('Could not get value');
  
//writeln(BoolToStr(MyDictionary.TryGetValue('Key', SearchedValue)));
+
//writeln(BoolToStr(MyDictionary.TryGetValue('Key', SearchedValue), true));
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 149: Line 159:
 
You can set up custom Functions/Procedures which are triggered if an Key or Value got added/removed.<br>
 
You can set up custom Functions/Procedures which are triggered if an Key or Value got added/removed.<br>
 
To do this, we add 2 procedures to the class we specialize from TDictionary.<br>
 
To do this, we add 2 procedures to the class we specialize from TDictionary.<br>
The TDictionary class provides the in-build functions 'OnKeyNotify' and 'OnValueNotify'. Now we assign them to our procedures.<br>
+
The TDictionary class provides the in-build functions 'OnKeyNotify' and 'OnValueNotify'. We assign them to our procedures. (via function pointers??)<br>
 
(Hint: If you update a existing value, there is no "updated"Event. Instead "removed" followed by "added" is triggered.)
 
(Hint: If you update a existing value, there is no "updated"Event. Instead "removed" followed by "added" is triggered.)
 
TODO: get the Key Name inside the ValueNotify-Event kontext
 
TODO: get the Key Name inside the ValueNotify-Event kontext
Line 178: Line 188:
 
procedure TStrStrDictionary.DoValueNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
 
procedure TStrStrDictionary.DoValueNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
 
   begin
 
   begin
         Writeln(AAction);//can be cnRemoved or cnAdded
+
         //Writeln(AAction);//can be 'cnRemoved' or 'cnAdded'
 
         if AAction = cnAdded then
 
         if AAction = cnAdded then
 
           writeln('A Value got added, the value is:', AItem)
 
           writeln('A Value got added, the value is:', AItem)
Line 198: Line 208:
  
 
   try
 
   try
     MyDictionary.AddOrSetValue('Key1', 'Value1');//triggers Add Event
+
     MyDictionary.AddOrSetValue('Key1', 'Value1');//triggers 'Add' Event
     MyDictionary.AddOrSetValue('Key1', 'New Value');//triggers Add and Remove Event
+
     MyDictionary.AddOrSetValue('Key1', 'New Value');//triggers 'Add' and 'Remove' Event
     MyDictionary.Remove('Key1');//triggers Remove Event
+
     MyDictionary.Remove('Key1');//triggers 'Remove' Event
 
     readln();
 
     readln();
 
   finally
 
   finally
Line 209: Line 219:
  
  
==Exceptions==
+
==Using Classes==
To handle Exceptions, import the SysUtils Library.  
+
 
 +
You can use classes for Keys and/or Values.
 +
Because of the fact that all created classes must be manually freed, it is strongly recommended to use TObjectDictionary instead.<br>
 +
A TObjectDictionary takes ownership of its contained classes and frees them automaticly if its freed.
 +
 
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
 
{$mode objfpc}
 
{$mode objfpc}
program Example;
+
program example;
  
 
uses
 
uses
Generics.Collections,SysUtils;
+
Generics.Collections;
  
 
type
 
type
TStrStrDictionary =  specialize TDictionary<string, string>;
+
 
 +
TMyTestClass = class
 +
  Name : string;
 +
end;
 +
 
 +
TIntClassDictionary =  specialize TDictionary<Integer, TMyTestClass>;
  
 
var
 
var
   MyDictionary : TStrStrDictionary;
+
   MyDictionary : TIntClassDictionary;
 +
  i: integer;
 +
  MyClass : TMyTestClass;
 
begin
 
begin
   MyDictionary := TStrStrDictionary.Create;
+
   MyDictionary := TIntClassDictionary.Create;
 
   try
 
   try
 +
    for i:= 0 to 5 do
 +
      begin
 +
        MyClass := TMyTestClass.Create;
 +
        MyClass.Name := 'Something';
 +
        MyDictionary.Add(i,MyClass);
 +
      end;
 +
  finally
 +
  //is this even necessary?????
 +
    for MyClass in MyDictionary.Values do
 +
      begin
 +
        MyClass.Free;
 +
      end;
 +
 +
    MyDictionary.Free;
 +
  end;
 +
end.
 +
</syntaxhighlight>
  
      try         
+
==Exceptions==
          WriteLn(MyDictionary.Items['KeyXY']); // Accessing a non existing key
+
To handle Exceptions, import the SysUtils Library.  
        except
+
<syntaxhighlight lang="pascal">
 +
{$mode objfpc}
 +
program Example;
 +
...
 +
uses
 +
... SysUtils;
  
            on E: Exception do begin
+
try         
              WriteLn('An exception was raised: ' + E.Message);
+
  WriteLn(MyDictionary.Items['KeyXY']); // Accessing a non-existing key
              WriteLn(E.ClassName);
+
except
  
            end;
+
  on E: Exception do begin
 +
      WriteLn('An exception was raised: ', E.Message);
 +
      WriteLn(E.ClassName);
 +
  end;
  
        end;
+
end;
        ReadLn();
 
  finally
 
    MyDictionary.Free;
 
  end;
 
end.
 
  
//Output: An exception was raised: Dictionary key does not exist
+
//Output:
 +
// An exception was raised: Dictionary key does not exist
 
//EListError
 
//EListError
 
</syntaxhighlight>
 
</syntaxhighlight>
 
 
  
 
==Delphi==
 
==Delphi==

Latest revision as of 00:31, 16 May 2024

Warning-icon.png

Warning: All of this is subject to change. Do NOT use it until its finished. For any Questions or Input write me an E-Mail nhollm@gmx.de or write me at the forums 'nhollm'.


TDictionary

TDictionary is a generic data-container for key-value pairs (associative array).

It was introduced with Generics.Collections (rtl-generics) in FPC 3.1.1+ and published in major release 3.2 (June 2020). It's part of the FPC Runtime Library and fully Delphi compatible.

(Like in Delphi) The TDictionary is implemented as a hash table. This means the key-value pairs are not sorted in a defined order. (in memory / = not iterable by index??)

Both key and value can be of primitive type (e.g. integer, string, bool..) or rather complex (e.g. classes, records, arrays)

Basic syntax (FPC)

{$mode objfpc}
program Example;

uses
Generics.Collections;

type
TStrStrDictionary =  specialize TDictionary<string, string>;

var
  MyDictionary : TStrStrDictionary;
begin
  MyDictionary := TStrStrDictionary.Create;
  try
    //do stuff with your dictionary..
  finally
    MyDictionary.Free;
  end;
end.

See also #Delphi syntax

Usage:

Add

AddOrSetValue() is recommended, because Add() will fail, if the key already exists: 'Dublicate not allowed in Dictionary'.

Add

MyDictionary.Add('Key1', 'Value1');

AddOrSetValue

recommended

MyDictionary.AddOrSetValue('Key1', 'Value1');

Remove

MyDictionary.Remove('Key2');

If the Key does not exist, no Error is thrown.

Clear

MyDictionary.Clear;

Get Value

WriteLn(MyDictionary.Items['Key1']);

If the provided key does not exist, this will throw an Error: 'Dictionary Key does not Exist'. You can use TryGetValue() instead.

Item Count

Total number of Items in dictionary (count)

WriteLn('Items in Dictionary: ', IntToStr(MyDictionary.Count));

Loops

Loop Keys

for KeyStr in MyDictionary.Keys do
  WriteLn('Found key:', KeyStr);

Loop Values

for ValueStr in MyDictionary.Values do
  WriteLn('Found value: ', ValueStr);

Loop Key-Value-Pairs

//var KeyValuePair : TStrStrDictionary.TDictionaryPair;
for KeyValuePair in MyDictionary do
  WriteLn('Found a Pair: Key:' , KeyValuePair.Key , ' Value:' , KeyValuePair.Value);

Check If Key/Value Exists

//Check if Key exists
if MyDictionary.ContainsKey('Key1') then
  WriteLn('key exists')
else
  WriteLn('key not found');


//Check if Value exists
if MyDictionary.ContainsValue('Searched value') then
  writeln('value exists')
else
  writeln('value not found');

Trying

These functions return a boolean.
True if sucessfull, False if not. No error is thrown, so they are a good alternative to Get Value or Add().

TryAdd

if MyDictionary.TryAdd('Key','TestValue') then
  writeln('added successfully')
else
  writeln('not sucessfull');

Limitation: TryAdd does not update an existing keys value. Use AddOrSetValue() instead. (Even tho there is no feedback provided)

TryGetValue

TryGetValue takes two Arguments ->(key, value) and returns a boolean.
If found, it returns True and the provided 'value' variable becomes the value.
If not found, the function returns False and the provided 'value'-variable is nil.

//var SearchedValue: string;
if MyDictionary.TryGetValue('Key', SearchedValue) then
  WriteLn('Key found. Its value is: ' + SearchedValue)
else
  WriteLn('Could not get value');

//writeln(BoolToStr(MyDictionary.TryGetValue('Key', SearchedValue), true));

ToArray

Returns an Array of type 'TDictionaryPair'.

//var myArray :  array of TStrStrDictionary.TDictionaryPair;
myArray := MyDictionary.ToArray()
//WriteLn(length(myArray));
//WriteLn(myArray[0].Key)
//WriteLn(myArray[0].Value)

Event Notifications

You can set up custom Functions/Procedures which are triggered if an Key or Value got added/removed.
To do this, we add 2 procedures to the class we specialize from TDictionary.
The TDictionary class provides the in-build functions 'OnKeyNotify' and 'OnValueNotify'. We assign them to our procedures. (via function pointers??)
(Hint: If you update a existing value, there is no "updated"Event. Instead "removed" followed by "added" is triggered.) TODO: get the Key Name inside the ValueNotify-Event kontext

{$mode objfpc}
program Example;

uses
Generics.Collections;

type
TStrStrDictionary = Class(Specialize TDictionary<String,String>)
  private
    procedure DoKeyNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
    procedure DoValueNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
  end;

procedure TStrStrDictionary.DoKeyNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
   begin
        //WriteLn(AAction);//AAction can be 'cnRemoved' or 'cnAdded'
        if AAction = cnAdded then
           writeln('A Key got added, the Key is:', AItem)
        else if AAction = cnRemoved then
            writeln('A Key got removed, the Key was:', AItem)
   end;

procedure TStrStrDictionary.DoValueNotify(ASender: TObject;constref AItem: ShortString; AAction: TCollectionNotification);
   begin
        //Writeln(AAction);//can be 'cnRemoved' or 'cnAdded'
        if AAction = cnAdded then
          writeln('A Value got added, the value is:', AItem)
        else if AAction = cnRemoved then
          writeln('A Value got removed, the value was:', AItem)
   end;



var
  MyDictionary : TStrStrDictionary;
  TestArr : array of TStrStrDictionary.TDictionaryPair;
begin
  MyDictionary := TStrStrDictionary.Create;

  //Event notifications:
  MyDictionary.OnKeyNotify:=@MyDictionary.DoKeyNotify;
  MyDictionary.OnValueNotify:=@MyDictionary.DoValueNotify;

  try
    MyDictionary.AddOrSetValue('Key1', 'Value1');//triggers 'Add' Event
    MyDictionary.AddOrSetValue('Key1', 'New Value');//triggers 'Add' and 'Remove' Event
    MyDictionary.Remove('Key1');//triggers 'Remove' Event
    readln();
  finally
    MyDictionary.Free;
  end;
end.


Using Classes

You can use classes for Keys and/or Values. Because of the fact that all created classes must be manually freed, it is strongly recommended to use TObjectDictionary instead.
A TObjectDictionary takes ownership of its contained classes and frees them automaticly if its freed.

{$mode objfpc}
program example;

uses
Generics.Collections;

type

TMyTestClass = class
  Name : string;
end;

TIntClassDictionary =  specialize TDictionary<Integer, TMyTestClass>;

var
  MyDictionary : TIntClassDictionary;
  i: integer;
  MyClass : TMyTestClass;
begin
  MyDictionary := TIntClassDictionary.Create;
  try
    for i:= 0 to 5 do
      begin
         MyClass := TMyTestClass.Create;
         MyClass.Name := 'Something';
         MyDictionary.Add(i,MyClass);
      end;
  finally
   //is this even necessary?????
    for MyClass in MyDictionary.Values do
      begin
         MyClass.Free;
      end;

    MyDictionary.Free;
  end;
end.

Exceptions

To handle Exceptions, import the SysUtils Library.

{$mode objfpc}
program Example;
...
uses
... SysUtils;

try          
   WriteLn(MyDictionary.Items['KeyXY']); // Accessing a non-existing key 
except

   on E: Exception do begin
      WriteLn('An exception was raised: ', E.Message);
      WriteLn(E.ClassName);
   end;

end;

//Output:
// An exception was raised: Dictionary key does not exist
//EListError

Delphi

{$mode delphi}
uses generics.collections;
 
type  TStrIntDict = TDictionary<string,integer>;
 
    var
      MyDict : TStrIntDict;
    begin
      MyDict:=TStrIntDict.create;
      MyDict.AddorSetValue('foo',42);
      if MyDict.ContainsKey('foo') then
        WriteLn(MyDict['foo']);
    end.