Reputation: 35
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
Reputation: 108938
There are a few issues with your program.
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
).
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.
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.)
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.
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?
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
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
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