如何使用数组作为值在 Pascal 中创建关联数组

How to create an associative array in Pascal using arrays for the values

我有这个文件:

Bulgaria = Bulgarian
Croatia = Croatian
Austria = Croatian
Czech Republic = Czech
Slovakia = Czech
Denmark = Danish
Germany = Danish
Belgium = Dutch
Netherlands = Dutch
Ireland = English
Malta = English
United Kingdom = English
Estonia = Estonian
Finland = Finnish
Belgium = French
France = French
Italy = French
Luxembourg = French
Austria = German
Belgium = German
Denmark = German
Germany = German
Italy = German
Luxembourg = German
Cyprus = Greek
Greece = Greek
Austria = Hungarian
Hungary = Hungarian
Romania = Hungarian
Slovakia = Hungarian
Slovenia = Hungarian
Ireland = Irish
United Kingdom = Irish
Croatia = Italian
Italy = Italian
Slovenia = Italian
Latvia = Latvian
Lithuania = Lithuanian
Malta = Maltese
Poland = Polish
Portugal = Portuguese
Romania = Romanian
Slovakia = Slovak
Czech Republic = Slovak
Hungary = Slovak
Slovenia = Slovenian
Austria = Slovenian
Hungary = Slovenian
Italy = Slovenian
Spain = Spanish
Sweden = Swedish
Finland = Swedish

在 python 中,我使用此代码创建一个关联数组,其中一个字符串作为键,一个数组作为值:

from collections import defaultdict
mydata = defaultdict(list)
myfile = open("myfile", "r")
for line in myfile:
    country, language = line.rstrip('\n').split(" = ")
    mydata[country].append(language)

它创建了一个这样的数据结构:

'Bulgaria' = ['Bulgarian']
'Croatia' = ['Croatian']
'Austria' = ['Croatian', 'German', 'Hungarian', 'Slovenian']
# and so on

Perl 和 Ruby 有相似的关联数组。 Go 可以用 maps 和 append().

创建它

我在 Pascal 中看到很多对关联数组的引用,但我找不到使用 数组作为值.

的示例

我正在使用 FreePascal,我想避免使用外部库。你能举个例子吗?

PS:我知道这看起来像作业,但不是。

下面是演示程序。

打开 Lazarus,创建一个应用程序,然后将单元 fgl 添加到表单单元的 uses 子句中:

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, fgl;

在表单中添加一个 TButton (Button1) 和一个 TMemo (Memo1) 并为按钮提供以下代码:

{ Can't find anything suitable in the Lazarus runtime. I'm sure this }
{ can be improved.                                                   }
function Strip(const S: string): string;
var
  left, right: Integer;
begin
  if S = '' then
  begin
    Result := S;
    Exit;
  end;
  left := 1;
  while S[left] in [' ', #9] do
    Inc(left);
  right := Length(S);
  while (right > 0) and (S[right] in [' ', #9]) do
    Dec(right);
  Result := Copy(S, left, right - left + 1);
end;

type
  TMap = specialize TFPGMap<string, TStringList>;

procedure TAssocForm.Button1Click(Sender: TObject);
var
  mydata: TMap;
  myfile: Text;
  line: string;
  country: string;
  language: string;
  mypos: Integer;
  SL: TStringList;
  I: Integer;
begin
  { Error handling needs to be added, e.g. if file doesn't exist, or if 
    a line doesn't contain an =, etc. etc. }
  mydata := TMap.Create;

  { Open file 'myfile.txt' for reading. }
  System.Assign(myfile, '/Users/xxx/yyy/myfile.txt'); { adjust accordingly }
  Reset(myfile);

  { Read lines. }
  while not Eof(myfile) do
  begin
    Readln(myfile, line);
    mypos := Pos('=', line);

    { Split line into country and language. }
    country := Strip(Copy(line, 1, mypos - 1));
    language := Strip(Copy(line, mypos + 1, MaxInt));

    { If key not present yet, add a new string list. }
    if mydata.IndexOf(country) < 0 then
      mydata.Add(country, TStringList.Create);

    { add language to string list of country. }
    mydata[country].Add(language);
  end;
  System.Close(myfile);

  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;
  for I := 0 to mydata.Count - 1 do
  begin
    { Get key. }
    country := mydata.Keys[I];
    line := country + ' -> ';

    { Get string list. }
    SL := mydata[country];

    { Get languages in the string list. }
    for language in SL do
      line := line + language + ' ';

    { Add line to memo. }
    Memo1.Lines.Add(Strip(line));
  end;
  Memo1.Lines.EndUpdate;

  { Free the string lists. }
  for I := 0 to mydata.Count - 1 do
    mydata[mydata.Keys[I]].Free;
end;

end.

运行 程序并单击按钮。备忘录将填满国家和那里使用的语言,例如

Bulgaria -> Bulgarian 
Croatia -> Croatian Italian 
Austria -> Croatian German Hungarian Slovenian 
Czech Republic -> Czech Slovak 
Slovakia -> Czech Hungarian Slovak 
etc...

数组

Pascal (Delphi) 语言在该语言中没有关联数组。只有具有序数类型索引的数组(即整数,而不是字符串或浮点数)。有多维数组,最近还有动态数组,但索引无论如何都是有序的。

但是,标准库单元(Classes.pas 和 System.Generics.Collections.pas)提供 class 实现您所说的功能的实体。

您可以使用新的 Delphi 泛型,尤其是 TDictionary,用于真正的关联数组,或者使用旧的 TStringList,仅用于普通字符串列表。 TStringList class 已在 Delphi 的较新版本中使用 Name+Delimiter+Value 功能进行了扩展。对于对,TStringList 比 Delphi 泛型的 TDictionary 慢,因为它只是在内部存储纯字符串并即时解析它们。然而,泛型使用高效的结构来快速添加和删除项目,使用值的散列,因此速度非常快。在 TStringList 中,相反,随机插入和删除很慢 - O(N),但通过索引获取字符串是瞬间的 - O(1)。

泛型

uses
  System.Generics.Collections,

procedure TestGenerics;
type
  TKey = string;
  TValue = string;
  TKeyValuePair = TPair<TKey, TValue>;
  TStringDictionary  = TDictionary<TKey, TValue>;

var
  D: TStringDictionary;
  K: TKey;
  V: TValue;
  P: TKeyValuePair;
  ContainsKey,  ContainsValue: Boolean;
begin
  D := TStringDictionary.Create;
  D.Add('Bulgaria', 'Bulgarian');
  D.Add('Croatia', 'Croatian Italian');
  K := D.Items['Bulgaria'];
  P := D.ExtractPair('Bulgaria');
  ContainsKey := D.ContainsKey('Bultaria');
  ContainsValue := D.ContainsValue('Bultarian');
  // you do not need to free P, since it is just a record in the stack
  D.Free;
end;

字符串列表

请注意,Delphi的作者称它为Name+Value对,而不是Key+Value对,因为在TStringList中,这些实际上不是"Keys"的快速访问方式, 它只是同一个字符串的一部分。它们没有特殊的排序索引功能 - 如果您愿意,只需常规排序的 TStringList。

另请注意,当 TStringList 对象包含名称-值对或名称字符串时,请阅读键以访问字符串的名称部分。如果字符串不是名称-值对,Keys returns 完整字符串。分配键将为名称-值对写入新名称。这与名称 属性.

相反

此外,考虑到 TStringList 使用连续内存 space 来保存指向字符串数据的指针,因此当您添加新字符串时,它会使用预分配的 space更多条目,但随后分配一个新的更大的内存块并将旧指针复制到新指针,释放一个旧块。因此,如果您事先知道项目的数量,最好将该数字告诉 TStringList 以便它一劳永逸地预先分配缓冲区。这不会阻止以后扩大缓冲区,如果你需要更多的项目。

uses
  Classes;

procedure TestStringList;
var
  SL: TStringList;
  FullString, Separator, FName, FValue: string;
begin
  SL := TStringList.Create;
  SL.AddPair('Bulgaria', 'Bulgarian'); // add a Name -> Value pair
  SL.AddPair('Croatia', 'Croatian Italian');

  // Names and KeyNames is the same
  FName := SL.Names[0]; // Indicates the name part of strings that are name-value pairs.
  FName := SL.KeyNames[0];
  FValue := SL.Values['Bulgaria']; // Represents the value part of a string associated with a given name, on strings that are name-value pairs.
  FValue := SL.ValueFromIndex[0]; // Represents the value part of a string with a given index, on strings that are name-value pairs.

  FullString := SL.Strings[0]; // References the strings in the list by their positions (the whole Name+Separator+Value pair)
  Separator := SL.NameValueSeparator; // Indicates the character used to separate names from values.

  SL.Free;
end;