Arrays in Delphi (object Pascal) using variables? - arrays

I have these 10 numbers (one in each line) in a Text File and I need them to be sorted in a chronological order starting with the highest number. I wrote a code which works just fine, but the problem is that the code isn't flexible, because once I add another number to the Text File it won't work since the code is set to sort 10 numbers only...this is due to my array of integers which is supposed to read the values before the sorting process starts, but won't allow me to add a variable to the array's properties so it will be able to read and sort any-size text file...I know there has to be a way of making a program which can sort any-size file of this structure, just please tell me how I could improve my code. (If you think my way isn't too efficient, it's because that's my homework from high school and I need to use these arrays to implement a bubblesort).
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
numbers, sortednumbers : TextFile;
count : integer=0;
number : array[1..10] of integer;
I : integer;
Procedure Swap(var X, Y : Integer);
var
Temp : integer;
begin
Temp := X;
X := Y;
Y := Temp;
end;
procedure Assign;
var I : Integer;
begin
reset(numbers);
for I := 1 to count do ReadLn(numbers, number[I]);
end;
procedure BubbleSort;
var I, J : integer;
begin
for I := 2 to count do
begin
for J := count downto I do
if (number[J] > number[J - 1]) then
Swap(number[J - 1], number[J]);
end;
end;
begin
AssignFile(numbers, 'Numbers.txt');
AssignFile(sortednumbers, 'Sorted-Numbers.txt');
Reset(numbers);
While not EoF(numbers) do
begin
ReadLn(numbers);
Inc(count);
end;
Reset(numbers);
ReWrite(sortednumbers);
Assign;
BubbleSort;
For I := 1 to count do writeln(sortednumbers, number[I]);
CloseFile(numbers);
CloseFile(sortednumbers);
end.

Use a dynamic array. This is an array that can change the number of elements it holds.
Instead of declaring:
number : array[1..10] of integer;
instead skip the bounds declaration:
Number : array of integer;
Then, before you start using it, set its length:
SetLength(Number, 10);
Once you are done, free the memory by setting it to have length 0:
SetLength(Number, 0);
Note that:
Indexes of a dynamic array start from 0, not 1. That is, Number[0] is the first element and Number[9] is the tenth. This is quite common in programming but can be confusing if it's not something you've come across before.
There are other 'quality' changes you can make to your code too. As commenter David Heffernan said, stop using global variables. I would also suggest using try/finally for resource allocation and cleanup, and using streams (and another link, wrap this over TFileStream) instead of the old-style file IO you're currently doing. Also, consider your variable names - Number for an array of numbers is odd - why not add the S and call it Numbers, for example?
That might be a bit much all at once, so go slowly in small steps, save and backup often (preferably into source control), and have fun!

And if it had not been a school project:
function Comparer(List: TStringList; index1, index2: integer): integer;
begin
try
Result:= StrToInt(List[index1]) - StrToInt(List[Index2]);
except
raise Exception.Create('Your file does not contain numbers');
end;
end;
function SortNumbers
var
lStringList: TStringlist;
begin
lStringList := TStringlist.create;
try
lStringList.LoadFromFile('Numbers.txt');
lStringList.CustomSort(Comparer);
lStringList.SaveToFile('Sorted-Numbers.txt');
finally
lStringList.Free;
end;
end;
However the Delphi RTL uses Quicksort, not Bubblesort.

Related

Special For-Loop in Pascal

I work with a current software for the simulation of power plant processes. Smaller scripts can be written within the software for automation, these scripts are based on Pascal and own function libraries. Was simply retained after the initial release 20 years ago.
My simple script transfers values from one element to another and has this structure:
var f: integer;
S13Be.MXTSTO.data(1,1) := 22;
S12Be.MXTSTO.data(1,S12Be.NFLOW) := 22;
S11Be.MXTSTO.data(1,1) := S12Be.MXTSTO.data(1,S12Be.NFLOW);
S10Be.MXTSTO.data(1,S10Be.NFLOW) := 22;
S9Be.MXTSTO.data(1,1) := S10Be.MXTSTO.data(1,S10Be.NFLOW);
S8Be.MXTSTO.data(1,S8Be.NFLOW) := 22;
S7Be.MXTSTO.data(1,1) := S8Be.MXTSTO.data(1,S8Be.NFLOW);
S5Be.MXTSTO.data(1,S5Be.NFLOW) := 22;
S4Be.MXTSTO.data(1,1) := S5Be.MXTSTO.data(1,S4Be.NFLOW);
S2Be.MXTSTO.data(1,S2Be.NFLOW) := 22;
S1Be.MXTSTO.data(1,1) := S2Be.MXTSTO.data(1,S4Be.NFLOW);
for f := 1 to S13Be.NFLOW+1 do begin
S13Be.MXTSTO.data(1,f) := S13Be.MXTSTO.data(1,1);
end;
for f := 1 to S12Be.NFLOW+1 do begin
S12Be.MXTSTO.data(1,f) := S12Be.MXTSTO.data(1,1);
end;
for f := 1 to S11Be.NFLOW+1 do begin
S11Be.MXTSTO.data(1,f) := S11Be.MXTSTO.data(1,1);
end;
.
.
.
for f := 1 to S2Be.NFLOW+1 do begin
S2Be.MXTSTO.data(1,f) := S2Be.MXTSTO.data(1,1);
end;
for f := 1 to S1Be.NFLOW+1 do begin
S1Be.MXTSTO.data(1,f) := S1Be.MXTSTO.data(1,1);
end;
I would like to put another loop around the outside so that the elements are automatically selected.
The names of the elements are S1Be, S2Be.... S13Be and S1Ent, S2Ent, S3Ent...S13Ent
.MXSTO.data accesses a matrix in the respective element
(1,f) defines the position in the matrix (currently there are only 1x5 and 1x10 matrices; the value .NFLOW specifies which matrix is involved.)
I would be very grateful for a tip, a book recommendation and of course a code.
With best regards
Felix
Translated with www.DeepL.com/Translator (free version)
Names generally no meaning in a compiled program, it doesn't contain them.
If the identifiers are the same type you might be able to define an array of pointers to them, and then iterate using that array.
This can be handled with an enumerated type. The solution will require a little reorganization of your date, but I think it will be worth it. Like most languages variables are independent of each other. If you want to deal with a list of variables they will need to be in a structure of some sort. An array in your case makes sense. However, associating a numeric index with each variable is a bother and requires pointers or some difficult to maintain parsing system.
Nicklaus Wirth created a mechanism to deal with this sort of problem. It is called an enumerated type. For example:
type
BeName = (S1Be, S2Be, S3Be, S4Be);
var
NFLOW: array[BeName] of integer;
MXTSTOdata: array[BeName,1:5,1:10] of integer;
SnBe: BeName;
begin
… Initialization here, the following is how to change your code.
MXTSTOdata(S2Be,1,NFLOW[S2Be]) := 22;
MXTSTOdata(S1Be,1,1) :=MXTSTOdata(S2Be,1,NFLOW[S4Be]);
… Here is just one for loop:
for f := 1 to NFLOW[S1Be]+1 do
MXTSTOdata[S1Be,1,f] := MXTSTOdata[S1Be,1,1];
… Here is a loop of for loops:
for SnBe := S1Be to S4Be do
for f := 1 to NFLOW[SnBe]+1 do
MXTSTOdata[SnBe,1,f] := MXTSTOdata[SnBe,1,1];
Note how the for loop doesn’t need a start and end index. But that depends on which Pascal you are using. Delphi, FreePascal, and standard Pascal differ. You might have to use the first and last element like I showed. You might have a Low and High function available.
var
NFLOW: array[BeName] of integer;
SnBe: BeName;
for SnBe := Low(BeName) to High(BeName) do
for f := 1 to NFLOW[SnBe]+1 do
MXTSTOdata[SnBe,1,f] := MXTSTOdata[SnBe,1,1];
And I might have the syntax for the array declaration wrong. I’ve seen var NFLOW: array of [BeName] of integer; documented on the web, built I haven’t fired up my pascal compiler to check this fragment. However, the enumeration type would help you a lot. Also, there is a for-in construct in FreePascal
for SnBe in BeName do
for f := 1 to NFLOW[SnBe]+1 do
MXTSTOdata[SnBe,1,f] := MXTSTOdata[SnBe,1,1];
The enumeration type is useful in preventing bothersome minor spelling errors from messing up the program, has nice for loop options, and can change the ordering of the values in the enumeration. Changing the ordering may be needed for handling what order assignments are made in, at the cost of program fragility, as you might imagine.
The pred and succ operators are implemented. If you ever wondered what the need were for pred and succ you have now found out. A while loop:
SnBe := S1Be;
repeat
… something
SnBe := succ(SnBe)
until SnBe = S4Be;
Of course that doesn’t get easily to the last value in the enumeration. You could add guard values, but that adds some confusion and messes up the for-in loop.
SnBe := S1Be;
repeat
SomefunctionF(SnBe);
SnBe := succ(SnBe)
until SnBe = S4Be;
SomefunctionF(S4Be);
Is probably the cleanest way to deal with the problem of running in a repeat loop. The reason for adding these examples is you may have two enumerations running in parallel:
type
ToBeName = (S1Be, S2Be, S3Be, S4Be);
NotToBeName = (Bob, Carol, Ted, Alice);
var
NFLOW: array[BeName] of integer;
MXTSTOdata: array[BeName,1:5,1:10] of integer;
Romeo: NotToBeName;
SnBe: BeName;
begin
SnBe:=S1Be;
Romeo:=Bob;
Repeat
ActionFunction(SnBe,Romeo);
SnBe := succ(SnBe);
Romeo := succ(Romeo)
until SnBe = Alice;
Also, this idea might be helpful for your program:
type
EType = (S1Be, S2Be, S3Be, S4Be, Bob, Carol, Ted, Alice);
var
Romeo: EType;
SnBe: EType;
begin
Romeo:= Bob;
SnBe:=S1Be;
repeat
SomeFn(Romeo,SnBe);
SnBe:=succ(SnBe);
until SnBe>S4Be;
The range check applies to pred and succ. For example, the statement
succ(Alice)
would produce an error because there is no element after Alice in the enumerations above.
Lastly, if you need to do things in reverse order you can do:
for SnBe := S4Be downto S1Be do

Pascal incrementing variable names of textboxes

I am using Arrays for managing my data in and outputs.
My problem is that I need to i/o from or into textboxes which are continously incremented by 1- So my Array starts at 1 and ends at 31. My textboxes which are variables and cannot be arrays are named like memo67... - memo97.
Is it possible in a way that I use a tmepcounter? So I wanted to use something like memo(a+66) in a for loop. With this I could fill my textboxes with my array[a] 1 to 31.
Greets
As Andreas says, usually arrays are used in such case. But if you like me like the quick copy and past action, you can fill such arrays can be using TComponent features with one single procedure:
Type
TForm1 = class
editsset1 : array of tedit;
end;
type TControlDynArray = Array of TControl;
procedure loadarray (root:tcomponent;var x : TControlDynArray;basename:string;i1,i2:integer);
var l,i : integer;
begin
l:=i2-i1+1;
setlength(x,l);
for i := i1 to i2 do
begin
x[i-i1]:=tcontrol(root.FindComponent(basename+inttostr(i)));
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// loads edit1..31 into editsset1[0] ... editsset[30]
loadarray(self,TControlDynArray(editsset1),'Edit',1,31);
end;
Disclaimer: the above code is pieced together from working code, but I didn't test the endresult

Making new array whose elements are every second element of another array.(Pascal)

I've been trying to make a program that will ask user to input elements of an array and then use that array to make a new one whose elements would be every 2nd element of inputted array. This is what I was writing:
program Keanu;
uses crt;
type Arr=array of integer;
var n,i:integer;
A,C:Arr;
begin
writeln('--Enter desired length of array--');
readln(n);
setlength(A,n);
setlength(C,n);
writeln('Elements of array A:');
for i:=1 to n do
readln(A[i]);
writeln('Elements of array C are:');
i:=1;
while (i<=n) do
begin
c[i]:=a[i];
i:=i+2;
end;
write('C = {');
for i:=1 to n do
begin
if c[i]=0 then continue else
begin
write(c[i],' ');
end;
end;
write('}');
readln;
end.
But as you can notice this is far from efficient way to make this program do the job.
First, because my new array will contain blank/empty elements(zeros) which I simply ignored with continue statement and I dont want to do that if possible.
Second,I have problem when inputting an even number for array length.Last element of new array in output window is very small,negative number and he shouldn't be there at all.I know this has to do something with my counter "i" crossing into "undefined" indexes of array.
I also tried replacing while loop with some variations of:
for i:=0 to n do
c[i]:=a[2*i-1] ;
Which is more elegant way but I still get , besides desired result , those large numbers , again because of crossing limits of an array.I suspect this has to be done with proper steps of how new array is made and moving those elements next to each other with no blank elements.
So, if anyone can give me some solutions of how to get these loop steps and limits into right order and make efficient,shortest algorithm, and preferably without using while loop if possible ,and definitely without ignoring those blank elements of new array.
Declaring variables by one character A, C: array of integer is a bad practice. The name of variable will tell you about it's type and meaning. (highly recommended)
And read about Hungarian notation, then you'll understand why TArr and arrA instead of Arr and A. Not necessary, but recommended. (also recommended)
As for the second arrC, you can use operator div to make it twice smaller than the first array arrA.
You can also crete a new variable that will be n div 2, in order not to change n div 2 in the whole code (good practice):
nDivided := n div 2;
SetLength(arrA,n);
SetLength(arrC, nDivided);
This will make your program quite a bit efficient: you'll save n - (n div 2) * sizeof(integer) bytes.
Here are both cases (for even and odd N). No "blank elements" and no "very small, negative number in the end of new array(-32768)".
N is 6 N is 5
Elements of array A: Elements of array A:
arrA[1]=1 arrA[1]=1
arrA[2]=2 arrA[2]=2
arrA[3]=3 arrA[3]=3
arrA[4]=4 arrA[4]=4
arrA[5]=5 arrA[5]=5
arrA[6]=6
Elements of array C are: Elements of array C are:
arrC[1]=2 arrC[1]=2
arrC[2]=4 arrC[2]=4
arrC[3]=6
Anyway, here is code (a little changed). Also not very good (efficent) but it does exactly what you need.
program WorkingWithArrays;
uses crt;
type
TArr = Array of Integer;
var
i, n: integer;
arrA, arrC: TArr;
begin
writeln('Enter the length of array:');
readln(n);
SetLength(arrA,n);
SetLength(arrC, n div 2);
writeln('Elements of array A:');
for i:=1 to (n) do begin
arrA[i]:=i;
writeln('#> arrA[',i,']=', arrA[i]);
end;
writeln('Elements of array C are:');
i:=1;
while (i <= n div 2) do
begin
arrC[i]:=arrA[i+i];
i:=i+1;
end;
for i:=0 to (n div 2) do begin
if arrC[i]=0 then continue else begin
writeln('#> arrC[',i,']=', arrC[i]);
end;
end;
readln;
end.
// compiled here: http://www.compileonline.com/compile_pascal_online.php

Is any way to add 2 arrays into one?

Is there any simple univesal way to add 2 arrays into one? In the case below it is not possible simply use C := A + B statement...
I would like to avoid making algorhytm for it everytime .
TPerson = record
Birthday: Tdate;
Name, Surname:string;
end;
Tpeople = array of TPerson;
var A, B, C:Tpeople;
C:=A+B; // it is not possible
thanx
Due to the two string fields in each TPerson record, you can't just use binary "move", since you'll mess the reference counting of string - especially in a multi-threaded environment.
You can do it manually - this is fast and nice:
TPerson = record
Birthday: TDate;
Name, Surname: string;
end;
TPeople = array of TPerson;
var A, B, C: TPeople;
// do C:=A+B
procedure Sum(const A,B: TPeople; var C: TPeople);
begin
var i, nA,nB: integer;
begin
nA := length(A);
nB := length(B);
SetLength(C,nA+nB);
for i := 0 to nA-1 do
C[i] := A[i];
for i := 0 to nB-1 do
C[i+nA] := B[i];
end;
Or you can use our TDynArray wrapper, which has a method for handling such cases:
procedure AddToArray(var A: TPeople; const B: TPeople);
var DA: TDynArray;
begin
DA.Init(TypeInfo(TPeople),A);
DA.AddArray(B); // A := A+B
end;
The AddArray method can add a sub-port of the original array:
/// add elements from a given dynamic array
// - the supplied source DynArray MUST be of the same exact type as the
// current used for this TDynArray
// - you can specify the start index and the number of items to take from
// the source dynamic array (leave as -1 to add till the end)
procedure AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1);
Note that with such records, it will use the System._CopyRecord RTL function, which is not so optimized for speed. I've written a faster version - see this blog article or this forum thread.
If you use dynamic arrays in functions/procedures, don't forget to use explicitly const or var parameters (as I coded above), otherwise it will make a temporary copy at each call, therefore it may be slow.
There is nothing built in that allows dynamic arrays to be concatenated.
You may consider using one of the generic container classes found in Generics.Collections, TList.
In your case you would have 3 instances of TList, say A, B and C. Then you could write
A.Clear;
A.AddRange(B);
A.AddRange(C);
I think this is as close as you can get to what you want with what is delivered out of the box.
If you are prepared to do a bit of coding yourself then you could make use of operator overloading to use the exact syntax you requires. Declare a record containing an array of TPerson with private visibility. You then need to implement an Add operator, a Count property and a default Items[] property. This could be made generic too so you only need write it once.
TTurboArray = record<T>
private
FItems: array of T;
//property accessors here
public
class operator Add(a, b: TTurboArray<T>): TTurboArray<T>;
property Count: Integer read GetCount write SetCount;
property Items[Index: Integer]: T read GetItem write SetItem; default;
end;
This idea can be extended into a very powerful data structure as you see fit.
There is a quick-and-dirty way to do this. It is a terrible hack, but it should work and even take care of reference counting:
function ConcatPeople(const A, B: TPeople): TPeople;
var
Temp: TPeople;
ALen, BLen: Integer;
begin
Result := Copy(A);
BLen := Length(B);
if BLen = 0 then
Exit;
ALen := Length(A);
Temp := Copy(B);
SetLength(Result, ALen + BLen);
Move(Temp[0], Result[ALen], BLen * SizeOf(B[0]));
FillChar(Temp[0], BLen * SizeOf(B[0]), 0);
end;
In effect, the data in Temp are "swapped" with the empty records in Result, so the balance is maintained and refcounting will keep on working.
Update
For what it is worth: This is aparently the same technique as used in this accepted SO answer and in, e.g. TList<T>.Insert. I had deleted this answer, but I still think it is valid, so I undeleted it again. It could do with a lock around the Move/FillChar block, so no one accesses the items when they are being "swapped". I'll add that.
Here's how I handled it, though it required a slight (but hopefully immaterial to you) modification to your original code to use TArray:
(tested in XE2)
uses
Generics.Collections;
type
TArrayExt = class(TArray)
class function Concat<T>(const First, Second: array of T): TArray<T>; overload;
end;
class function TArrayExt.Concat<T>(const First, Second: array of T): TArray<T>;
var
i: Integer;
l: Integer;
begin
l := Length(First);
SetLength(Result, l + Length(Second));
for i := Low(First) to High(First) do
Result[i] := First[i];
for i := Low(Second) to High(Second) do
Result[l + i] := Second[i];
end;
type
TPerson = record
Birthday: TDate;
Name, Surname: String;
end;
TPeople = TArray<TPerson>;
var
A, B, C: TPeople;
begin
C := TArrayExt.Concat<TPerson>(A, B);
The main difference here is that I use "TArray" rather than "array of TPerson". This can be used for arrays strings, records, etc. I find the main benefit of doing it this way is that it's truly making a copy rather than a move. And I am using the "normal" Delphi functions instead of things like bulk memory copies, which can give me the willies.
Of course, if you were doing this in a tight loop and needed the performance, this way might not be best for you. But I think this is the best for most other situations, especially in terms of maintenance and readability.
(Unless someone posts a comment about how there's some horrible hidden memory leak here. Hopefully not!)
You code works fine in the newest version of delphi C := A+B;.
But for dynamic arrays in older versions you can use the function concat. Example:
C := Concat(A, B);

Array begin from 0 or 1 in Delphi 5.0 Pascal?

I want to do an ArrayList in Delphi 5.0. So I found a solution doing this code:
var arr: array of String;
OK, but every time I add something I do this:
var
Form1: TForm1;
var arr : array of String;
procedure TForm1.Button1Click(Sender: TObject);
var aux :string;
var len:integer;
begin
len := Length(arr) + 1;
SetLength(arr, len);
arr[len-1] := 'abc' + IntToStr(len);
Button1.Caption := arr[len-1]; // just to writeout something
end;
I'm a C++ programmer, and I do not know anything about Pascal. I always heard a Pascal index begins from 1, not 0. As in the above procedure I do arr[len-1] because of 0 index begin.
Is there a better way than Pascal arrays? Like with C++'s std::vector?
Dynamic arrays' indexes begin with zero
var
a: array of Integer;
begin
SetLength(a, 500);
a[0] := 0;
Static arrays can have arbitrary indexes
var
i: Integer;
b: array [50..100] of Integer;
c: array[-10..10] of Integer;
begin
for i := 50 to 100 do b[i] := i * i;
// Note negative starting index above in declaration
for i := -10 to 10 do c[i] := i * i;
Strings' indexes begin with one
var
c: String;
begin
c := 'Zap!';
c[1] := 'W';
ShowMessage(c); /// shows 'Wap!'
Anyway you can always use Low() and High() functions which return the lower and higher index of an array.
For handling a list of strings the most commonly used class is TStringList which is found in unit Classes.
What you're using is known as a dynamic array which is different from a Pascal classic array. Dynamic arrays are variable in size and the index is 0 based.
Classic Pascal arrays are not 0 nor 1 based... It's up to the programmer where the index start or ends. The only compiler restriction is that the index must be an ordinal type. You can declare
procedure x;
var
IntArr: array[50..75] of Integer;
StrArr: array[0..49] of string;
DblArr: array[1..10] of Double;
Delphi Pascal also has a nice feature that helps iterating through an array of any dimension:
Simply use for i:= Low(Array) to High(Array) do....
which is completely transparent to starting offset i.e. 0,1 or 5 or whatever.
I tried to edit the above answer to improve it but the editor keeps rejecting my posting. Arrays can have negative indexes.
var
A:array[-20..9] of integer;
B:array[-30..-10] of integer;
These are both the same, an array of 20 integers but will not be treated the same by the compiler because the index range is different. Allows you to make the data fit the problem domain, not the other way around.
Now, a string like var S:string[200]; is technically equivalent to var s:packed array[0..200] of char where byte 0 is the length except when you use a string with no length or the specified length is greater than 255, then the string is 1 to whatever current size it is. Because strings can be dynamic it's not good to depend on the 0th element to contain length.
When you use SetLength(array, length) it is worth mentioning that it has indexes starting from 0 as mentioned up to length-1. Also in pascal index on array can be character from ANSI table. So you can define array like a:array['A'..'Z'] of integer. This comes in handy when you need to count all characters in your Strings or Char Array.

Resources