Procedure with an arbitrary number of double parameters - delphi

I have 10 double variables I would like to initialize with the value 0. They are unstructured and not part of an array by design.
procedure Initialize;
var
a1, a2, a3, a4, a5, b1, b2, b3, b4, b5: Double;
begin
a1 := 0;
a2 := 0;
a3 := 0;
a4 := 0;
a5 := 0;
b1 := 0;
b2 := 0;
b3 := 0;
b4 := 0;
b5 := 0;
end;
To refactor that piece of code, I'm introducing a helper method AssignValue.
procedure Initialize;
var
a1, a2, a3, a4, a5, b1, b2, b3, b4, b5: Double;
begin
AssignValue(0,a1);
AssignValue(0,a2);
...
end;
procedure AssignValue(value: Double; var target: Double);
begin
target:= value;
end;
How do I write a more general AssignValue procedure that takes an arbitrary number of arguments and make the call AssignValue(0,a1,a2,a3,a4,a5,b1,b2,b3,b4,b5) possible?
Bonus question: How do you write that procedure so that it takes into account double or int reference in any order, assuming value: Int as first parameter.

You could do it like this:
procedure AssignValue(const Value: Double; const Addresses: array of PDouble);
var
i: Integer;
begin
for i := low(Addresses) to high(Addresses) do
Addresses[i]^ := Value;
end;
Call it like this:
AssignValue(0.0, [#a1, #a2, #a3, ...]);
Here we are passing an open array containing the addresses of your variables.
To support multiple types you would use overloads declared like this:
procedure AssignValue(const Value: Double; const Addresses: array of PDouble);
overload;
procedure AssignValue(const Value: Integer; const Addresses: array of PInteger);
overload;
// and so on, implementation of these functions is obvious
It's up to you to judge whether or not this is any better than your current solution. Personally, I'd stick with the plain old assignment operator. Another option would be to put the variables inside a record and assign Default(TMyRecord) to your record variable.

You can use open array parameters for this:
procedure AssignValue(value: double; const arr: array of PDouble);
var
i: Integer;
begin
for i := 0 to length(arr)-1 do
PDouble(arr[i])^ := value;
end;
use it like this (i don't see the way to avoid of "#" for such task):
AssignValue(1, [#a1,#a2,#a3]);

First of all, you can use a record and call fillchar(myrecord,sizeof(myrecord),0) but it may be error prone if you have some internal reference-counted values (like string).
But in your case, since it is only double values, it may be very easy to write:
procedure Initialize;
var localdata: record
a1, a2, a3, a4, a5, b1, b2, b3, b4, b5: Double;
obj: TObject;
i1, i2, i3, i4: integer;
end;
begin
fillchar(localdata,sizeof(localdata),0);
with localdata do
begin
a1 := 10;
a2 := a1+10;
assert(obj=nil);
inc(i1,20);
i2 := i1+10;
assert(i2=30);
end;
end;
As you can see, you can even mix types within the record. The trick is that you define your record type inline, without any type definition, which is not needed.
I admit this is not the direct answer, but I humbly suggest that you change your design to switch to something more "OOP-compatible".
Just use a dynamic array, or a class to embed the values. They will be all set to 0 by default.
For a dynamic array:
var a,b: array of double;
SetLength(a,5); // then use a[0] instead of a1, a[2] instead of a2...
SetLength(b,5); // then use b[0] instead of b1, b[2] instead of b2...
For a class - which is my preferred, since you can embedd your code within your data, as good objects:
type
TMyClass = class
public
a1, a2, a3, a4, a5, b1, b2, b3, b4, b5: Double;
procedure OneMethodHere;
function OneTestHere(aValue: double): boolean;
end;
var C: TMyClass;
C := TMyClass.Create; // every C member will be set to 0
try
if C.OneTestHere(10) then
C.OneMethodHere;
// you can use C.a1 or C.b5
finally
C.Free;
end;

Related

Custom Sort a Delphi TStringList name/value pairs as integers

I have a TStringList of Name/Value pairs. The names are all integer values 9stored as strings of course) and the values are all strings (comma separated).
e.g.
5016=Catch the Fish!,honeyman,0
30686=Ozarktree1 Goes to town,ozarktreel,0
.
.
.
I would like to call the add routine and add new lines in the TStringlist, but need a way to sort the list afterwards.
e.g.
Tags.Add(frmTag.edtTagNo.Text + '=' +
frmTag.edtTitle.Text + ',' +
frmTag.edtCreator.Text + ',' +
IntToStr(ord(frmTag.cbxOwned.Checked)));
Tags.Sort;
Here is what I tried:
Tags:= TStringList.Create;
Tags.CustomSort(StringListSortComparefn);
Tags.Sorted:= True;
my custom sort routine:
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result:= CompareValue(i1, i2);
end;
However, it still seems to be sorting them like strings instead of integers.
I even tried creating my own class:
type
TXStringList = class(TStringList)
procedure Sort;override;
end;
implementation
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result:= CompareValue(i1, i2);
end;
procedure TXStringList.Sort;
begin
CustomSort(StringListSortComparefn);
end;
I even tried some examples on SO (e.g. Sorting TSTringList Names property as integers instead of strings)
Can someone tell me what I am doing wrong? Everytime, the list gets sorted as strings and not as integers.
30686=Ozarktree1 Goes to town,ozarktreel,0
5016=Catch the Fish!,honeyman,0
You can do a simple integer subtraction:
function StringListSortComparefn(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2 : Integer;
begin
i1 := StrToIntDef(List.Names[Index1], 0);
i2 := StrToIntDef(List.Names[Index2], 0);
Result := i1 - i2
end;
To reverse the sort order, simply reverse the operands in the subtraction:
Result := i2 - i1;
Here's a quick, compiilable console example:
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
function StringListSortProc(List: TStringList; Index1, Index2: Integer): Integer;
var
i1, i2: Integer;
begin
i1 := StrToIntDef(List.Names[Index1], -1);
i2 := StrToIntDef(List.Names[Index2], -1);
Result := i1 - i2;
end;
var
SL: TStringList;
s: string;
begin
SL := TStringList.Create;
SL.Add('3456=Line 1');
SL.Add('345=Line 2');
SL.Add('123=Line 3');
SL.Add('59231=Line 4');
SL.Add('545=Line 5');
WriteLn('Before sort');
for s in SL do
WriteLn(#32#32 + s);
SL.CustomSort(StringListSortProc);
WriteLn('');
WriteLn('After sort');
for s in SL do
WriteLn(#32#32 + s);
ReadLn;
SL.Free;
end.
And the resulting output:
Before sort
3456=Line 1
345=Line 2
123=Line 3
59231=Line 4
545=Line 5
After sort
123=Line 3
345=Line 2
545=Line 5
3456=Line 1
59231=Line 4
The question is, do you require the list to remain sorted? Or is it sufficient to sort it at the end, after all the items have been added.
If you just need to be able to sort the list as needed, you're first example is almost correct. You just need to call CustomSort at the end, after your items have been added.
Tags := tStringList . Create;
Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
Tags.CustomSort(StringListSortComparefn);
If you need the list to stay sorted, then you need to override CompareStrings.
type
TXStringList = class(TStringList)
function CompareStrings(const S1, S2: string): Integer; override;
end;
function NumberOfNameValue ( const S : string ) : integer;
begin
Result := StrToIntDef(copy(S,1,pos('=',S)-1), 0);
end;
function txStringList . CompareStrings ( const S1, S2 : string ) : integer;
var
i1, i2 : Integer;
begin
i1 := NumberOfNameValue ( S1 );
i2 := NumberOfNameValue ( S2 );
Result:= CompareValue(i1, i2);
end;
begin
Tags := txstringlist . Create;
Tags . Sorted := true;
Tags . Add ( '5016=Catch the Fish!,honeyman,0' );
Tags . Add ( '30686=Ozarktree1 Goes to town,ozarktreel,0' );
// List will be correctly sorted at this point.
end;
The CustomSort command is a one-time operation. You appear to be using it as though you're setting a property so that further sorting will use the custom comparison function, but that's not really how it works. It sorts the (newly created, empty) list once. Then, when you set the Sorted property, you re-sort the list using the default comparison, and you specify that any further additions to the list should be inserted using that default sort order.
When you override the Sort method, you're a little closer to a solution, but insertions to a sorted list (where Sorted=True) do not actually call Sort! Instead, they perform a binary search for the correct insertion location and then insert there. Instead of overriding Sort, you could try overriding CompareStrings:
type
TXStringList = class(TStringList)
protected
function CompareStrings(const S1, S2: string): Integer; override;
end;
function TXStringList.CompareStrings(const S1, S2: string): Integer;
var
i1, i2, e1, e2: Integer;
begin
Val(S1, i1, e1);
Assert((e1 = 0) or (S1[e1] = NameValueSeparator));
Val(S2, i2, e2);
Assert((e2 = 0) or (S2[e2] = NameValueSeparator));
Result := CompareValue(i1, i2);
end;
Beware that this will break the IndexOf method, though. It might also break Find, but you might want that, depending on how you want to treat elements with the same numeric key. (Find is what's used to locate the correct insertion point of a sorted list, and with the above code, it would treat all elements with the same key as equal.) They all use CompareStrings just like Sort does.

Alternative to nested for-loop in Delphi

I came across the following (conceptually very simple) problem, and want to write code to do it, but am struggling. Let's say we have two rows of equal length, k. Each cell of each row can be either a 0 or a 1.
For e.g., consider the following row-pair, with k = 5: 01011, 00110
Now if the two rows could freely exchange values at each cell, there would be 2^5 possible combinations of row-pairs (some of which may not be unique). For instance, we could have 00010, 01111 as one possible row-pair from the above data. I want to write code in Delphi to list all the possible row-pairs. This is easy enough to do with a set of nested for-loops. However, if the value of k is known only at run-time, I'm not sure how I can use this approach for I don't know how many index variables I would need. I can't see how case statements will help either because I don't know the value of k.
I am hoping that there is an alternative to a nested for-loop, but any thoughts would be appreciated. Thanks.
Given two vectors A and B of length k, we can generate a new pair of vectors A1 and B1 by selectively choosing elements from A or B. Let our decision to choose from A or B be dictated by a bit vector S, also of length k. For i in [0..k), when Si is 0, store Ai in A1i and Bi in B1i. If Si is 1, then vice versa.
We can define that in Delphi with a function like this:
procedure GeneratePair(const A, B: string; S: Cardinal; out A1, B1: string);
var
k: Cardinal;
i: Cardinal;
begin
Assert(Length(A) = Length(B));
k := Length(A);
Assert(k <= 32);
SetLength(A1, k);
SetLength(B1, k);
for i := 1 to k do
if (S and (1 shl Pred(i))) = 0 then begin
A1[i] := A[i];
B1[i] := B[i];
end else begin
A1[i] := B[i];
B1[i] := A[i];
end;
end;
If we count in binary from 0 to 2k−1, that will give us a sequence of bit vectors representing all the possible combinations of exchanging or not-exchanging characters between A and B.
We can write a loop and use the above function to generate all 2k combinations:
A := '01011';
B := '00110';
for S := 0 to Pred(Round(IntPower(2, Length(A)))) do begin
GeneratePair(A, B, S, A1, B1);
writeln(A1, ', ', B1);
end;
That effectively uses one set of nested loops. The outer loop is the one from 0 to 31. The inner loop is the one inside the function from 1 to k. As you can see, we don't need to know the value of k in advance.
Now that, thanks to Rob, I understand the problem, I offer this recursive solution:
{$APPTYPE CONSOLE}
procedure Swap(var A, B: Char);
var
temp: Char;
begin
temp := A;
A := B;
B := temp;
end;
procedure Generate(const A, B: string; Index: Integer);
var
A1, B1: string;
begin
Assert(Length(A)=Length(B));
inc(Index);
if Index>Length(A) then // termination
Writeln(A, ', ', B)
else
begin // recurse
// no swap
Generate(A, B, Index);
//swap
A1 := A;
B1 := B;
Swap(A1[Index], B1[Index]);
Generate(A1, B1, Index);
end;
end;
begin
Generate('01011', '00110', 0);
Readln;
end.

Dynamic array reference count in record

I have an advanced record with a dynamic array field.
The record has a class operator for concatenation of a record and a byte. Also an Add method, adding a byte.
For what I'm about to use the record, the reference count of the dynamic array field is of importance. When running the two test procedures below, you can see that the concatenation results in a reference count of 2 while the add method results in a reference count of 1.
program TestReferenceCount;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
Type
TRec = record
class operator Add(const a: TRec; b: Byte): TRec;
private type
PDynArrayRec = ^TDynArrayRec;
TDynArrayRec = packed record
{$IFDEF CPUX64}
_Padding: LongInt; // Make 16 byte align for payload..
{$ENDIF}
RefCnt: LongInt;
Length: NativeInt;
end;
private
FArr: TBytes;
function GetRefCnt: Integer;
public
procedure Add(b : Byte);
property RefCnt: Integer read GetRefCnt;
end;
procedure TRec.Add(b : Byte);
var
prevLen: Integer;
begin
prevLen := System.Length(Self.FArr);
SetLength(Self.FArr, prevLen + 1);
Self.FArr[prevLen] := b;
end;
class operator TRec.Add(const a: TRec; b: Byte): TRec;
var
aLen: Integer;
begin
aLen := System.Length(a.FArr);
SetLength(Result.FArr, aLen + 1);
System.Move(a.FArr[0], Result.FArr[0], aLen);
Result.FArr[aLen] := b;
end;
function TRec.GetRefCnt: Integer;
begin
if Assigned(FArr) then
Result := PDynArrayRec(NativeInt(FArr) - SizeOf(TDynArrayRec)).RefCnt
else
Result := 0;
end;
procedure TestConcatenation;
var
r1 : TRec;
begin
WriteLn('RC:', r1.RefCnt); // <-- Writes 0
r1 := r1 + 65;
WriteLn('RC:', r1.RefCnt); // <-- Writes 2
end;
procedure TestAdd;
var
r1 : TRec;
begin
WriteLn('RC:', r1.RefCnt); // <-- Writes 0
r1.Add(65);
WriteLn('RC:', r1.RefCnt); // <-- Writes 1
end;
begin
TestConcatenation;
TestAdd;
ReadLn;
end.
The compiler takes care of the extra reference count when the record variable goes out of scope, so no problem really at this point.
But can this behavior be explained? Is it an undocumented implementation detail? Is there a way to avoid the extra count?
Let's take a look at this function:
procedure TestConcatenation;
var
r1 : TRec;
begin
r1 := r1 + 65;
end;
The compiler actually implements it like this:
procedure TestConcatenation;
var
r1 : TRec;
tmp : TRec;
begin
tmp := r1 + 65;
r1 := tmp;
end;
The compiler introduces a temporary local to store the result of r1 + 65. There's a very good reason for that. If it did not, where would it write the result of your addition operator? Since the ultimate destination is r1, if your addition operator writes directly to r1 it is modifying its input variable.
There is no way to stop the compiler generating this temporary local.

WideChar to Bytes?

I have simple question here. How to convert WideChar to 2xByte in Delphi - 7? I searched the internet and the StackOverflow but with no results...
David gave you the preferable way, namely,
var
b1, b2: Byte;
wc: WideChar;
...
b1 := WordRec(wc).Lo;
b2 := WordRec(wc).Hi;
A few other options (just for fun):
b1 := Lo(Word(wc));
b2 := Hi(Word(wc));
and
b1 := Byte(wc);
b2 := Byte(Word(wc) shr 8);
and
b1 := PByte(#wc)^;
b2 := PByte(NativeUInt(#wc) + 1)^;
and
var
wc: WideChar;
bytes: WordRec absolute wc;
begin
// Magic! The bytes are already found in bytes.Lo and bytes.Hi!
Lots of ways to do this. For example my personal choice would be:
var
b1, b2: Byte;
wc: WideChar;
....
b1 := WordRec(wc).Lo;
b2 := WordRec(wc).Hi;

Delphi SetLength Custom Indexing

In Delphi, it is possible to create an array of the type
var
Arr: array[2..N] of MyType;
which is an array of N - 1 elements indexed from 2 to N.
If we instead declare a dynamic array
var
Arr: array of MyType
and later allocate N - 1 elements by means of
SetLength(Arr, N - 1)
then the elements will be indexed from 0 to N - 2. Is it possible to make them indexed from 2 to N (say) instead?
No, in Delphi dynamic arrays are always indexed from zero.
YES! By using a trick!First declare a new type. I use a record type instead of a class since records are a bit easier to use.
type
TMyArray = record
strict private
FArray: array of Integer;
FMin, FMax:Integer;
function GetItem(Index: Integer): Integer;
procedure SetItem(Index: Integer; const Value: Integer);
public
constructor Create(Min, Max: integer);
property Item[Index: Integer]: Integer read GetItem write SetItem; Default;
property Min: Integer read FMin;
property Max: Integer read FMax;
end;
With the recordtype defined, you now need to implement a bit of code:
constructor TMyArray.Create(Min, Max: integer);
begin
FMin := Min;
FMax := Max;
SetLength(FArray, Max + 1 - Min);
end;
function TMyArray.GetItem(Index: Integer): Integer;
begin
Result := FArray[Index - FMin];
end;
procedure TMyArray.SetItem(Index: Integer; const Value: Integer);
begin
FArray[Index - FMin] := Value;
end;
With the type declared, you can now start to use it:
var
Arr: TMyArray;
begin
Arr := TMyArray.Create(2, 10);
Arr[2] := 10;
It's actually a simple trick to create arrays with a specific range and you can make it more flexible if you like. Or convert it to a class. Personally, I just prefer records for these kinds of simple types.
The only thing that you can do that mimics this behaviour is using pointers..
type
TMyTypeArr = array [ 0..High(Integer) div sizeof( MyType ) - 1 ] of Mytype;
PMyTypeArr = ^TMyTypeArr;
var
x: ;
A: PMyTypeArr;
begin
SetLength( A, 2 );
x := PMyTypeArr( #A[ 0 ] ); Dec( PMyType( x ), 2 ); // now [2,4> is valid.
x[2] := Get_A_MyType();
end;
Please note that you lose any range checking, and combine that with non zero starting arrays is a VERY VERY bad idea!
If you really need this indexing, then you could write a simple "translation" function, which will receive an index figure in the range from 2 to N and will return an index from 0 to N-2, just by subtracting 2 from the parameter, for example:
function translate(i : integer) : integer;
begin
result := i - 2;
end;
And you could call your array like this:
array[translate(2)]
Of course, you could in addition do range checking within the function, and maybe you could give it a shorter name.
Or even better, wrap the whole access to the array with a function like this:
function XYZ(i : integer) : MyType;
begin
// Do range checking here...
result := MyArray[i - 2];
end;
Hope this helps

Resources