如何在Pascal中使用数组创建关联数组以用于存储值

6

我有一个文件:

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:我知道这看起来像作业,但它不是。


2
请查看单元fgl中的TFPGMap。键是字符串,值是动态的string数组。向这样的数组添加数据与Python中不同,但它们的工作方式相似。 - Rudy Velthuis
2个回答

6

数组

Pascal(Delphi)语言中没有关联数组。只有带有序数类型索引(即整数,而不是字符串或浮点数)的数组。虽然有多维数组和最近的动态数组,但索引仍然是序数。

然而,标准库单元(Classes.pas和System.Generics.Collections.pas)提供了实现您所说功能的类。

您可以使用新的Delphi Generics,特别是TDictionary,用于真正的关联数组,或者使用老牌的TStringList,仅用于普通的字符串列表。 TStringList类已在Delphi的新版本中扩展了Name+Delimiter+Value功能。 对于成对出现的内容,与Delphi Generics的TDictionary相比,TStringList速度较慢,因为它只在内部存储普通字符串,并在运行时解析它们。 然而,Generics使用高效的结构快速添加和删除项,使用值的哈希,因此非常快速。 相反,在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中,它们实际上不是快速访问的"键",只是同一字符串的一部分。它们没有特殊的排序索引功能,如果你希望使用有序的TStringList,则只有普通的排序。

还要注意,当TStringList对象包含名称-值对或只有名称的字符串时,请使用Keys属性访问字符串的名称部分。如果该字符串不是名称-值对,则Keys返回完整字符串。赋值Keys将为名称-值对编写新名称,这与Names属性相反。

此外,请注意,TStringList使用连续的内存空间来保存指向字符串数据的指针,因此当您添加新字符串时,它会使用预分配的空间进行几个更多的条目,然后分配一个新的较大内存块,并将旧指针复制到新的内存块中,释放旧块。因此,如果您事先知道项目数量,最好告诉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;

1
谢谢帮忙。不过,Rudy的例子更适合我的使用场景。 - Mariano R.

4

以下是一个演示程序。

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

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...

这个例子帮了我很多,谢谢! - Mariano R.

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