【问题标题】:How to create an associative array in Pascal using arrays for the values如何使用值的数组在 Pascal 中创建关联数组
【发布时间】:2017-10-12 05:51:08
【问题描述】:

我有这个文件:

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 可以使用地图和append() 创建它。

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

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

PS:我知道这看起来像家庭作业,但事实并非如此。

【问题讨论】:

  • fgl单元中查看TFPGMap。键是字符串,值是动态的array of string。向这样的数组添加数据与在 Python 中不同,它们的工作方式相似。

标签: arrays associative-array pascal freepascal


【解决方案1】:

下面是一个演示程序。

打开Lazarus,创建一个Application,将单元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...

【讨论】:

  • 这个例子对我帮助很大。谢谢!
【解决方案2】:

数组

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

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

您可以将新的 Delphi 泛型,尤其是 TDictionary 用于真正的关联数组,或者将旧的 TStringList 用于纯字符串列表。 TStringList 类已在较新版本的 Delphi 中扩展了 Name+Delimiter+Value 功能。对于这些对,TStringList 比 Delphi Generics 的 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 中,这些实际上并不是快速访问方式中的“键”,它们只是相同的部分细绳。它们没有特殊的排序索引功能 - 如果您愿意,只需常规排序的 TStringList。

还要注意,当 TStringList 对象包含名称-值对或只是名称的字符串时,请阅读 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;

【讨论】:

  • 感谢您的帮助。不过,Rudy 的示例更适合我的用例。
猜你喜欢
  • 2020-04-04
  • 1970-01-01
  • 2012-11-22
  • 2016-07-05
  • 2012-04-30
  • 2017-08-02
  • 2012-03-19
  • 2017-04-05
  • 1970-01-01
相关资源
最近更新 更多