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

时间:2023-02-07 12:48:25

I've this file:

我有这个文件:

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

In python, I use this code to create an associative array with a string as key and an array as value:

在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)

It creates a data structure like this:

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

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

Perl and Ruby has similar associative arrays. Go can create it with maps and append().

Perl和Ruby有类似的关联数组。 Go可以使用maps和append()创建它。

I see a lot of references to associative arrays in Pascal but I can't find examples of use with arrays as values.

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

I'm using FreePascal and I would like avoid external libraries. Can you show me an example for this?.

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

PS: I know this looks like homework but it's not.

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

2 个解决方案

#1


4  

A demo program follows.

接下来是演示程序。

Open Lazarus, create an Application, and add the unit fgl to the uses clause of the unit for the form:

打开Lazarus,创建一个Application,并将单元fgl添加到单元的uses子句中:

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

Add a TButton (Button1) and a TMemo (Memo1) to the form and give the button the following code:

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

Run the program and click the button. The memo will be filled with the countries and the languages that are spoken there, e.g.

运行该程序,然后单击按钮。备忘录将填写国家和那里所说的语言,例如:

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

#2


4  

Arrays

Pascal (Delphi) language doesn't have associative arrays in the language. There are just arrays with an index of an ordinal type (i.e. integer, not string or a floating-point number). There are multi-dimensional arrays and, recently, dynamic arrays, but the index is ordinal anyway.

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

However, standard library units (Classes.pas and System.Generics.Collections.pas) provide classes that implement the functionality you are speaking about.

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

You can use new Delphi Generics, particularly TDictionary, for truly associative arrays, or the good old TStringList, just for plain string lists. The TStringList class have been extended with the Name+Delimiter+Value functionality in newer versions of Delphi. For the pairs, the TStringList is slower than TDictionary of Delphi Generics, because it just stores plain strings inside and parses them on-the-fly. The Generics, however, use efficient structures that add and remove items quickly, use hashes of the values, and thus are very fast. In the TStringList, to the contrary, random insertions and deletions are slow - O(N), but getting a string by index is momentary - O(1).

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

Generics

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;

String List

Please note that the authors of Delphi call it Name+Value pairs, not Key+Value pairs, because, in the TStringList, these are not actually "Keys" in the means of fast access, it is just parts of the same string. They don't have special sorted indexing capabilities - just regular sorted TStringList if you wish.

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

Also be aware that when the TStringList object includes strings that are name-value pairs or just names, read Keys to access the name part of a string. If the string is not a name-value pair, Keys returns full string. Assigning Keys will write new name for name-value pair. This is in contrast to Names property.

另请注意,当TStringList对象包含名称 - 值对或仅名称的字符串时,请读取键以访问字符串的名称部分。如果字符串不是名称 - 值对,则Keys返回完整字符串。分配键将为名称 - 值对写入新名称。这与Names属性形成对比。

Also, take into consideration, that TStringList uses continuous memory space to hold pointers to the string data, so when you add new strings, it uses a pre-allocated space for a few more entries, but then allocates a new larger memory block and copies old pointers to the new one, releasing an old block. So, if you know in advance the number of items, it's better to tell that number to TStringList to it would pre-allocate the buffer once and for all. That would not prevent from enlarging the buffer later, should you need more items then.

另外,请注意,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


4  

A demo program follows.

接下来是演示程序。

Open Lazarus, create an Application, and add the unit fgl to the uses clause of the unit for the form:

打开Lazarus,创建一个Application,并将单元fgl添加到单元的uses子句中:

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

Add a TButton (Button1) and a TMemo (Memo1) to the form and give the button the following code:

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

Run the program and click the button. The memo will be filled with the countries and the languages that are spoken there, e.g.

运行该程序,然后单击按钮。备忘录将填写国家和那里所说的语言,例如:

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

#2


4  

Arrays

Pascal (Delphi) language doesn't have associative arrays in the language. There are just arrays with an index of an ordinal type (i.e. integer, not string or a floating-point number). There are multi-dimensional arrays and, recently, dynamic arrays, but the index is ordinal anyway.

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

However, standard library units (Classes.pas and System.Generics.Collections.pas) provide classes that implement the functionality you are speaking about.

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

You can use new Delphi Generics, particularly TDictionary, for truly associative arrays, or the good old TStringList, just for plain string lists. The TStringList class have been extended with the Name+Delimiter+Value functionality in newer versions of Delphi. For the pairs, the TStringList is slower than TDictionary of Delphi Generics, because it just stores plain strings inside and parses them on-the-fly. The Generics, however, use efficient structures that add and remove items quickly, use hashes of the values, and thus are very fast. In the TStringList, to the contrary, random insertions and deletions are slow - O(N), but getting a string by index is momentary - O(1).

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

Generics

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;

String List

Please note that the authors of Delphi call it Name+Value pairs, not Key+Value pairs, because, in the TStringList, these are not actually "Keys" in the means of fast access, it is just parts of the same string. They don't have special sorted indexing capabilities - just regular sorted TStringList if you wish.

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

Also be aware that when the TStringList object includes strings that are name-value pairs or just names, read Keys to access the name part of a string. If the string is not a name-value pair, Keys returns full string. Assigning Keys will write new name for name-value pair. This is in contrast to Names property.

另请注意,当TStringList对象包含名称 - 值对或仅名称的字符串时,请读取键以访问字符串的名称部分。如果字符串不是名称 - 值对,则Keys返回完整字符串。分配键将为名称 - 值对写入新名称。这与Names属性形成对比。

Also, take into consideration, that TStringList uses continuous memory space to hold pointers to the string data, so when you add new strings, it uses a pre-allocated space for a few more entries, but then allocates a new larger memory block and copies old pointers to the new one, releasing an old block. So, if you know in advance the number of items, it's better to tell that number to TStringList to it would pre-allocate the buffer once and for all. That would not prevent from enlarging the buffer later, should you need more items then.

另外,请注意,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;