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
Related
I have 20 buttons that are created automatically when the program starts. How to select five random non-repeating buttons and copy them to the Panel1 component?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1:TForm1;
but:array[1..9] of TButton;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
i: integer;
begin
for i := 1 to 20 do
begin
but[i] := TButton.Create(Form1);
but[i].Parent := Form1;
but[i].Caption := IntToStr(i);
but[i].Font.Size := 26;
but[i].Width := 50;
but[i].Height := 50;
but[i].Left := 50 + Round(i mod 3)*but[i].Width;
but[i].Top := 50 + Round(i / 3)*but[i].Height;
end;
end;
end.
The algorithm is like this:
Hold references to the buttons in an array.
Perform a uniform shuffle of the array, for instance using the Fisher-Yates shuffle.
Choose the first 5 items from the array.
You can optimise this if you wish by interrupting the shuffle after 5 iterations of its loop, because the first 5 items have already been chosen by that point. However, that's probably not necessary for such a small array.
There are many implementations of Fisher-Yates around, although beware that a large proportion are implemented incorrectly! Correct implementations abound also, for instance here https://stackoverflow.com/a/20361298/505088
You could do worse than use the spring4d library for this task.
As far as copying buttons goes, create new buttons and copy over the properties that you need. It may be easier to think of this job as actually choosing 5 random numbers between 1 and 20, and then creating 5 buttons captioned with those numbers. Again, that's just a shuffle of the numbers.
If you want to put 20 items in an array, you'll want to make sure the array is large enough. Your array can hold 9 items, but you try to put 20 in.
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.
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);
Is something like this possible with Delphi? (with dynamic arrays of strings and records)
type
TStringArray = array of String;
TRecArray = array of TMyRecord;
procedure DoSomethingWithStrings(Strings : TStringArray);
procedure DoSomethingWithRecords(Records : TRecArray);
function BuildRecord(const Value : String) : TMyRecord;
DoSomethingWithStrings(['hello', 'world']);
DoSomethingWithRecords([BuildRecord('hello'), BuildRecord('world')]);
I know that it does not compile like that. Just wanted to ask if there's a trick to get something similar to that.
If you don't have to change the length of the arrays inside your DoSomethingWith* routines, I suggest using open arrays instead of dynamic ones, e.g. like this:
procedure DoSomethingWithStrings(const Strings: array of string);
var
i: Integer;
begin
for i := Low(Strings) to High(Strings) do
Writeln(Strings[i]);
end;
procedure DoSomethingWithRecords(const Records: array of TMyRecord);
var
i: Integer;
begin
for i := Low(Records) to High(Records) do
Writeln(Records[i].s);
end;
procedure Test;
begin
DoSomethingWithStrings(['hello', 'world']);
DoSomethingWithRecords([BuildRecord('hello'), BuildRecord('world')]);
end;
Please note the array of string in the parameter list - not TStringArray! See the article "Open array parameters and array of const", especially the section about "Confusion", for more information.
I have two Delphi7 programs: a COM automation server (EXE) and the other program which is using the automation server.
I need to pass an array of bytes from one program to the other.
After some searching I've found that using variant arrays is the way to go (correct me please if you know any better methods).
My question is:
How do I create a variant array in one program, and then how do I read its values in the other?
I know about VarArrayCreate and VarArrayLowBound/VarArrayHighBound, but I'm unsure on how to do this properly.
Thanks!
You create it like that:
Declarations first
var
VarArray: Variant;
Value: Variant;
Then the creation:
VarArray := VarArrayCreate([0, Length - 1], varVariant);
or you could also have
VarArray := VarArrayCreate([0, Length - 1], varInteger);
Depends on the type of the data. Then you iterate like this:
i := VarArrayLowBound(VarArray, 1);
HighBound := VarArrayHighBound(VarArray, 1);
while i <= HighBound do
begin
Value := VarArray[i];
... do something ...
Inc(i);
end;
Finally you clear the array when you don't need it anymore. EDIT: (This is optional, see In Delphi 2009 do I need to free variant arrays? )
VarClear(VarArray);
That is all there is to it. For another example look at the official Embracadero Help
EDIT:
The array should be created only once. Then just use it like shown in the above example.
For the other side:
(assuming Value is the Variant parameter and the element type is WideString)
var
Source: PWideStringArray;
if VarIsArray(Value) then begin
Source:= VarArrayLock(Value);
try
for i:= 0 to TVarData(Value).VArray^.Bounds[0].ElementCount - 1 do
DoWhatEverYouWantWith(Source^[i]);
end;
finally
VarArrayUnlock(Value);
end;
end;