I'm work on a program for school (Cinema app) but I have a problem with my array. My app closed but nothing is showed.
program TFE;
{$APPTYPE CONSOLE}
uses
SysUtils,
StrUtils,
Crt;
var
MovieList, MovieInfo: Text;
Choice: Byte;
i: Integer;
L: String;
S: array of String[14];
begin
i := 0
Assign(MovieInfo, 'MovieInfo.txt');
Reset(MovieInfo);
Readln(Choice);
i := 0;
ClrScr;
While not eof (MovieInfo) do
begin
Readln(MovieInfo, L);
S[i] := L;
i := i + 1;
end;
Writeln(S[Choice]);
Readln;
end.
It's all my code for the moment.
Somebody can help me ?
In the title you speak about a variable MyVar, but the code doesn't show any such variable. For future reference, please carefully proof read your question before posting.
You have declared a dynamic array:
S: array of String[14];
that is, an array of 14 character strings (short strings). But you have never set the length of this array, and so it can not hold any strings at all.
Use procedure SetLength(var S: <string or dynamic array>; NewLength: Integer); to allocate space for items in the array.
As you dont know (I presume) how many movies there might be in the file, you must first allocate some amount, and then be prepared to expand the array (with a new call to SetLength()) if the array becomes filled up before all movies are read from the file. For example, initialize (before the while loop) with space for 10 movies:
SetLength(S, 10);
and then in the while loop, e.g. just before ReadLn(),
if i > (Length(S)-1) then
SetLength(S, Length(S)+10);
Another comment is that the user is not presented any prompt when requested for a choice, but maybe this is still under development ;-)
The message you got is correct, because you only work with the array inside the while not eof loop. At that moment, the compiler cannot know the content of the file. It may be blank, as far as he's concerned. If the file is, indeed, blank, he'll skip entirely the while not eof part and go straight to the array writing part. Because the array was never used, it doesn't have a defined value, hence this message.
The solution is simple: initialize the array's values with 0:
program TFE;
{$APPTYPE CONSOLE}
uses
SysUtils,
StrUtils,
Crt;
var
MovieList, MovieInfo: Text;
Choice: Byte;
i: Integer;
L: String;
S: array of String[14];
begin
SetLength(s,10); //10 is an example
for i:=0 to Length(s) do
s[i]:='';
Assign(MovieInfo, 'MovieInfo.txt');
Reset(MovieInfo);
Readln(Choice);
i := 0;
ClrScr;
While not eof (MovieInfo) do
begin
Readln(MovieInfo, L);
S[i] := L;
i := i + 1;
end;
Writeln(S[Choice]);
Readln;
end.
Digging deep into your code, you define MovieList and MovieInfo, but you only use MovieInfo. Why?
Related
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!
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.
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.
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.
I have come up with this function to return the number of occurrences of a string in a Delphi Stream. However, I suspect there is a more efficient way to achieve this, since I am using "higher level" constructs (char), and not working at the lower byte/pointer level (which I am not that familiar with)
function ReadStream(const S: AnsiString; Stream: TMemoryStream): Integer;
var
Arr: Array of AnsiChar;
Buf: AnsiChar;
ReadCount: Integer;
procedure AddChar(const C: AnsiChar);
var
I: Integer;
begin
for I := 1 to Length(S) - 1 do
Arr[I] := Arr[I+1];
Arr[Length(S)] := C;
end;
function IsEqual: Boolean;
var
I: Integer;
begin
Result := True;
for I := 1 to Length(S) do
if S[I] <> Arr[I] then
begin
Result := False;
Break;;
end;
end;
begin
Stream.Position := 0;
SetLength(Arr, Length(S));
Result := 0;
repeat
ReadCount := Stream.Read(Buf, 1);
AddChar(Buf);
if IsEqual then
Inc(Result);
until ReadCount = 0;
end;
Can someone supply a procedure that is more efficient?
Stream has a method that will let you get into the internal buffer.
You can get a pointer to the internal buffer using the Memory property.
If you are working in 32 bit and you are willing to let go of the deprecated TMemoryStream and use TBytesStream instead you can use abuse the fact that a dynamic array and an AnsiString share the same structure in 32 bit.
Unfortunately Emba broke that compatibility in X64, Which means that for no good reason whatsoever you cannot have strings > 2GB in X64.
Note that this trick will break in 64 bit! (See fix below)
You can use Boyer-Moore string searching.
This allows you to write code like this:
function CountOccurrances(const Needle: AnsiString; const Haystack: TBytesStream): integer;
var
Start: cardinal;
Count: integer;
begin
Start:= 1;
Count:= 0;
repeat
{$ifdef CPUx86}
Start:= _FindStringBoyerAnsiString(string(HayStack.Memory), Needle, false, Start);
{$else}
Start:= __FindStringBoyerAnsiStringIn64BitTArrayByte(TArray<Byte>(HaySAtack.Memory), Needle, false, Start);
{$endif}
if Start >= 1 then begin
Inc(Start, Length(Needle));
Inc(Count);
end;
until Start <= 0;
Result:= Count;
end;
For 32 bit you'll have to rewrite the BoyerMoore code to use AnsiString; a trivial rewrite.
For 64 bit you'll have to rewrite the BoyerMoore code to use a TArray<byte> as a first parameter; a relatively simple task.
If you are looking for efficiency, please try and avoid WinAPI calls that use pchars. c-style strings are a horrible idea, because they do not have a length prefix.
Johan has given you a good answer about Boyer-Moore searching. BM is fine if
your are content to use it as a "black box", but if you want to understand what's going on,
there is a bit of a gulf between the complexity of your own code and a BM implementation.
You might find it helpful to explore searching that's more efficient than your own code
but not so complex as BM. There is one ultra-simple way to do what you want without
getting invoved with pointers, PChars, etc.
Let's leave aside for a moment the fact that you want to work with a TMemoryStream, and
consider finding the number of occurrences of a string SubStr in another string Target.
For efficiency, things you want to avoid are a) repeatedly scanning the same characters
over and over and b) copying one or both strings.
Since D7, Delphi has included a PosEx function:
function PosEx(const SubStr, S: string; Offset: Cardinal = 1): Integer;
Description
PosEx returns the index of SubStr in S, beginning the search at Offset. If Offset is 1 (default), PosEx is equivalent to Pos.
PosEx returns 0 if SubStr is not found, if Offset is greater than the length of S, or if Offset is less than 1.
So what you can do is repeatedly call PosEx, starting with Offset = 1, and each time it
finds SubStr in Target you increment Offset to skip over it, like this (in a console application):
function ContainsCount(const SubStr, Target : String) : Integer;
var
i : Integer;
begin
Result := 0;
i := 1;
repeat
i := PosEx(SubStr, Target, i);
if i > 0 then begin
Inc(Result);
i := i + Length(SubStr);
end;
until i <= 0;
end;
var
Count : Integer;
Target : String;
begin
Target := 'aa b ca';
Count := ContainsCount('a', Target);
writeln(Count);
readln;
end.
The fact that PosEx and ContainsCount both pass SubStr and Target as
consts meants that no string copying is involved, and it should be obvious
that ContainsCount never scans the same characters more that once.
Once you've satisfied yourself that this works, you might care to trace
into PosEx to see how it does its stuff.
You can do something which works in a similar way on PChars using the RTL functions StrPos/AnsiStrPos
To convert your memory stream to a string, you could use this code from
Rob Kennedy's answer to this q Converting TMemoryStream to 'String' in Delphi 2009
function MemoryStreamToString(M: TMemoryStream): string;
begin
SetString(Result, PChar(M.Memory), M.Size div SizeOf(Char));
end;
(Note what he says about the alternative version later in his answer)
Btw, if you look through the VCL + RTL code, you'll see that quite a lot of the string-parsing and processing code (e.g. in TParser, TStringList, TExpressionParser) all does its work with PChars. There's a reason for that of course, because it minimizes character copying and means that most scanning operations can be done by changing pointer (PChar) values.
I've done some research here regarding the problem given above and come up with the following code:
VarStr = array of WideChar;
function ArrayToString(const a: VarStr): UnicodeString;
begin
if Length(a) > 0 then
begin
ShowMessage ('Länge des übergebenen Strings: ' + IntToStr(Length(a)));
SetString(Result, PWideChar(#a[0]), Length(a) div 2)
end
else
Result := '';
end;
ShowMessage displays the correct number of characters in a given array, but the result of the function is always an empty string.
Your ideas please?
You are passing the wrong length value. You only ask for half of the characters. Fix your code like this:
function ArrayToString(const a: VarStr): string;
begin
SetString(Result, PWideChar(a), Length(a));
end;
However, you also report that your function returns an empty string. The most likely cause for that is that you are passing invalid input to the function. Consider this program:
{$APPTYPE CONSOLE}
type
VarStr = array of WideChar;
function ArrayToStringBroken(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(#a[0]), Length(a) div 2);
end;
function ArrayToStringSetString(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(a), Length(a));
end;
var
a: VarStr;
begin
a := VarStr.Create('a', 'b', 'c', 'd');
Writeln(ArrayToStringBroken(a));
Writeln(ArrayToStringSetString(a));
end.
The output is:
ab
abcd
So as well as the problem with the code in your question, you would seem to have problems with the code that is not in your question.
Perhaps when you said:
The result of the function is always an empty string.
You actually meant that no text is displayed when you pass the returned value to ShowMessage. That's a completely different thing altogether. As #bummi points out in comments, ShowMessage will truncate its input at the first null-terminator that is encountered. Use proper debugging tools to inspect the contents of variables.
Result:= Trim(string(a));
UPDATE: As colleagues graciously pointed in comments, this is a wrong answer! It works only because internal string and dynamic array implementation are pretty similar and there is no guarantee that such code would work in the future compilator versions. The correct way to DynArray->String conversion is described in the David answer. I would not delete my answer to preserve comments, in my opinion their worth is much greater..
I have a dynamic array. But initially I am not knowing the length of the array. Can I do like first I set the length of it as 1 and then increase length as I needed without lost of previously stored data?
I know I can do such task using TList. But I want to know whether I can do it with array or not?
Dynamic Arrays can be resized to a larger size without losing the contained data.
The following program demonstrates this in action.
program Project7;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
A : Array of Integer;
I : Integer;
begin
for I := 0 to 19 do
begin
SetLength(A,I+1);
A[I] := I;
end;
for I := Low(A) to High(A) do
begin
writeln(A[I]);
end;
readln;
end.
This is not exactly a straight-out question because I have just solved it, but more like "am I getting it right" type of question and a reminder for those who might get stuck into that.
Turns out, Delphi does not align variables on stack and there are no directives/options to control this behavior. Default COM marshaller on my XP SP3 seem to require 4-byte alignment when marshaling records though. Worse, when it encounters unaligned pointer, it does not return an error, oh no: it rounds the pointer down to the nearest 4-byte boundary and continues like that.
Therefore, if you pass a record you have allocated on stack into COM-marshaled function by reference, you're screwed and you won't even know.
The problem can be solved by using New/Dispose to allocate records, as memory managers tend to align everything at 8 bytes or better, but god, this is annoying, both the misalignment part and the "trim-down-pointers" part.
Is this really the reason, or am I wrong somewhere?
Update: How to reproduce (Delphi 2007 for Win32).
uses SysUtils;
type
TRec = packed record
a, b, c, d, e: int64;
end;
TDummy = class
protected
procedure Proc(param1: integer);
end;
procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
rec: TRec;
begin
a := 5;
b := 9;
c := 100;
rec.a := param1;
rec.b := a;
rec.c := b;
rec.d := c;
writeln(IntToHex(integer(#rec), 8));
readln;
end;
var Obj: TDummy;
begin
obj := TDummy.Create;
try
obj.Proc(0);
finally
FreeAndNil(obj);
end;
end.
This gives odd result address, clearly not aligned on anything. If it doesn't, try adding more byte variables to "a, b, c: byte" (and don't forget to simulate some work with them at the end of the function).
The part with COM is easier to reproduce but longer to explain. Create a new VCL app called Sample Server, add a COM object SampleObject implementing ISampleObject, with a type library, free-threaded, single instance (make sure to check ISampleObject is marked as Ole Automation in type library). Open the type library, declare a new SampleRecord with five __int64 fields. Add a SampleFunction with a single SampleRecord* out parameter to ISampleObject. Implement SampleFunction in TSampleObject by returning fixed values:
function TSampleObject.SampleFunction(out rec: SampleRecord): HResult;
begin
rec.a := 1291;
rec.b := 742310;
//...
Result := S_OK;
end;
Note how Delphi declares SampleRecord as "packed record" in automatically generated type library header code:
SampleRecord = packed record
a: Int64;
b: Int64;
//...
end;
I have checked, and this, at least, was fixed in Delphi 2010. Automatically generated records are not packed there.
Register the COM server. Run it.
Now modify the source above (sample 1) to call this server instead of just doing writeln:
uses SysUtils, Windows, ActiveX, SampleServer_TLB;
procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
rec: SampleRecord;
Server: ISampleObject;
begin
a := 5;
b := 9;
c := 100;
rec.a := param1;
rec.b := a;
rec.c := b;
rec.d := c;
Server := CoSampleObject.Create;
hr := Server.SampleFunction(rec);
writeln('#: 'IntToHex(integer(#rec), 8)+', rec.a='+IntToStr(rec.a));
readln;
end;
var Obj: TDummy;
begin
CoInitializeEx(nil, COINIT_MULTITHREADED);
obj := TDummy.Create;
try
obj.Proc(0);
finally
FreeAndNil(obj);
CoUninitialize();
end;
end.
Observe that when the address of rec is not aligned, values of rec fields are wrong (specifically, bitshifted to 8, 16 or 24 bits, sometimes wrapped over to the next value).
Do you have an example of the stack being misaligned? Delphi should be aligning everything to 4 byte boundaries. The only case I can think of that would cause misalignment is if someplace along the call-chain is some assembler code that has explicitly done something to the stack to mis-align it.