Christian
Christian

Reputation: 35

Reading String from a text file into Array in Pascal

With this program, I am trying to read a file and randomly print it to console. I am wondering If I have to use arrays for that. For example, I could assign my strings into an array, and randomly print from my array. But, I'm not sure how to approach to that. Also another problem is that, my current program does not read the first line from my file. I have a text file text.txt that contains

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

And below is my code.

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.

Please let me know how I could approach this problem!

Upvotes: 2

Views: 5205

Answers (3)

Andreas Rejbrand
Andreas Rejbrand

Reputation: 108938

There are a few issues with your program.

  1. Your first Readln reads the first line of the file into s, but you don't use this value at all. It is lost. The first time you do a Readln in the loop, you get the second line of the file (which you do print to the console using Writeln).

  2. Your arr record type is completely meaningless in this case (and in most cases), since it is a record without any members. It cannot store any data, because it has no members.

  3. In your loop, you expand the length of the array, one item at a time. But you don't set the new item's value to anything, so you do this in vain. (And, because of the previous point, there isn't any value to set in any case: the elements of the array are empty records that cannot contain any data.)

  4. Increasing the length of a dynamic array one item at a time is very bad practice, because it might cause a new heap allocation each time. The entire existing array might need to be copied to a new location in your computer's memory, every time.

  5. The contents of the loop seem to be trying to do two things: saving the current line in the array, and printing it to the console. I assume the latter is only for debugging?

  6. Old-style Pascal I/O (text, Assign, Reset) is obsolete. It is not thread-safe, possibly slow, handles Unicode badly, etc. It was used in the 90s, but shouldn't be used today. Instead, use the facilities provided by your RTL. (In Delphi, for instance, you can use TStringList, IOUtils.TFile.ReadAllLines, streams, etc.)


A partly fixed version of the code might look like this (still using old-school Pascal I/O and the inefficient array handling):

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.

If you want to fix the inefficient array issue, but still not use modern classes, allocate in chunks:

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.

But if you have access to modern classes, things get much easier. The following examples use the modern Delphi RTL:

The generic TList<T> handles efficient expansion automatically:

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.

But you could simply use a 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.

Or you could keep the array approach and use 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.

As you can see, the modern approaches are much more convenient (less code). They are also faster and give you Unicode support.


Note: All snippets above assume that the file contains at least a single line. They will fail if this is not the case, and in real/production code, you must verify this, e.g. like

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

or

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

before the // Print strings randomly part, which assumes that the array/list isn't empty.

Upvotes: 10

Kevin Ng
Kevin Ng

Reputation: 2174

I edited the answer as I read your question clearly.

Nonetheless, for your reading an extra line issue, you happened to read a line before going into your read loop. So this is your program from the begin to the end statement without that extra readln().

For this code the routine is simple and there is actually more than one method that I can think of. For the first method, you can read each line into an array. Then traverse through the array and create a random number of 0 or 1. If it is 1 print that line.

For the second method, each time read a line from the file, generate a random number of 0 or 1. If that random number is a 1, print that line.

Take note to use randomize before you run random() to not get the same random number of the last program execution. Another thing to take into consideration, if you going on a large text file, each set length can cost a lot. It is better to keep track of what going in there and set 20 - 30 length as one or even 100. That is if you going with the array route.

The code below is for the array method, for the method of not using an array, it is very simple once you see the routines below.

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.

Upvotes: 0

LU RD
LU RD

Reputation: 34899

Also another problem is that, my current program does not read the first line from my file.

Yes it does. But you don't write it to the console. See the third line, readln(x, s);

I am trying to read a file and randomly print it to console. I am wondering If I have to use arrays for that.

Yes that is a sound approach.

Instead of using an array of a record, just declare:

myArray : array of string;

To get a random value from the array, use Randomize to initialize the random generator, and Random() to get a random index.

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.

Upvotes: 1

Related Questions