创建示例代码期间填充TDictionary

4

有没有人有 TDictionary<TKey, TValue> 在构造函数期间被填充的示例代码?

3个回答

5

显然,您只需要一行代码,因此我尝试实现了一个TDictHelper,它允许使用一行代码创建和填充字典。


使用任何形式的一行代码初始化字典的问题在于它需要值对,并且我们没有所需的良好语法来传递这些值对。例如,如果需要为添加到字典中的每个值对使用TPair<Key, Value>.Create(A, B)语法,则会变成一个丑陋的一行代码。

我确实想出了几种看起来不错的替代方法;第一个用法如下:

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

使用with是必需的,因为我实现的TDictHelper类具有一个Make例程,它需要将一个TPair<Key, Value>数组作为参数;如果我将其编写为以下形式,则无法使用:
Dict := TDictHelper<Integer, string>.Make(TPair<Integer, string>.Create(1, 'one'), TPair<Integer, string>.Create(2, 'two'));

这会起作用,但会非常非常丑陋!

由于使用with可能存在问题(特别是如果您想使用两种类型的字典),我提供了一种替代语法; 不幸的是,这种方法无法扩展,它会变得非常丑陋:

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

这种替代方法需要为Key和Value分别设置两个不同的数组,并在Make方法内将它们组合在一起。对于2-3个元素来说看起来还可以,但是不适用于大规模:如果有10个元素并且需要删除第7个键值对,你需要计算元素的数量,这可能会导致错误。

以下是完整的代码,没有太多东西:

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.

1
这很整洁。请注意,它从未完全执行问题所要求的任务,即在构造函数中填充字典。无论如何,当字典构造函数使用TEnumerable进行此操作时,它会调用AddOrSetValue,你可能希望在此处模仿它。 - David Heffernan
@David,我理解这个问题是在询问一种一行代码的方式来填充TDictionary<>。当然,我的理解也可能是错误的。 - Cosmin Prund
不,这很公平。我对一些创造性的解释感到满意。我的回答是字面意思。你的值得+1。 - David Heffernan

4
您需要调用接收类型为TEnumerable<TPair<TKey, TValue>>Collection参数的字典构造函数重载

例如,假设我们有TDictionary<string, Integer>。那么我们可以将TEnumerable<TPair<string, Integer>>的实例传递给构造函数。这样一个例子是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);

这样做非常不方便,相比于简单的Create后跟一系列对Add方法的调用,你永远不会更喜欢这个选项。只有在手头上恰好有现成的集合时,你才会使用传入现有集合的选项。

另一个从TEnumerable<T>继承的类是TDictionary本身:

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

如果您已经有一个字典实例,您可以创建另一个实例,并使用第一个实例的内容进行初始化:

Dict2 := TDictionary<string, Integer>.Create(Dict1);

1
只有两种构造函数重载可在构造期间填充数据。它们都接受一个类型为 TEnumerable<TPair<TKey, TValue>> 的类实例。您可以编写一个辅助函数来创建其中之一,以便进行内联编码。这与 Python 版本相距甚远:Dictionary = {'Foo': 42, 'Bar': 666} - David Heffernan
1
实际上,你甚至不能轻易地在这里编写一个帮助函数,因为你需要释放传递给构造函数的临时容器。所以,是的,这个类的设计对你不利。这是一种情况,其他一些容器库可能更加灵活。我不知道它,但我听说 Delphi Spring 库中的容器有很多好处。 - David Heffernan
@Cosmin TDictionary 构造函数接收一个 TEnumerable<...> 类型的对象引用,因此这就是你需要传递的内容。你不能在构造函数的调用中内联构造一个。 - David Heffernan
1
@David 真遗憾它不能同时接受 IEnumerable<> - Cosmin Prund
1
@David,正如我在答案中展示的那样,一个由一对组成的开放数组很难以可读的方式实例化。这就是为什么我使用了with运算符,这样我就可以使用帮助器P类函数。在Delphi中没有语法来初始化一个漂亮的单行记录,你需要调用一个构造函数。 - Cosmin Prund
显示剩余5条评论

1
在下面的示例中,将一个键和值数组传递给自定义构造函数。使用模式key1,value1,key2,value2,....,keyN,valueN将键和值放置在同一数组中。该数组必须包含偶数项。
unit MainUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Generics.Collections, System.Rtti;


type
  TForm3 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TMyDictionary<TK, TV> = class(TDictionary<TK,TV>)
    constructor Create(const values: array of variant);
  end;

var
  Form3: TForm3;
  extensions: TMyDictionary<string, integer>;

implementation

constructor TMyDictionary<TK, TV>.Create(const values: array of variant);
var
  I: Integer;
  k, v: TValue;
  kt: TK;
  vt: TV;
begin
  inherited Create(Length(values) div 2);
  I := Low(values);
  while i <= High(values)  do
  begin
    k := TValue.FromVariant(values[i]);
    v := TValue.FromVariant(values[i + 1]);
    kt := k.AsType<TK>;
    vt := v.AsType<TV>;
    Add(kt, vt);
    Inc(I, 2);
  end;

end;

{$R *.dfm}
begin

 extensions := TMyDictionary<string, integer>.Create(['1', 1, '3', 3]);

 OutputDebugString(PChar(IntToStr(extensions['1'])));
end.

我不太确定TValue方法的性能,但如果你只有几个项目,我认为它是可以忽略的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接