Чтение строки из текстового файла в массив в Pascal - PullRequest
1 голос
/ 09 октября 2019

С помощью этой программы я пытаюсь прочитать файл и случайно распечатать его на консоли. Мне интересно, если я должен использовать массивы для этого. Например, я мог бы назначить свои строки в массив и распечатать случайным образом из моего массива. Но я не уверен, как подойти к этому. Также другая проблема заключается в том, что моя текущая программа не читает первую строку из моего файла. У меня есть текстовый файл 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.

Пожалуйста, дайте мне знать, как я могу решить эту проблему!

Ответы [ 3 ]

8 голосов
/ 09 октября 2019

Есть несколько проблем с вашей программой.

  1. Ваш первый Readln читает первую строку файла в s, но вы не используете это значение ввсе. Это потеряно. Когда вы в первый раз делаете Readln в цикле, вы получаете вторую строку файла (которую вы печатаете на консоли, используя Writeln).

  2. Ваш arr тип записи в данном случае (и в большинстве случаев) совершенно не имеет смысла, так как это запись без каких-либо членов. Он не может хранить никакие данные, потому что у него нет членов.

  3. В вашем цикле вы увеличиваете длину массива, по одному элементу за раз. Но вы не устанавливаете значение нового элемента на что-либо, поэтому вы делаете это напрасно. (И из-за предыдущего пункта в любом случае значение не устанавливается: элементы массива являются пустыми записями, которые не могут содержать никаких данных.)

  4. Увеличениедлина динамического массива по одному элементу за раз очень плохая практика , потому что это может вызывать новое выделение кучи каждый раз. Весь существующий массив может потребоваться каждый раз копировать в новое место в памяти вашего компьютера.

  5. Кажется, что содержимое цикла пытается сделать две вещи: сохранить текущуюстрока в массиве, и печать его на консоль. Я полагаю, что последний предназначен только для отладки?

  6. Старый ввод / вывод Pascal (text, Assign, Reset) устарел. Он не является поточно-ориентированным, возможно медленным, плохо обрабатывает Unicode и т. Д. Он использовался в 90-х годах, но не должен использоваться сегодня. Вместо этого используйте средства, предоставляемые вашим RTL. (Например, в Delphi вы можете использовать TStringList, IOUtils.TFile.ReadAllLines, потоки и т. Д.)


Частично исправленная версия кода может выглядеть следующим образом (все еще использующий ввод-вывод старой школы Pascal и неэффективную обработку массивов):

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.

Но если у вас есть доступ к современным классам, все становится намного проще. В следующих примерах используется современный RTL Delphi:

Общий 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.

Как видите, современные подходы намного удобнее (меньше кода). Они также работают быстрее и обеспечивают поддержку Юникода.


Примечание: Все приведенные выше фрагменты предполагают, что файл содержит хотя бы одну строку. Они потерпят неудачу, если это не так, и в реальном / производственном коде вы должны проверить это, например, как

  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 часть, которая предполагает , что массив / список не пуст.

1 голос
/ 09 октября 2019

Также другая проблема заключается в том, что моя текущая программа не читает первую строку из моего файла.

Да, это так. Но вы не пишете это на консоль. См. Третью строку, 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.
0 голосов
/ 09 октября 2019

Я отредактировал ответ, когда четко прочитал ваш вопрос.

Тем не менее, для чтения дополнительной строки вы случайно прочитали строку перед тем, как перейти к циклу чтения. Так что это ваша программа от оператора начала до конца без этого дополнительного readln ().

Для этого кода процедура проста, и на самом деле я могу придумать несколько методов. Для первого метода вы можете прочитать каждую строку в массив. Затем просмотрите массив и создайте случайное число 0 или 1. Если это 1, выведите эту строку.

Для второго метода каждый раз, когда читается строка из файла, генерируется случайное число 0 или 1. Если это случайное число равно 1, выведите эту строку.

Обратите внимание на использование randomize перед запуском random (), чтобы не получить одно и то же случайное число последнего выполнения программы. Еще одна вещь, которую стоит принять во внимание, если вы собираетесь использовать большой текстовый файл, каждая длина набора может стоить дорого. Лучше следить за тем, что происходит там, и установить длину 20-30 как единицу или даже 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.
...