Link to home
Start Free TrialLog in
Avatar of LeTay
LeTay

asked on

Use of array of a record

I have defined a type like this (example)

TMyStuff = record
 FirstName:string;
 LastName:string;
 .../...
end;

I want to use in my application a number of such record. The number is not know at design time, just at execution (read external file etc...)

Currently I do this

TAllStuff = record
 Count:integer;
 Stuff:array[1..100] of TMyStuff;
end;

.../...

var
 AllStuff:TAllStuff.

I had to hardcode the size (100) to a value I know will not be overflowed
Isn't there another way, nicer, where I only "create" these records when needed, in a kind of array structure ?
Avatar of MichaelStaszewski
MichaelStaszewski
Flag of United States of America image

SetLength(AllStuff, Length(AllStuff) + 1);
    AllStuff[Length(AllStuff) - 1].FirstName := 'Something';
    AllStuff[Length(AllStuff) - 1].LastName := 'Something';
Sorry, that should act on your Stuff array.

Stuff:array[1..100] of TMyStuff;

You should be able to declare that as...

Stuff:array of TMyStuff;

To use dynamic sizing.
Complete...

TAllStuff = record
 Count:integer;
 Stuff:array of TMyStuff;
end;

...

Initialize Stuff with SetLength(Stuff, 0); // May not be required, but I do it as a matter of practice

....

SetLength(Stuff, Length(Stuff) + 1);
    Stuff[Length(Stuff) - 1].FirstName := 'Something';
    Stuff[Length(Stuff) - 1].LastName := 'Something';
Avatar of LeTay
LeTay

ASKER

Sorry but I do not understand the principle
Can you explain what is happening and how memory is practically assigned at run time ?
Avatar of Ferruccio Accalai
You could declare your stuff just as a dynamic array of TMyStuff

Stuff := Array of TMyStaff

When you'll have to loop into it you could use
For i := Low(Stuff) to High(Stuff)

and if you want to set the size on runtime just use SetLength(Stuff, 100) (or maybe SetLength(Stuff, SomeStringList.count) )
You declare your array with an unspecified length. You initialize it with a length of 0. As you need to add items to the array you are adding one item at a time so that your array is never using more memory than required by your application.

So, if you have a new name to add you may have a procedure that looks like...

procedure AddRecord(const First, Last: string);
begin
    SetLength(Stuff, Length(Stuff) + 1);
    Stuff[Length(Stuff) - 1].FirstName := First;
    Stuff[Length(Stuff) - 1].LastName := Last;
end;

Also, when you are cleaning up your application (or the context of this array) call SetLength(Stuff, 0) to clean everything up.
Avatar of LeTay

ASKER

So I can also get rid of the "Count", replace with Length ?
Length is to be used with array to test for number of items. Count is used by other objects like TStringList.

So if you had an array of string and a TStringList then you would use Length(array) to get the number of items whereas you'd use TStringList.Count to get the number there.  Ferruccio68 demonstrates the proper way to iterate an array too. Use Low/High.

If you have a known list of names that you are reading from a file or something then  Ferruccio68's suggestion is more efficient. A single setlength call to set the array to exactly what is needed. If you have an unknown list of items then my suggestion will add 1 at a time. You'll need to consider your context. Is the number of names known at runtime? If so, use the single setlength that  Ferruccio68 mentioned.
Wait, you mean the Count variable in your TAllStuff record? If so, yes. I think that you can eliminate TAllStuff completely and use a single, dynamic array of TMyStuff. Using the principles outlined above you can dynamically add to it and iterate it. That should be all that you need.
ASKER CERTIFIED SOLUTION
Avatar of MichaelStaszewski
MichaelStaszewski
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of LeTay

ASKER

Many thanks for these excellent explanations
I will test it this week-end and close the question if (hopefully) it works
Use a TList.
Avatar of LeTay

ASKER

Hum ... isn't TList used for objects and not record ?
You could use a TList but you have to clean up the memory afterwards

type
  PMyStuff = ^TMyStuff;
  TMyStuff = record
    FirstName, LastName: string;
  end;

....
var 
  P: PMyStuff;
...
New(P);
P^.FirstName := 'Alex';
List.Add(P);
...

On destroy, you could do this
for I := List.Count -1 downto 0 do
  Dispose(PMyStuff(List[I]));

Open in new window

You dont have to worry about length of the list.
addition to ewangoya

using a tlist is wiser memory wise, you don't need to allocate as large blocks as you do with a (dynamic) array. It's a little harde to do though
I agree that TList is easier to code and maintain.

If you must use an array (class assignment or such), I would recommend that you increase as a percentage of the size of array and keep track of the number of items (slots) filled.  Increasing the size of the array with each new addition causes performance problems as the array size increases.  Start with an expected/reasonable size (~100) and then increase the size of the array when it is time to add the 101st item.  Increase the size of the array by 50%.  Then you won't have to increase the size until you add item 151.
why not use a class for your record structure ?
and use that in the TList (or a descendant)

a class is basically a enhanced record structure
>> you can add procedures/functions and properties
>> and have visibility specifiers like private, protected, ...

type
  TItem = class(TObject)
  private
    fFirstName: string;
    fLastName: string;
  ...
  end;