Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TDictionary populated during create example code

Tags:

delphi

Does anyone have example code of TDictionary<TKey, TValue> being populated during its constructor?

like image 752
SysJames Avatar asked Dec 27 '22 10:12

SysJames


2 Answers

Apparently you just want a one-liner, so I gave this a try, implemented a TDictHelper that allows creating AND populating the dictionary using a one-liner.


The problem with initializing the Dictionary using any form of one-liner is that it needs pairs of values, and we don't have the required nice syntax to pass those pairs. For example, if one would be required to use the TPair<Key, Value>.Create(A, B) syntax for each pair of values added to the dictionary, that would be one ugly one liner.

I did figure out a couple of good-looking alternatives; The first one is used like this:

  with TDictHelper<Integer, string> do
    Dict := Make([P(1, 'one'), P(2, 'two')]);

The use of the with is required because the TDictHelper class I implemented has a Make routine that takes an array of TPair<Key, Value> as parameter; This would be unusable if I wrote it as:

Dict := TDictHelper<Integer, string>.Make(TPair<Integer, string>.Create(1, 'one'), TPair<Integer, string>.Create(2, 'two'));

It would work, but it would be very, very ugly!

Since the use of the with can be problematic (especially if you'd like to use two kinds of dictionaries), I included an alternative syntax; Unfortunately this one does not scale, it gets real ugly real fast:

  Dict := TDictHelper<Integer, string>.Make([1, 2], ['one', 'two']);

This alternative takes two separate arrays for Keys and Values, combines them inside the Make method. Looks ok for 2-3 elements, but would not scale: what if you have 10 elements and need to remove the 7th pair? You'd need to COUNT the elements and that's error-prone.

Here's the complete code, not much to it:

program Project25;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type
  TDictHelper<Key, Value> = class
  public
    class function P(const K:Key; const V:Value): TPair<Key, Value>;
    class function Make(init: array of TPair<Key, Value>): TDictionary<Key, Value>;overload;
    class function Make(KeyArray: array of Key; ValueArray: array of Value): TDictionary<Key, Value>;overload;
  end;

{ TDictHelper<Key, Value> }

class function TDictHelper<Key, Value>.Make(init: array of TPair<Key, Value>): TDictionary<Key, Value>;
var P: TPair<Key, Value>;
begin
  Result := TDictionary<Key, Value>.Create;
  for P in init do
    Result.AddOrSetValue(P.Key, P.Value);
end;

class function TDictHelper<Key, Value>.Make(KeyArray: array of Key;
  ValueArray: array of Value): TDictionary<Key, Value>;
var i:Integer;
begin
  if Length(KeyArray) <> Length(ValueArray) then
    raise Exception.Create('Number of keys does not match number of values.');
  Result := TDictionary<Key, Value>.Create;
  for i:=0 to High(KeyArray) do
    Result.AddOrSetValue(KeyArray[i], ValueArray[i]);
end;

class function TDictHelper<Key, Value>.P(const K: Key;
  const V: Value): TPair<Key, Value>;
begin
  Result := TPair<Key, Value>.Create(K, V);
end;

// ============================== TEST CODE FOLLOWS

var Dict: TDictionary<Integer, string>;
    Pair: TPair<Integer, string>;

begin
  try
    try
      // Nice-looking but requires "with" and you can't work with two kinds of DictHelper at once
      with TDictHelper<Integer, string> do
        Dict := Make([P(1, 'one'), P(2, 'two')]);
      // Use the array
      for Pair in Dict do
        WriteLn(Pair.Key, ' = ', Pair.Value);
      Dict.Free;

      // Passing the Keys and the Values in separate arrays; Works without "with" but it would
      // be difficult to maintain for larger number of key/value pairs
      Dict := TDictHelper<Integer, string>.Make([1, 2], ['one', 'two']);
      // Use the array
      for Pair in Dict do
        WriteLn(Pair.Key, ' = ', Pair.Value);
      Dict.Free;

    except on E:Exception do
      WriteLn(E.ClassName, #13#10, E.Message);
    end;
  finally ReadLn;
  end;
end.
like image 160
Cosmin Prund Avatar answered Jan 09 '23 21:01

Cosmin Prund


You need to call the dictionary constructor overload that receives a Collection parameter of type TEnumerable<TPair<TKey, TValue>>.

For example, suppose that we have TDictionary<string, Integer>. Then we could pass to the constructor an instance of TEnumerable<TPair<string, Integer>>. An example of such a thing is TList<TPair<string, Integer>>.

List := TList<TPair<string, Integer>>.Create;
List.Add(TPair<string, Integer>.Create('Foo', 42));
List.Add(TPair<string, Integer>.Create('Bar', 666));
Dictionary := TDictionary<string, Integer>.Create(List);

This is very unwieldy and you would never prefer this option over a simple Create followed by a series of calls to Add. You would only use the option of passing in an existing collection if you happened to a ready-made one at hand.

Another example of a class that derives from TEnumerable<T> is TDictionary itself:

type
  TDictionary<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>)

So if you already had one instance of the dictionary, you could create another one and initialise it with the contents of the first:

Dict2 := TDictionary<string, Integer>.Create(Dict1);
like image 42
David Heffernan Avatar answered Jan 09 '23 22:01

David Heffernan