【问题标题】:Reading String from a text file into Array in Pascal将文本文件中的字符串读入 Pascal 中的数组
【发布时间】:2019-10-09 05:18:27
【问题描述】:

使用这个程序,我正在尝试读取文件并将其随机打印到控制台。我想知道是否必须为此使用数组。例如,我可以将我的字符串分配到一个数组中,并从我的数组中随机打印。但是,我不确定如何解决这个问题。另一个问题是,我当前的程序没有从我的文件中读取第一行。我有一个文本文件text.txt,其中包含

1. ABC
2. ABC
...
6. ABC

下面是我的代码。

type
  arr = record 
  end;

var
  x: text;
  s: string;
  SpacePos: word;
  myArray: array of arr;
  i: byte;

begin
  Assign(x, 'text.txt');
  reset(x);
  readln(x, s); 
  SetLength(myArray, 0);
  while not eof(x) do
  begin
    SetLength(myArray, Length(myArray) + 1);
    readln(x, s);
    WriteLn(s);
  end;
end.

请告诉我如何解决这个问题!

【问题讨论】:

    标签: delphi pascal freepascal


    【解决方案1】:

    您的程序存在一些问题。

    1. 您的第一个Readln 将文件的第一行读入s,但您根本不使用此值。它丢失了。第一次在循环中执行Readln 时,您会得到文件的第二行(使用Writeln 将其打印到控制台)。

    2. 您的arr 记录类型在这种情况下(在大多数情况下)完全没有意义,因为它是没有任何成员的记录。它不能存储任何数据,因为它没有成员。

    3. 在您的循环中,您扩展数组的长度,一次一项。但是您没有将新项目的值设置为任何值,因此您这样做是徒劳的。 (而且,由于前面的一点,在任何情况下都不需要设置任何值:数组的元素是不能包含任何数据的空记录。)

    4. 每次增加一个动态数组的长度是very bad practice,因为它可能每次都会导致新的堆分配。每次都可能需要将整个现有阵列复制到计算机内存中的新位置。

    5. 循环的内容似乎试图做两件事:将当前行保存在数组中,并将其打印到控制台。我假设后者仅用于调试?

    6. 旧式 Pascal I/O(textAssignReset)已过时。它不是线程安全的,可能很慢,处理 Unicode 不好等等。它在 90 年代使用过,但今天不应该使用。相反,请使用 RTL 提供的设施。 (例如,在 Delphi 中,您可以使用 TStringListIOUtils.TFile.ReadAllLines、流等)


    部分固定的代码版本可能如下所示(仍然使用老式的 Pascal I/O 和低效的数组处理):

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils;
    
    var
      x: text;
      arr: array of string;
    
    begin
    
      // Load file to string array (old and inefficient way)
      AssignFile(x, 'D:\test.txt');
      Reset(x);
      try
        while not Eof(x) do
        begin
          SetLength(arr, Length(arr) + 1);
          Readln(x, arr[High(Arr)]);
        end;
      finally
        CloseFile(x);
      end;
    
      Randomize;
    
      // Print strings randomly
      while True do
      begin
        Writeln(Arr[Random(Length(Arr))]);
        Readln;
      end;
    
    end.
    

    如果您想解决数组效率低下的问题,但仍不使用现代类,请分块分配:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils;
    
    var
      x: text;
      s: string;
      arr: array of string;
      ActualLength: Integer;
    
    
      procedure AddLineToArr(const ALine: string);
      begin
        if Length(arr) = ActualLength then
          SetLength(arr, Round(1.5 * Length(arr)) + 1);
        arr[ActualLength] := ALine;
        Inc(ActualLength);
      end;
    
    begin
    
      SetLength(arr, 1024);
      ActualLength := 0; // not necessary, since a global variable is always initialized
    
      // Load file to string array (old and inefficient way)
      AssignFile(x, 'D:\test.txt');
      Reset(x);
      try
        while not Eof(x) do
        begin
          Readln(x, s);
          AddLineToArr(s);
        end;
      finally
        CloseFile(x);
      end;
    
      SetLength(arr, ActualLength);
    
      Randomize;
    
      // Print strings randomly
      while True do
      begin
        Writeln(Arr[Random(Length(Arr))]);
        Readln;
      end;
    
    end.
    

    但是,如果您可以使用现代课程,事情就会变得容易得多。以下示例使用现代 Delphi RTL:

    通用TList<T> 自动处理高效扩展:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils, Generics.Defaults, Generics.Collections;
    
    var
      x: text;
      s: string;
      list: TList<string>;
    
    begin
    
      list := TList<string>.Create;
      try
    
        // Load file to string array (old and inefficient way)
        AssignFile(x, 'D:\test.txt');
        Reset(x);
        try
          while not Eof(x) do
          begin
            Readln(x, s);
            list.Add(s);
          end;
        finally
          CloseFile(x);
        end;
    
        Randomize;
    
        // Print strings randomly
        while True do
        begin
          Writeln(list[Random(list.Count)]);
          Readln;
        end;
    
      finally
        list.Free;
      end;
    
    end.
    

    但你可以简单地使用TStringList:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils, Classes;
    
    var
      list: TStringList;
    
    begin
    
      list := TStringList.Create;
      try
    
        list.LoadFromFile('D:\test.txt');
    
        Randomize;
    
        // Print strings randomly
        while True do
        begin
          Writeln(list[Random(list.Count)]);
          Readln;
        end;
    
      finally
        list.Free;
      end;
    
    end.
    

    或者您可以保留数组方法并使用IOUtils.TFile.ReadAllLines

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils, IOUtils;
    
    var
      arr: TArray<string>;
    
    begin
    
      arr := TFile.ReadAllLines('D:\test.txt');
    
      Randomize;
    
      // Print strings randomly
      while True do
      begin
        Writeln(arr[Random(Length(arr))]);
        Readln;
      end;
    
    end.
    

    如您所见,现代方法更方便(代码更少)。它们也更快,并为您提供 Unicode 支持。


    注意: 上面所有的 sn-ps 都假定文件至少包含一行。如果不是这种情况,它们将失败,并且在实际/生产代码中,您必须验证这一点,例如喜欢

      if Length(arr) = 0 then
        raise Exception.Create('Array is empty.');
    

      if List.Count = 0 then
        raise Exception.Create('List is empty.');
    

    // Print strings randomly 部分之前,假设数组/列表不为空。

    【讨论】:

    • 注意:我不知道 FreePascal,但我 100% 确信它的 RTL 包含与我提到的现代 Delphi 工具等效的类/记录/例程。也许它们的名字甚至相似(例如TStringList)。
    • Generics.* 单元仅在主干中工作(或作为发布版本的单独下载)。只有少数新单位有命名空间前缀,大的重命名还没有发生。其余的一切都应该可以正常工作。
    • @MarcovandeVoort:太好了,感谢您的确认!所以 FreePascal 用户需要写 SysUtils 而不是 System.SysUtils,如果我没记错的话。
    【解决方案2】:

    另外一个问题是,我当前的程序没有从我的文件中读取第一行。

    是的。但是您不会将其写入控制台。见第三行,readln(x, s);

    我正在尝试读取文件并将其随机打印到控制台。我想知道我是否必须为此使用数组。

    是的,这是一种合理的方法。

    不使用记录数组,只需声明:

    myArray : array of string;
    

    要从数组中获取随机值,请使用Randomize 初始化随机生成器,使用Random() 获取随机索引。

    var
      x: text;
      myArray: array of String;
      ix: Integer;
    begin
      Randomize;  // Initiate the random generator
      Assign(x, 'text.txt');
      reset(x);
      ix := 0; 
      SetLength(myArray, 0);
      while not eof(x) do
      begin
        SetLength(myArray, Length(myArray) + 1);
        readln(x, myArray[ix]);
        WriteLn(myArray[ix]);
        ix := ix + 1;
      end;
      WriteLn('Random line:');
      WriteLn(myArray[Random(ix)]);  // Random(ix) returns a random number 0..ix-1
    end.
    

    【讨论】:

    • 当然,在生产代码中,您不会使用旧式 Pascal I/O,不会每次增加一个动态数组的元素,或者省略 CloseFile。 (但我知道你知道!)
    • @Andreas,作为新手,我宁愿学习爬行也不愿全速奔跑。引入高级库来解决这个问题很好,但可能会妨碍学习语言的基本部分。有一个平衡,但我认为你的答案涵盖了所有部分。
    【解决方案3】:

    当我清楚地阅读您的问题时,我编辑了答案。

    尽管如此,对于您阅读额外行的问题,您碰巧在进入阅读循环之前阅读了一行。所以这是你的程序从开始到结束的语句,没有额外的 readln()。

    对于这段代码,例程很简单,实际上我能想到的方法不止一种。对于第一种方法,您可以将每一行读入一个数组。然后遍历数组并创建一个随机数 0 或 1。如果是 1,则打印该行。

    对于第二种方法,每次从文件中读取一行,生成一个随机数 0 或 1。如果该随机数是 1,则打印该行。

    请注意在运行 random() 之前使用 randomize,以免获得与上次程序执行相同的随机数。要考虑的另一件事是,如果您处理大型文本文件,则每个设置的长度可能会花费很多。最好跟踪其中的内容并将 20 - 30 长度设置为 1 甚至 100。那就是如果您使用数组路由。

    下面的代码是数组方法,不使用数组的方法,看下面的例程就很简单了。

    var
      x: text;
      SpacePos: word;
      myArray: array of string;
      i: integer;
    
    begin
      Randomize;
      Assign(x, 'text.txt');
      reset(x);
      SetLength(myArray, 0);
      i := 0;
    
      while not eof(x) do
      begin
        SetLength(myArray, Length(myArray) + 1);
        readln(x, myArray[i]);
        i := i + 1;
      end;
    
      for i:= 0 to Length(myArray) - 1 do
      begin
        if random(2) = 1 then
          WriteLn(myArray[i]);
      end;
    
    end.
    

    【讨论】:

    • 但是代码仍然不能满足 OP 的要求。例如,你不填充数组(你不能,因为元素类型没有成员)。
    • 感谢您的评论,我有点跳过这个问题。明白了,谢谢。
    猜你喜欢
    • 1970-01-01
    • 2017-09-12
    • 2017-03-08
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多