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
Related
I'm trying to convert an array of T into a Variant (varArray).
With not-generic types (i.e: Integer), I'm using the following function:
function ToVarArray(AValues : array of Integer) : Variant;
var
i : integer;
begin
Result := VarArrayCreate(
[Low(AValues), High(AValues)],
varInteger
);
for i := Low(AValues) to High(AValues) do
Result[i] := AValues[i];
end;
I'm having some problems while trying to do the same thing with a generic TArray:
uses
System.Generics.Collections;
type
TArray = class(System.Generics.Collections.TArray)
public
class function ToVarArray<T>(const AValues: array of T) : Variant; static;
end;
I've tried the following:
class function TArray.ToVarArray<T>(const AValues: array of T) : Variant;
var
i : integer;
Tmp : T;
begin
Result := Tmp;
Result := VarArrayCreate(
[Low(AValues), High(AValues)],
VarType(Result)
);
for i := Low(AValues) to High(AValues) do
Result[i] := AValues[i];
end;
But It produces the following compile error at each row where I'm assigning a T to a Variant:
[dcc32 Error] Unit1.pas(36): E2010 Incompatible types: 'Variant' and
'T'
The title of the question says that you'd like to process generic TArray<T>, but the first sentence say it's array of T. You might think that both terms refer to the same data structure (dynamic array), and most of the time they do, but they make difference if you use them in place of a procedure/function argument declaration.
Therefore the following methods have different signatures and accept different types of parameters:
class function TArray.ToVarArray<T>(const AValues: TArray<T>): Variant;
class function TArray.ToVarArray<T>(const AValues: array of T): Variant;
While the first invariant accepts true dynamic array as a parameter, the latter takes an open array. This unfortunate language design is regular source of confusion for Delphi developers.
Delphi RTL already contains function that converts dynamic array to variant array of appropriate type - DynArrayToVariant. It takes pointer to initial element of dynamic array and array's type information as its arguments. To make use of generics you would write:
uses
System.Variants;
class function TArray.DynArrayToVarArray<T>(const AValues: TArray<T>): Variant;
begin
DynArrayToVariant(Result, #AValues[0], TypeInfo(TArray<T>));
end;
Your code would fail to compile if you try to use this routine with a static array:
var
SA: array[0..2] of Integer;
begin
SA[0] := 0;
SA[1] := 1;
SA[2] := 2;
{ E2010 Incompatible types: 'System.TArray<System.Integer>' and 'array[0..2] of Integer' }
TArray.DynArrayToVarArray<Integer>(SA);
end.
Open array solves this issue, but you need to "convert" it to dynamic array in order to use it with RTL's DynArrayToVariant.
class function TArray.OpenArrayToVarArray<T>(const AValues: array of T): Variant;
var
LArray: TArray<T>;
Index: Integer;
begin
SetLength(LArray, Length(AValues));
for Index := Low(AValues) to High(AValues) do
LArray[Index] := AValues[index];
Result := DynArrayToVarArray<T>(LArray);
end;
var
DA: TArray<Integer>;
SA: array[0..2] of Integer;
begin
DA := [0, 1, 2];
SA[0] := 0;
SA[1] := 1;
SA[2] := 2;
{ these all work }
TArray.OpenArrayToVarArray<Integer>(DA); // dynamic array
TArray.OpenArrayToVarArray<Integer>(SA); // static array
TArray.OpenArrayToVarArray<Integer>([0, 1, 2]); // open array constructor
end.
The above OpenArrayToVarArray<T> routine is pretty ineffective both in terms of performance and memory usage, because it copies elements one by one from open array to dynamic array. If you really need to support open arrays you should probably write better implementation inspired by RTL's DynArrayToVariant, however I find that one to be sub-optimal too.
how do I correctly write this ?:
If number is different from Array[1] to Array[x-1] the begin......
where number is an integer and array is an array of integers from 1 to x
I believe you want to do something if number is not found in the array MyArray. Then you can do it like this:
NoMatch := True;
for i := Low(MyArray) to High(MyArray) do
if MyArray[i] = number then
begin
NoMatch := False;
Break;
end;
if NoMatch then
DoYourThing;
You could create a function that checks if a number is found in an array. Then you can use this function every time you need to perform such a check. And each time, the code will be more readable. For example, you could do it like this:
function IsNumberInArray(const ANumber: Integer;
const AArray: array of Integer): Boolean;
var
i: Integer;
begin
for i := Low(AArray) to High(AArray) do
if ANumber = AArray[i] then
Exit(True);
Result := False;
end;
...
if not IsNumberInArray(number, MyArray) then
DoYourThing;
If you use a old version of Delphi, you have to replace Exit(True) with begin Result := True; Exit; end. In newer versions of Delphi, I suppose you could also play with stuff like generics.
You could also write a Generic version, however you can't use generics with stand-alone procedures, they need to be bound to a class or record. Something like the following
unit Generics.ArrayUtils;
interface
uses
System.Generics.Defaults;
type
TArrayUtils<T> = class
public
class function Contains(const x : T; const anArray : array of T) : boolean;
end;
implementation
{ TArrayUtils<T> }
class function TArrayUtils<T>.Contains(const x: T; const anArray: array of T): boolean;
var
y : T;
lComparer: IEqualityComparer<T>;
begin
lComparer := TEqualityComparer<T>.Default;
for y in anArray do
begin
if lComparer.Equals(x, y) then
Exit(True);
end;
Exit(False);
end;
end.
usage would be
procedure TForm6.Button1Click(Sender: TObject);
begin
if TArrayUtils<integer>.Contains(3, [1,2,3]) then
ShowMessage('Yes')
else
ShowMessage('No');
end;
Should work with parameters like TArray<integer> or array of integer as well as constant arrays (shown) - and you could add many other methods to the class, such as IndexOf or Insert...
From Delphi 10.3 you can omit the <integer> due to type inferencing so would look like TArrayUtils.Contains(3, [1,2,3]).
I had the same question and solved it like this:
if value in myArray then
...
But as I needed to compare with specific values, I simply did:
if value in [0, 1, 2, 3] then
...
How can I remove empty elements or elements with nil pointers from an Array? A generic solution would be welcome.
You could write it like this:
type
TArrayHelper = class
class function RemoveAll<T>(var Values: TArray<T>; const Value: T); static;
end;
....
function TArrayHelper.RemoveAll<T>(var Values: TArray<T>; const Value: T);
var
Index, Count: Integer;
DefaultComparer: IEqualityComparer<T>;
begin
// obtain an equality comparer for our type T
DefaultComparer := TEqualityComparer<T>.Default;
// loop over the the array, only retaining non-matching values
Count := 0;
for Index := 0 to high(Values) do begin
if not DefaultComparer.Equals(Values[Index], Value) then begin
Values[Count] := Values[Index];
inc(Count);
end;
end;
// re-size the array
SetLength(Values, Count);
end;
Suppose that you had an array of pointers:
var
arr: TArray<Pointer>;
Then you would remove the nil elements like this:
TArrayHelper.RemoveAll<Pointer>(arr, nil);
This code takes the easy way out and always uses the default comparer. For more complex types that is no good. For instance some records need custom comparers. You would need to supply a comparer to support that.
The above implementation is as simple as possible. In terms of performance, it may well be wasteful in the likely common scenario where no matching values, or very few, are found. That's because the version above unconditionally assigns, even if the two indices are the same.
Instead, if there was an issue with performance, you might optimize the code by stepping through the array as far as the first match. And only then start moving values.
function TArrayHelper.RemoveAll<T>(var Values: TArray<T>; const Value: T);
var
Index, Count: Integer;
DefaultComparer: IEqualityComparer<T>;
begin
// obtain an equality comparer for our type T
DefaultComparer := TEqualityComparer<T>.Default;
// step through the array until we find a match, or reach the end
Count := 0;
while (Count<=high(Values))
and not DefaultComparer.Equals(Values[Count], Value) do begin
inc(Count);
end;
// Count is either the index of the first match or one off the end
// loop over the rest of the array copying non-matching values to the next slot
for Index := Count to high(Values) do begin
if not DefaultComparer.Equals(Values[Index], Value) then begin
Values[Count] := Values[Index];
inc(Count);
end;
end;
// re-size the array
SetLength(Values, Count);
end;
As you can see this is a lot more difficult to analyse. You would only contemplate doing this if the original version was a bottleneck.
I have a question. I am a newbie with Run Time Type Information from Delphi 2010. I need to set length to a dynamic array into a TValue. You can see the code.
Type TMyArray = array of integer;
TMyClass = class
publihed
function Do:TMyArray;
end;
function TMyClass.Do:TMyArray;
begin
SetLength(Result,5);
for i:=0 to 4 Result[i]=3;
end;
.......
.......
......
y:TValue;
Param:array of TValue;
.........
y=Methods[i].Invoke(Obj,Param);//delphi give me a DynArray type kind, is working, Param works to any functions.
if Method[i].ReturnType.TypeKind = tkDynArray then//is working...
begin
I want to set length for y to 10000//i don't know how to write.
end;
I don't like Generics Collections.
TValue wasn't designed for arbitrary manipulation of its contents (it would have more helpers for e.g. setting record fields etc. if so), but rather for transporting values between concrete static types and dynamic RTTI. In this respect, TValue.SetArrayElement is an anomaly, and in hindsight, perhaps should not have been included. However, what you ask is possible:
uses Rtti;
type
TMyArray = array of Integer;
TMyClass = class
function Go: TMyArray;
end;
function TMyClass.Go: TMyArray;
var
i: Integer;
begin
SetLength(Result, 5);
for i := 0 to 4 do
Result[i] := 3;
end;
procedure P;
var
ctx: TRttiContext;
v: TValue;
len: Longint;
i: Integer;
begin
v := ctx.GetType(TMyClass).GetMethod('Go').Invoke(TMyClass.Create, []);
Writeln(v.ToString);
len := 10;
DynArraySetLength(PPointer(v.GetReferenceToRawData)^, v.TypeInfo, 1, #len);
Writeln(v.GetArrayLength);
for i := 0 to v.GetArrayLength - 1 do
Writeln(v.GetArrayElement(i).ToString);
end;
begin
P;
end.
I'd like to set the length of a dynamic array, as suggested in this post. I have two classes TMyClass and the related TChildClass defined as
TChildClass = class
private
FField1: string;
FField2: string;
end;
TMyClass = class
private
FField1: TChildClass;
FField2: Array of TChildClass;
end;
The array augmentation is implemented as
var
RContext: TRttiContext;
RType: TRttiType;
Val: TValue; // Contains the TMyClass instance
RField: TRttiField; // A field in the TMyClass instance
RElementType: TRttiType; // The kind of elements in the dyn array
DynArr: TRttiDynamicArrayType;
Value: TValue; // Holding an instance as referenced by an array element
ArrPointer: Pointer;
ArrValue: TValue;
ArrLength: LongInt;
i: integer;
begin
RContext := TRTTIContext.Create;
try
RType := RContext.GetType(TMyClass.ClassInfo);
Val := RType.GetMethod('Create').Invoke(RType.AsInstance.MetaclassType, []);
RField := RType.GetField('FField2');
if (RField.FieldType is TRttiDynamicArrayType) then begin
DynArr := (RField.FieldType as TRttiDynamicArrayType);
RElementType := DynArr.ElementType;
// Set the new length of the array
ArrValue := RField.GetValue(Val.AsObject);
ArrLength := 3; // Three seems like a nice number
ArrPointer := ArrValue.GetReferenceToRawData;
DynArraySetLength(ArrPointer, ArrValue.TypeInfo, 1, #ArrLength);
{ TODO : Fix 'Index out of bounds' }
WriteLn(ArrValue.IsArray, ' ', ArrValue.GetArrayLength);
if RElementType.IsInstance then begin
for i := 0 to ArrLength - 1 do begin
Value := RElementType.GetMethod('Create').Invoke(RElementType.AsInstance.MetaclassType, []);
ArrValue.SetArrayElement(i, Value);
// This is just a test, so let's clean up immediatly
Value.Free;
end;
end;
end;
ReadLn;
Val.AsObject.Free;
finally
RContext.Free;
end;
end.
Being new to D2010 RTTI, I suspected the error could depend on getting ArrValue from the class instance, but the subsequent WriteLn prints "TRUE", so I've ruled that out. Disappointingly, however, the same WriteLn reports that the size of ArrValue is 0, which is confirmed by the "Index out of bounds"-exception I get when trying to set any of the elements in the array (through ArrValue.SetArrayElement(i, Value);). Do anyone know what I'm doing wrong here? (Or perhaps there is a better way to do this?) TIA!
Dynamic arrays are kind of tricky to work with. They're reference counted, and the following comment inside DynArraySetLength should shed some light on the problem:
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
Your object is holding one reference to it, and so is the TValue. Also, GetReferenceToRawData gives you a pointer to the array. You need to say PPointer(GetReferenceToRawData)^ to get the actual array to pass to DynArraySetLength.
Once you've got that, you can resize it, but you're left with a copy. Then you have to set it back onto the original array.
TValue.Make(#ArrPointer, dynArr.Handle, ArrValue);
RField.SetValue(val.AsObject, arrValue);
All in all, it's probably a lot simpler to just use a list instead of an array. With D2010 you've got Generics.Collections available, which means you can make a TList<TChildClass> or TObjectList<TChildClass> and have all the benefits of a list class without losing type safety.
I think you should define the array as a separate type:
TMyArray = array of TMyClass;
and use that.
From an old RTTI based XML serializer I know the general method that you use should work (D7..2009 tested):
procedure TXMLImpl.ReadArray(const Name: string; TypeInfo: TArrayInformation; Data: Pointer; IO: TParameterInputOutput);
var
P: PChar;
L, D: Integer;
BT: TTypeInformation;
begin
FArrayType := '';
FArraySize := -1;
ComplexTypePrefix(Name, '');
try
// Get the element type info.
BT := TypeInfo.BaseType;
if not Assigned(BT) then RaiseSerializationReadError; // Not a supported datatype!
// Typecheck the array specifier.
if (FArrayType <> '') and (FArrayType <> GetTypeName(BT)) then RaiseSerializationReadError;
// Do we have a fixed size array or a dynamically sized array?
L := FArraySize;
if L >= 0 then begin
// Set the array
DynArraySetLength(PPointer(Data)^,TypeInfo.TypeInformation,1,#L);
// And restore he elements
D := TypeInfo.ElementSize;
P := PPointer(Data)^;
while L > 0 do begin
ReadElement(''{ArrayItemName},BT,P,IO); // we allow any array item name.
Inc(P,D);
Dec(L);
end;
end else begin
RaiseNotSupported;
end;
finally
ComplexTypePostfix;
end;
end;
Hope this helps..