Delphi: How to reference an array from within a class - arrays

I work with arrays and have tested the functionality within the context of a class, like:
Ttest = class
values : array of integer;
procedure doStuff;
end;
The methods, like doStuff all operate on the values array without the need to pass the array as a parameter. This suits me and it is fast.
Now I want to use this class to work with an external array, like Ttest.create(myValues) In the constructor I could copy myValues to the internal values but that would be quite a waste and moreover, at the end the copy would have to be reversed to pass the updated values back.
My question is how can I extend this class so that it can efficiently work with an external array. In pseudo code like this:
constructor create(var myValues : array of integer);
begin
address of values := address of myValues;
doSTuff;
end;

Lesson 1
In Delphi, dynamic arrays are reference types. A variable of dynamic array type contains only a pointer to the actual dynamic array heap object, and in an assignment,
A := B
where A and B are dynamic arrays of the same type, the dynamic array heap object isn't copied. The only thing that happens is that A and B will point to the same dynamic array heap object (that is, the 32- or 64-bit B pointer is copied to A) and that the reference count of the heap object is increased by 1.
Lesson 2
When you write
constructor Create(var AValues: array of Integer);
you need to realise that this, despite the appearance, isn't a dynamic array parameter, but an open array parameter.
If you explicitly want a dynamic array parameter, you need to use such a type explicitly:
constructor Create(AValues: TArray<Integer>);
By definition, TArray<Integer> = array of Integer is a dynamic array of Integers.
Please note that the language only has two types of arrays – static and dynamic; the open array concept is only about function parameters.
If you want to work with dynamic arrays, taking advantage of their nature as reference types, I would suggest you use dynamic array parameters. Then the only thing that is passed to the function (the constructor in this case) is the pointer to the heap object. And the heap object's reference count is increased, of course.
Lesson 3 – An example
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
After SetLength, Arr points to a dynamic array heap object of reference count 1. You can see it in your RAM (press Ctrl+Alt+E, then Ctrl+G and goto Arr[0]). When you enter Test, the reference count is increased to 2 because both Arr and A refer to it. When you leave Test, the reference count is reduced back to 1 because A goes out of scope: now again only Arr refers to it.
Lesson 4
Now, a “gotcha”: if you change the number of elements of a dynamic array, it needs to be reallocated (*). Hence, a new dynamic array heap object is created with a reference count of 1, and the old object has its reference count decreased by 1 (and removed if it becomes zero).
Thus, while the previous example works as expected, the following will not:
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
The SetLength will create a new dynamic array heap object with refcount 1 and copy half of the old array into this, put the new address in the local A parameter, and Test will transform this new array, not touching the old one which the global Arr variable points to. The reference count of the original heap object is decreased by one.
However, if you use a var parameter,
procedure Test(var A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
it will work as before. This effectively works with Arr. If we had increased the number of elements, the array would probably have been reallocated and the global Arr variable would have been updated with the new address.
Conclusion
As long as you don't need to reallocate the memory (change the number of elements), Delphi already gives you what you want, because dynamic arrays are reference types. If you do need to reallocate, at least now you know enough of the technical details in order to reason about it.
Update: Hence, the suggestion is to do
type
TTest = class
FData: TArray<Integer>;
constructor Create(AData: TArray<Integer>);
procedure Enlarge;
procedure Shrink;
procedure ShowSum;
end;
{ TTest }
constructor TTest.Create(AData: TArray<Integer>);
begin
FData := AData; // will NOT copy the array, since dynamic arrays are reference types
end;
procedure TTest.Enlarge;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := 2*FData[i];
end;
procedure TTest.ShowSum;
var
s: Integer;
i: Integer;
begin
s := 0;
for i := 0 to High(FData) do
Inc(s, FData[i]);
ShowMessage(s.ToString);
end;
procedure TTest.Shrink;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := FData[i] div 2;
end;
To test it:
procedure TForm1.FormCreate(Sender: TObject);
var
MyArray: TArray<Integer>;
t: TTest;
begin
MyArray := [1, 2, 3, 4, 5];
t := TTest.Create(MyArray);
try
t.ShowSum;
t.Enlarge;
t.ShowSum;
t.Shrink;
t.ShowSum;
finally
t.Free;
end;
end;
Footnotes
If you (1) decrease the number of elements and (2) the reference count is 1, typically the data isn't moved in memory. If the reference count is > 1, the data is always moved, because SetLength guarantees that the reference count of its argument is 1 when it returns.

Related

How to append one array to another array of same type in Delphi?

How to append one array to another array of the same type without using iterative statements (for or while loops) in Delphi?
In the last Delphi versions (XE7+) you can just use + operator or Concat routine to append arrays. Link. Official help (doesn't mention +)
Otherwise write your own procedure (use generic arrays if possible). Quick example (checked in XE3):
type
TArrHelper = class
class procedure AppendArrays<T>(var A: TArray<T>; const B: TArray<T>);
end;
class procedure TArrHelper.AppendArrays<T>(var A: TArray<T>;
const B: TArray<T>);
var
i, L: Integer;
begin
L := Length(A);
SetLength(A, L + Length(B));
for i := 0 to High(B) do
A[L + i] := B[i];
end;
usage:
var
A, B: TArray<String>;
begin
A := TArray<String>.Create('1', '2', '3');
B := TArray<String>.Create('4', '5');
TArrHelper.AppendArrays<String>(A, B);
If you don't mind that your original array is destroyed, then there is a hackish solution. It is probably a lot faster than a loop, because each iteration of the loop must add one reference, while DestructiveConcatArrays preserves the reference count.
This means that copies of the strings to be moved are not allowed. They are either in Destination, or in Source, but can't be in both at the same time. Otherwise their refcounts would have to be updated anyway -- in a loop.
Note
Between the Move and the FillChar, all string references copied over are not properly refcounted. But after the FillChar, they are again. Care must be taken that nothing, no thread, should be able to access the Source array in that unstable state.
In other words: the following does not require the RTL to add or remove references, but it is tricky and it destroys the original (second) array:
procedure DestructiveConcatArrays(var Destination, Source: TArray<string>);
var
LenD, LenS: Integer;
begin
LenD := Length(Destination);
if LenD = 0 then
Destination := Source
else
begin
LenS := Length(Source);
if Length(Source) > 0 then
begin
SetLength(Destination, LenD + LenS);
// Copy strings -- Afterwards, the refcounts of all strings copied over are
// out of sync.
Move(Source[0], Destination[LenD], LenS * SizeOf(string));
// Clear Source -- Afterwards, all refcounts are in sync again.
FillChar(Source[0], LenS * SizeOf(string), 0);
end;
end;
Source := nil;
end;
Careful!
The above is not a general solution. It is a hack, designed for this single purpose only. But it works as expected. I tested that. But it is not thread-safe, although it can probably be made to be.
Update
This is very much what C++ introduced with move semantics for rvalue expressions. Just consider the Source as an rvalue.
Having two dynamic arrays arr1 and arr2
var
arr1, arr2: array of Integer;
. . .
SetLength(arr1, 3);
arr1[0] := 1;
arr1[1] := 2;
arr1[2] := 3;
SetLength(arr2, 3);
arr2[0] := 4;
arr2[1] := 5;
arr2[2] := 6;
you can append the first to the second like this:
SetLength(arr2, Length(arr2) + Length(arr1));
Move(arr1[0], arr2[3], Length(arr1) * SizeOf(Integer));
See System.Move.
As Uwe Raabe's comment points out, you can do as follows for managed types:
SetLength(arr2, Length(arr2) + Length(arr1));
for i := Low(arr1) to High(arr1) do
arr2[3+i] := arr1[i];

How to refactor copying/moving data between arrays?

I have a process that reads data into 150+ temp arrays, process the data and copies from temp arrays into work arrays. Work arrays are in one global array so I can import multiple data, means I can repeat the same process up to 100 times and end up with set of on big array that holds 100x work data I can work with, compare and do stuff.
I have 150+ arrays, so 150 times:
// for each array
SetLength(myData[Idx].WorkNames,Length(tmpNames)); // <- prepare to copy
for i := 0 to High(tmpNames) do // <- copy
myData[Idx].WorkNames[i]:=tmpNames[i];
SetLength(tmpNames,0); // <- clear tmp array
4 lines of code for each array - 150x4 = 600 loc + initial + empty lines - around 900 loc.
Here is the example of what I do:
type
TName = record
NameID:integer;
Description:string;
end;
TItem = record
ItemID:integer;
Description:string;
Active:boolean;
end;
TWorkData = record
WorkDataType:string;
WorkNames:array of TName;
WorkItems:array of TItem;
end;
var
AllWorkData:array of TWorkData; // <- global array that has all work data - up to 100x sets of work data
tmpNames:array of TName; // <- tmp arrays before saving to work array
tmpItems:array of TItem; //
procedure TForm1.Button1Click(Sender: TObject);
var i,Idx:integer;
begin
// 1. read data into tmp arrays
ReadDataIntoTmpArrays;
ProcessTmpData;
// 2. copy tmp arrays into work data
Idx:=GetWorkDataIdx; // <- work data sequence number; start with 0
AllWorkData[Idx].WorkDataType:=GetWorkDataName(Idx);
SetLength(AllWorkData[Idx].WorkNames,Length(tmpNames));
SetLength(AllWorkData[Idx].WorkItems,Length(tmpItems));
for i := 0 to High(tmpNames) do
AllWorkData[Idx].WorkNames[i]:=tmpNames[i];
for i := 0 to High(tmpItems) do
AllWorkData[Idx].WorkItems[i]:=tmpItems[i];
// 3. clear tmp arrays
SetLength(tmpNames,0);
SetLength(tmpItems,0);
end;
Question: Is there something I can do that is easier to maintain, refactor the code?
If you really do want to copy, then do so using generics. You could derive from the TArray class of static class methods declared in System.Generics.Collections. For instance:
type
TArray = class(Generics.Collections.TArray)
public
class function Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>; overload; static;
class function Copy<T>(const Source: array of T): TArray<T>; overload; static;
end;
....
class function TArray.Copy<T>(const Source: array of T; Index, Count: Integer): TArray<T>;
var
i: Integer;
begin
SetLength(Result, Count);
for i := 0 to high(Result) do begin
Result[i] := Source[i+Index];
end;
end;
class function TArray.Copy<T>(const Source: array of T): TArray<T>;
var
i: Integer;
begin
SetLength(Result, Length(Source));
for i := 0 to high(Result) do begin
Result[i] := Source[i];
end;
end;
Note that all of the above requires you to stop using array of TMyType and instead start using the generic dynamic array TArray<TMyType>.
In your case though you are overcomplicating. Replace:
SetLength(myData[Idx].WorkNames,Length(tmpNames)); // <- prepare to copy
for i := 0 to High(tmpNames) do // <- copy
myData[Idx].WorkNames[i]:=tmpNames[i];
SetLength(tmpNames,0); // <- clear tmp array
with:
myData[Idx].WorkNames := tmpNames;
tmpNames := nil;
If you are prepared to let tmpNames simply leave scope then you can use a single line:
myData[Idx].WorkNames := tmpNames;
Although, if tmpNames is re-used for a different array, then the nil assignment is needed.
Then again, as far as I can see from the code in the question you don't need the temporary arrays at all. Why not operate directly on the long lived data structures.
These array assignments are only permissible if the source and target of the assignment are assignment compatible. Your types are not because you have used distinct types. Switch to TArray<T> to avoid that. See this question for more: Why are two seemingly identical dynamic array types deemed not assignment compatible?
Remember that dynamic arrays are reference types. In the usage shown here, all you need to do is copy the reference. You only need one instance of the actual array. So copying is not necessary at all.

Delphi dynamic array with tlist

I'm creating a simple interpreter in Delphi/Lazarus for a language similar to BASIC.
I already achieved a lot of functionalities. At this moment, I'm trying to create a DIM like command in order to handle multidimensional numeric arrays.
My idea is to use TList to simulate multidimensional arrays limited only by available memory. For example, when a declare in my interpreter a command like the following:
DIM num_arr[3,3,3]
I would like to create a three dimensional array of double, with each index varying from 0 to 2.
So far I have only the function to create the "TList array". I'm using two TList objects to keep the array dimensions and the data items list, I have a third one that holds the indexes for store/retrieve data. What I can't figure is how to associate a list of indexes to a specific entry in the TList. It's simple when the array is up to two dimensions, I can convert each pair of indexes to a numeric sequence, but no success to three and more dimensions.
Is there any algorithm I could use to solve that? It's really hard to find something related to that matter.
Any suggestion about how to implement something similar is welcome. Below I'm publishing part of the code I developed so far:
//Creates a "n" dimensional array
function array_allocate(Dim: TList; var DataArray: TList): integer;
var
i: integer;
s: string;
begin
Result := 1;
//Simple way to find the array length
//For example. An array with dimensions [3,3,3] will handle 27 items
for i := 0 to Dim.Count-1 do
begin
Result := Result * Integer(Dim[i]);
end;
Result := Result;
DataArray.Capacity := Result; //DataArray now handles 27 items
//************************************
//Every line below is just for testing
//************************************
fmMain.Memo1.Lines.Add('Allocating size for array with '+IntToStr(Dim.Count)+' dimension(s).');
s := '';
for i := 0 to Dim.Count-1 do
s := s + '['+IntToStr(Integer(Dim[i]))+']';
fmMain.Memo1.Lines.Add('DIM Sizes: '+s);
fmMain.Memo1.Lines.Add('Manage: '+IntToStr(Result)+' Items.');
end;
{*************************************}
{NOT functional, this is the challenge}
{*************************************}
function calculate_offset(Dim, Indexes: TList; var DataArray: TList; BaseAddr: integer): integer;
var
i, Depth, dimIdx: Integer;
k,index,sizeProduct: integer;
begin
for Depth := 0 to Dim.Count-1 do
for dimIdx := 0 to Integer(Dim[Depth])-1 do
fmMain.mmOut.Lines.Add('Dim: '+IntToStr(Depth)+' ,Index: '+IntToStr(dimIdx));
result := 0;
end;
procedure TfmMain.FormShow(Sender: TObject);
var
dataList: TList; //keep the data
dimList: TList; //keep the dimensions
indexList: TList; //keep the indexes
offset: integer;
begin
dimList := TList.Create; //create the dim array
//simulate the creation of an array with dimension [3,3,3]
//something like DIM myVar[3,3,3]
dimList.Add(Pointer(3));
dimList.Add(Pointer(3));
dimList.Add(Pointer(3));
dataList := TList.Create; //create the data list
array_allocate(dimList, dataList); //allocates memory
//indexList is the way to indicate which index to retrieve/store data
indexList := TList.Create;
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
indexList.Add(Pointer(1));
//The idea is to relate indexes like [0,0,0], [0,0,1], [0,1,1] to
//a numeric sequence between 0 to 26 in this case (DIM = [3,3,3])
offset := calculate_offset(dimList, indexList, dataList, 1, 0);
indexList.Free;
dimList.Free;
dataList.Free;
end;
As I read your question, you want to convert from a tuple of indices to a linear index, assuming row major storage.
Suppose the dimensions are held in a dynamic array, dim and the indices are in a dynamic array, idx. Then the linear index is calculated like this:
function LinearIndex(const dim, idx: array of Integer): Integer;
var
i: Integer;
begin
Assert(Length(dim)=Length(idx));
Assert(Length(dim)>0);
Result := idx[0];
for i := 1 to high(dim) do
Result := Result*dim[i-1] + idx[i];
end;
The LinearIndex function described above was not functional to certain unbalanced arrays. For example, if I properly allocate "dim" to hold the value [2,3], the function would return 2 as the index for both contents of "idx": [0,2], [1,0].
I created a Pascal function based on another post I found here at StackOverflow and this one seems to work.
function LinearIndex2(const dim, idx: array of Integer): Integer;
var
i,index,mult: Integer;
begin
Assert(Length(dim)=Length(idx));
Assert(Length(dim)>0);
index := 0; mult := 1;
for i := 0 to High(dim) do
begin
index := index + idx[i] * mult;
mult := mult * dim[i];
end;
result := index;
end;

Reference a Delphi variant array without copying

This might seem like a strange request, but there is a good reason (code generation application). I pass a variant array to a procedure, contained within an array of variants, as follows:
TVarArray = array of variant;
procedure TMainForm.Button1Click(Sender: TObject);
var
params: TVarArray;
numRows: integer;
numCols: integer;
i: integer;
j: integer;
begin
SetLength(params, 2);
numRows := 2;
numCols := 2;
params[0] := 5;
params[1] := VarArrayCreate([1, numRows, 1, numCols], varVariant);
for i := 1 to numRows do
for j := 1 to numCols do
params[1][i, j] := i + j;
TestProc(params);
end;
procedure TMainForm.TestProc(params: TVarArray);
var
arr: variant;
p: PVariant;
v: variant;
begin
arr := params[1]; // -- Copies the array to arr.
arr[2, 2] := 99;
p := #(params[1]);
p^[2, 2] := 88; // -- Directly reference the passed-in array.
v := p^; // -- Copies the array to v -> How to prevent?
v[2, 2] := 77; // -- This should change the value in the original array.
edit1.Text := VarToStr(arr[2, 2]); // -- 99
edit2.Text := VarToStr(params[1][2, 2]); // -- 88 - should be 77
edit3.Text := VarToStr(v[2, 2]); // -- 77
end;
I don't want to create a copy of the array, so could use p^[] to directly access the passed-in array. However, I don't want to use the p^ syntax in TestProc but would prefer to use a variable name without the ^. Of course, if I try v := p^ I just get a copy. Is there any way around this? Thanks!
What you're looking for is a local variable that can act as a reference for something else (in particular, an element in an array of Variant). However, Delphi provides no way of creating a "local reference" variable. References only exist in the context of parameters passed as var, out, or sometimes const.
Maybe you could introduce a subroutine and pass param[1] as a var parameter. Inside the subroutine, you could refer to that parameter, and it would alias the array element form the caller. For example:
procedure ModifyVariant(var p: Variant);
begin
p[2, 2] := 77;
end;
procedure TMainForm.TestProc(params: TVarArray);
var
p: PVariant;
begin
p := #params[1];
ModifyVariant(params[1]);
Assert(params[1][2, 2] = p^[2, 2]);
end;
ModifyVariant could even be an anonymous procedure so you can implement within the same scope as the caller:
procedure TMainForm.TestProc(params: TVarArray);
var
ModifyVariant: reference to procedure(var x: Variant);
p: PVariant;
begin
p := #params[1];
ModifyVariant := procedure(var v: Variant)
begin
v[2, 2] := 77;
end;
ModifyVariant(params[1]);
Assert(params[1][2, 2] = p^[2, 2]);
end;
Neither of those looks particularly appealing, though, especially if you're afraid that mere pointer access will "spook" your code's consumers.
You've mentioned that you expect your users to incorporate their own code into the code you're generating. I wouldn't advise that. After all, what are they expected to do after they re-run your code generator? Surely they'll lose whatever customizations they've made. It's better to keep generated code separate, ideally in a separate file. For user customization, you can provide hooks in the form of callback functions that users can implement. That way, for example, a user could provide something analogous to ModifyVariant, and then your generated code can simply call it. You'll have your "Variant references," and you'll have generated code cleanly separated from user code.

How to swap two rows of a two-dimensional array, and why would it work?

Summarization:
Please check the comments below from David, Uwe, and other experts.
================================================================================
The following code swaps two rows in a two-dimensional, dynamic array of double values. I am wondering: (1) whether the following code is a best practice of swapping two rows of a two-dimensional array? If not, then what is the best practice to do this kind of job? (2) why would the following code work? I mean, isn't two-dimensional array a continuous contiguous section of memory? Does the following code work only by luck? Any suggestion is appreciated!
unit Unit5;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TAADouble = array of array of Double;
TForm5 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form5: TForm5;
procedure SwapRows(arr: TAADouble; row0, row1: Integer);
implementation
{$R *.dfm}
procedure SwapRows(arr: TAADouble; row0, row1: Integer);
var
Tmp: Integer;
begin
{$IFDEF FPC}
Tmp := PtrUInt(arr[row0]);
PtrUInt(arr[row0]) := PtrUInt(arr[row1]);
PtrUInt(arr[row1]) := Tmp;
{$ELSE}
Tmp := Integer(arr[row0]);
Integer(arr[row0]) := Integer(arr[row1]);
Integer(arr[row1]) := Tmp;
{$ENDIF}
end;
procedure TForm5.FormShow(Sender: TObject);
var
tmpArray: TAADouble;
I, J: Integer;
rowStr: string;
begin
SetLength(tmpArray, 10, 10);
rowStr := '';
for I := 0 to 9 do
for J := 0 to 9 do
tmpArray[I][J] := I * J;
for I := 0 to 9 do
begin
rowStr := '';
for J := 0 to 9 do
rowStr := rowStr + FloatToStr(tmpArray[I][J]) + ' ';
OutputDebugString(PWideChar(rowStr));
end;
SwapRows(tmpArray, 3, 4);
for I := 0 to 9 do
begin
rowStr := '';
for J := 0 to 9 do
rowStr := rowStr + FloatToStr(tmpArray[I][J]) + ' ';
OutputDebugString(PWideChar(rowStr));
end;
end;
end.
You ask:
Does the following code work only by
luck?
Well, yes, you are relying on implementation specific details.
In fact the correct way to write it is perfectly natural and simple:
type
TDoubleArray = array of Double;
TDoubleMatrix = array of TDoubleArray;
procedure SwapRows(M: TDoubleMatrix; Row1, Row2: Integer);
var
Temp: TDoubleArray;
begin
Temp := M[Row1];
M[Row1] := M[Row2];
M[Row2] := Temp;
end;
You need to declare an intermediate type for the row, TDoubleArray, so that you can perform the assignment to Temp in the swap routine.
A 2D constant size array
array [1..M] of array [1..N] of TMyType
is a contiguous block of memory.
A 2D dynamically size array as you have is not. Indeed it can even be ragged in the sense that the rows have different numbers of columns. So you can have, say, a triangular matrix.
A dynamic array is implemented as a pointer to a memory block representing that array. So a two-dimensional dynamic array is actually a pointer to an array of pointers. Thats why swapping the row(-pointer)s actually works.
See David's answer for a cleaner approach.
Update:
If you are allowed to use generics you might as well do this:
procedure <SomeClassOrRecord>.SwapRows<T>(var arr: TArray<T>; row0, row1: Integer);
var
Tmp: T;
begin
Tmp := arr[row0];
arr[row0] := arr[row1];
arr[row1] := Tmp;
end;

Resources