I'm working on a vintage code base and I am very new to delphi, so apologies in advance if my syntax is off a bit (or just plain wrong).
I have things like a pre defined (at compile time) array. arr and we used all over the place
file1.pas: arr[1] := 3.14
file2.pas: pi := arr[1]
And I've noticed that we never,ever use constants to access elements in the array. In C I would write:
int arr[100] = {0};
const int MY_INDEX=1;
arr[MY_INDEX] = 3.14
etc. But I don't see that in my delphi code base. Is that because Delphi/Pascal doesn't support it or because the original authors decided not to do it?
Thanks
Delphi allows you to use a constant as an array index. So if you don't see such a thing in your code, it's because the authors elected not to declare and use constants for array indices.
Yes constants can be used along with any expression that evaluates to an appropriate and valid index for the array. You should also note that an array in Delphi could be declared with a non-zero based index range:
var
MonthlyTotals: array[1..12] of Integer; // Jan = 1, Feb = 2 etc etc
You can even specify the index of an array as an enum type and use enum members for the indices which provides even tighter safety (where possible and appropriate), as per this contrived example:
type
TFileFormat = (ffXML, ffCSV, ffText, ffJSON);
var
sExtensions: array[TFileFormat] of String;
sExtensions[ffXML] := 'xml';
sExtensions[ffCSV] := 'csv';
sExtensions[ffText] := 'txt';
sExtensions[ffJSON] := 'json';
In such cases the array might only have members for certain (contiguous) values in the enum:
var
sExtensions: array[ffXML..ffCSV] of String;
For this reason, and the fact that array indices may not be zero based, unless you are 110% certain of the index range of an array it is a good idea to always use Low() and High() to determine the index bounds when iterating over the contents of an array and not assume the index basis:
// This will not work properly:
for i := 0 to 11 do
MonthlyTotals[i] := ....
// Neither will this, even though it looks more safe
for i := 0 to Pred(Length(MonthlyTotals)) do
MonthlyTotals[i] := ....
// This will be safe:
for i := Low(MonthlyTotals) to High(MonthlyTotals) do
MonthlyTotals[i] := ....
// And it works for enum indices as well:
for ext := Low(sExtensions) to High(sExtensions) do
sExtensions[ext] := ....
The exact conversion of your C code in Delphi would be along the lines of:
var
arr: array[100] of integer;
const
MY_INDEX = 1;
begin
arr[MY_INDEX] := 3.14;
end;
As others have said, perfectly feasible and the enum method is arguably a better alternative. One gotcha here is that this is relying on Delphi having the array initialised zeroed rather than setting it explicitly as in C.
Related
How can I slice a dynamic array to multiple sub arrays? Slice() function in Delphi does not support dynamic arrays. So how can it be done? A generic solution would be welcome.
program Project10;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
var
A: Array of Integer;
begin
SetLength(A, 4);
Slice(A, 2); // [dcc32 Error] Project10.dpr(15): E2193 Slice standard function only allowed as open array argument
end.
Use Copy(A, 0, 2) instead of Slice(A, 2).
The point is that either you need an "open array parameter" (in which case you need Slice) or you need a regular array, in which case Copy will provide a good solution.
It is possible to use pointers in order to access a Dynamic Array from different "start points"
Consider the code:
TYPE
tListArray = ARRAY[0..0] OF CARDINAL; {Or any other type you need}
tDynamicCardinalArray = ARRAY OF CARDINAL;
VAR
MyList : tDynamicCardinalArray;
pArrPos1, pArrPos2 : ^tListArray;
BEGIN
SetLength(MyList,100);
pArrPos1 := #MyList[0];
pArrPos2 := #MyList[50];
...
...
END;
The benefit is that you have direct access, no copying or moving of data involved.
The drawback is that functions such as "high", "low" and "length" CAN NOT be used on these array-pointers. Or at least not if you want a result to use.
You have to make sure that you never go beyond the scope of the SourceArray MyList when you address the array-pointers.
The benefit is that it brings versatility to Dynamic Arrays.
As we know, a Dynamic Array can only be created from 0 .. and up to the size.
pArrPos2 on the other side has effectively rendered the Dynamic Array into an Array-Pointer which also accept negative addressing:
pArrPos2^[-50] := 0; // Equals: MyList[ 0] := 0
pArrPos2^[ 0] := 50; // Equals: MyList[50] := 50
pArrPos2^[ 49] := 99; // Equals: MyList[99] := 99
Using pointers, you can "slice" a Dynamic Array into several pieces, but you have to keep track of the "High", "Low", and "Length" separately, at least if you want a fully dynamic solution that can be changed on the fly in software.
The needed extra information can be passed as an extra parameter to a procedure or function and takes less CPU-time than actually copying or moving a dataset into a new array.
I know. the post was old, but I still think my answer would/could be relevant here, in particular for new readers.
I know I can do that:
const
arrayOfIntegers : Array[1..15] of Integer = (3,2,8,10,1,6,2,13,13,3,13,13,13,3,45);
But how can I do the following instead?
var
arrayOfIntegers : Array[1..15] of Integer;
begin
arrayOfIntegers := (3,2,8,10,1,6,2,13,13,3,13,13,13,3,45);
end;
As soon as I try to compile the code above I get E2029 ')' expected but ',' found
You didn't mention what Delphi version you're using but in the modern Delphi you can do something like this:
var
arrayOfIntegers : TArray<Integer>;
begin
arrayOfIntegers := TArray<Integer>.Create(3,2,8,10,1,6,2,13,13,3,13,13,13,3,45);
end;
A typical use will be the following:
type
TIntegerArray1to15 = Array[1..15] of Integer;
const
INIT_INT_1_15_ARRAY: TIntegerArray1to15 = (3,2,8,10,1,6,2,13,13,3,13,13,13,3,45);
var
arrayOfIntegers : TIntegerArray1to15;
begin
arrayOfIntegers := INIT_INT_1_15_ARRAY;
.... use and update arrayOfIntegers[]
end;
You should better define your own type in this case (code won't be slower or bigger, and you'll be able to make assignments between instances of this type). And you'll ensure that your array boundaries will be as expected (1..15).
The const statement will be compiled as a "reference" array, which will be copied in your arrayOfIntegers local variable. I've made it uppercase, which a somewhat commmon usage when declaring constants (but not mandatory - this is just a personal taste).
If you want your code to be more generic and reusable (which IMHO makes sense if you want to be a lazy programmer) you may rely on dynamic arrays, and/or array of const parameters (if your array start with index 0).
The syntax used in the const section is only valid for typed array constants. You cannot use it as a literal array constant in an assignment.
This is an example of assign a value to a whole array of integers
const
C_ARR_COST : array ['a'..'e'] of string = ('01','02','03');
var Conter:Char;
begin
//Loop
for Conter := Low(C_ARR_COST) to high(C_ARR_COST) do
ShowMessage(C_ARR_COST[Conter]);
//Direct
ShowMessage(C_ARR_COST['a']);
end;
Good luck.
I have a calculation algorithm in Delphi with a number of different options, and I need to try every combination of options to find an optimal solution.
TMyOption = (option1, option2, option3, option4);
TMyOptions = set of TMyOption;
I wondered about using an Integer loop to enumerate them:
for EnumerationInteger := 0 to 15 do begin
Options := TMyOptions(EnumerationInteger);
end;
This does not compile. What I was wondering was if there was any fairly simple method to convert from Integer to Set (most questions on the Web try to go the other way, from Set to Integer), and if so what is it?
Another possibility is to just use the Integer as a bit-field:
C_Option1 = 1;
C_Option2 = 2;
C_Option3 = 4;
C_Option4 = 8;
and then test membership with a bitwise and:
if (Options and C_Option2) > 0 then begin
...
end;
I've tried this, and it works, but it feels like working with sets would be more natural and use the type system better (even though I'm going outside the said type system to enumerate the sets).
Is there a better/safer way to enumerate all possible set combinations than enumerating the underlying integer representation?
Notes:
I know that the integer values of a set are not guaranteed in theory (though I suspect they are in practice if you don't play with the enumeration numbering).
There could be more than four options (yes, I know that it grows exponentially and if there are too many options the algorithm could take forever).
I know this question is quite old, but this is my preference since it's simple and natural to me :
function NumericToMyOptions(n: integer): TMyOptions;
var
Op: TMyOption;
begin
Result:= [];
for Op:= Low(TMyOption) to High(TMyOption) do
if n and (1 shl ord(Op)) > 0 then Include(Result, Op);
end;
Try
var EnumerationByte: Byte;
...
for EnumerationByte := 0 to 15 do begin
Options := TMyOptions(EnumerationByte);
end;
Your code does not compile because your enumeration (TMyOption) have less than 8 values, and Delphi utilize the minimum possible size (in bytes) for sets. Thus, a byte variable will work for you.
If you have a set with more than 8 but less than 16 possible elements, a Word will work (and not an integer).
For more than 16 but less than 32 a DWord variable and typecast.
For more than 32 possible elements, I think a better approach is to use an array of bytes or something like that.
500 - Internal Server Error's answer is probably the most simple.
Another approach that would less likely to break with changes to the number of options would be to declare an array of boolean, and switch them on/off. This is slower than working with pure integers though. The main advantage, you won't need to change the integer type you use, and you can use it if you have more than 32 options.
procedure DoSomething
var BoolFlags : Array[TOption] of Boolean;
I: TOption;
function GetNextFlagSet(var Bools : Array of Boolean) : Boolean;
var idx, I : Integer;
begin
idx := 0;
while Bools[idx] and (idx <= High(Bools)) do Inc(idx);
Result := idx <= High(Bools);
if Result then
for I := 0 to idx do
Bools[I] := not Bools[I];
end;
begin
for I := Low(BoolFlags) to High(BoolFlags) do BoolFlags[i] := False;
repeat
if BoolFlags[Option1] then
[...]
until not GetNextFlagSet(BoolFlags);
end;
Casting from an Integer to a Set is not possible, but Tondrej once wrote a blog article on SetToString and StringToSet that exposes what you want in the SetOrdValue method:
uses
TypInfo;
procedure SetOrdValue(Info: PTypeInfo; var SetParam; Value: Integer);
begin
case GetTypeData(Info)^.OrdType of
otSByte, otUByte:
Byte(SetParam) := Value;
otSWord, otUWord:
Word(SetParam) := Value;
otSLong, otULong:
Integer(SetParam) := Value;
end;
end;
Your code then would become this:
for EnumerationInteger := 0 to 15 do begin
SetOrdValue(TypeInfo(TMyOptions), Options, EnumerationInteger);
end;
--jeroen
The problem is that you are trying to cast to the set type instead of the enumerated type. You can cast between integer and enumerated because both are ordinal types, but you can't cast to a set because they use bitfiels as you already noted. If you use:
for EnumerationInteger := 0 to 15 do begin
Option := TMyOption(EnumerationInteger);
end;
it would work, although is not what you want.
I had this same problem a few months ago and came to the conclusion that you can't enumerate the contents of a set in Delphi (at least in Delphi 7) because the language doesn't define such operation on a set.
Edit: It seems that you can even in D7, see coments to this answer.
Using System.Move() to insert/delete item(s) from an array of string is not as easy as insert/delete it from other array of simple data types. The problem is ... string is reference counted in Delphi. Using Move() on reference-counted data types needs deeper knowledge on internal compiler behaviour.
Can someone here explain the needed steps for me to achieve that, or better with some snippet codes, or direct me to a good reference on the internet?
Oh, Please don't tell me to use the "lazy-but-slow way", that is, for loop, I know that.
I've demonstrated how to delete items from a dynamic array before:
Delphi Q&A: How do I delete an element from an array?
In that article, I start with the following code:
type
TXArray = array of X;
procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
ALength: Cardinal;
i: Cardinal;
begin
ALength := Length(A);
Assert(ALength > 0);
Assert(Index < ALength);
for i := Index + 1 to ALength - 1 do
A[i - 1] := A[i];
SetLength(A, ALength - 1);
end;
You cannot go wrong with that code. Use whatever value for X you want; in your case, replace it with string. If you want to get fancier and use Move, then there's way to do that, too.
procedure DeleteX(var A: TXArray; const Index: Cardinal);
var
ALength: Cardinal;
TailElements: Cardinal;
begin
ALength := Length(A);
Assert(ALength > 0);
Assert(Index < ALength);
Finalize(A[Index]);
TailElements := ALength - Index;
if TailElements > 0 then
Move(A[Index + 1], A[Index], SizeOf(X) * TailElements);
Initialize(A[ALength - 1]);
SetLength(A, ALength - 1);
end;
Since X is string, the Finalize call is equivalent to assigning the empty string to that array element. I use Finalize in this code, though, because it will work for all array-element types, even types that include records, interfaces, strings, and other arrays.
For inserting, you just shift things the opposite direction:
procedure InsertX(var A: TXArray; const Index: Cardinal; const Value: X);
var
ALength: Cardinal;
TailElements: Cardinal;
begin
ALength := Length(A);
Assert(Index <= ALength);
SetLength(A, ALength + 1);
Finalize(A[ALength]);
TailElements := ALength - Index;
if TailElements > 0 then begin
Move(A[Index], A[Index + 1], SizeOf(X) * TailElements);
Initialize(A[Index]);
A[Index] := Value;
end;
Use Finalize when you're about to do something that's outside the bounds of the language, such as using the non-type-safe Move procedure to overwrite a variable of a compiler-managed type. Use Initialize when you're re-entering the defined part of the language. (The language defines what happens when an array grows or shrinks with SetLength, but it doesn't define how to copy or delete strings without using a string-assignment statement.)
You don't state if it is important for you to keep the array elements in the same order or not.
If the order is not relevant, you can so something really really fast like this:
procedure RemoveRecord(Index: integer);
begin
FRecords[Index]:= FRecords[High(FRecords)]; { Copy the last element over the 'deleted' element }
SetLength(FRecords, Length(FRecords)-1); { Cut the last element }
end;
{ I haven't tested the code to see it compiles, but you got the idea anyway... }
Sorting the list
If you have a HUGE list that needs to be modified by the user, you can use methods similar to the one above (break the list order). When the user its done editing (after multiple deletes), you present it with a button called "Sort list". Now he can do the lengthy (sort) operation.
Of course, I assume above that your list can be sorted by a certain parameter.
Sorting the list automatically
An alternative is to automate the sorting process. When the user deleted stuff from the list, start a timer. Keep resetting the timer if the user keeps deleting items. When the timer manages to trigger an event, do the sorting, stop the timer.
To insert a string, simply add a string (the lazy way) to the end of the array (which is an array of pointers), and then use Move to change the order of the elements of this array (of pointers).
If I wanted to insert a string into the middle of a list of strings, I'd use TStringList.Insert. (It does it quickly using System.Move.)
Any particular reason why you're using an array instead of a TStringList?
Call UniqueString() on it, before messing with it.
http://docwiki.embarcadero.com/VCL/en/System.UniqueString
Then you have a string with a single reference.
Fat chance that that is what delete and insert do too, and I doubt you'll be faster.
Just wanting to add this for any people that come here in the future.
Modifying Rob's code, I came up with this way of doing it that uses the newer TArray<T> type constructions.
type
TArrayExt = class(TArray)
class procedure Delete<T>(var A: TArray<T>; const Index: Cardinal; Count: Cardinal = 1);
end;
implementation
class procedure TArrayExt.Delete<T>(var A: TArray<T>; const Index: Cardinal;
Count: Cardinal = 1);
var
ALength: Cardinal;
i: Cardinal;
begin
ALength := Length(A);
Assert(ALength > 0);
Assert(Count > 0);
Assert(Count <= ALength - Index);
Assert(Index < ALength);
for i := Index + Count to ALength - 1 do
A[i - Count] := A[i];
SetLength(A, ALength - Count);
end;
A similar thing can be done for the insert.
(Not looking for this to get marked as the answer, just looking to provide an example that was too long to fit in the comments on Rob's excellent answer.)
(Fixed to address Rob's comments below.)
Move() works fine with reference counted types like strings or interfaces, and actually used internally in Delphi's arrays and lists. But, now, in general case, Move() is no longer valid because of managed records feature.
If you use System.Move to put items into an array of string, you should be aware that the strings that where there before the Move (and now overwritten), had a reference count of either -1 for constant strings, or > 0 for variable strings. Constant strings should not be altered, but variable strings should be treated accordingly: You should manually lower their reference-count (before they're overwritten!). To do that, you should try something like this:
Dec(PStrRec(IntPtr(SomeString)-12).refCnt);
But if the reference-count reached zero, you should also finalize the associated memory - something Delphi itself does a whole lot better if you let it work it's compiler-magic for strings. Oh, and also : if the strings you're copying come from the same array as your writing into, the needed administration becomes very cumbersome, very quickly!
So if it's in some way possible to avoid all this manual housekeeping, I would advise to let Delphi handle it itself.
I originally had an array[1..1000] that was defined as a global variable.
But now I need that to be n, not 1000 and I don't find out n until later.
I know what n is before I fill the array up but I need it to be global therefore need a way to define the size of a global array at run time.
Context is filling an array with a linear transformation of the bytes in a file.
I don't know how big the file is until someone wants to open it and the files can be of any size.
As of Delphi 4, Delphi supports dynamic arrays. You can modify their sizes at run time and they will retain the data you stored in other elements at the old size. They can hold elements of any homogeneous type, including records and other arrays. You can declare a dynamic array the same as you declare normal, "static" arrays, but simply omit the array bounds:
var
ArthurArray: array of TForm;
Although static arrays allow you to specify both the lower and upper bound, the low index of a dynamic array is always zero. The high index is given by the High function, which always returns one less than the length of the array. For any dynamic array x, High(x) = Length(x)-1.
A global variable can be accessed by any code, including local procedures.
A global variable of dynamic-array type will be initialized to be an empty array. Its length will be zero and High called on that array will be -1. Low on that array will still return zero.
At any time, you may resize a dynamic array. Use the SetLength function, just as you can do with strings:
var
NumElements: Integer;
begin
NumElements := GetNumberOfArthurForms();
SetLength(ArthurArray, NumElements);
end;
If you have a multidimensional array, you can set their lengths in a loop:
var
matrix: array of array of Double;
i: Integer;
begin
SetLength(matrix, height);
for i := 0 to height - 1 do
SetLength(matrix[i], width);
end;
There's a shortcut for that to set the lengths of all the inner arrays at once:
begin
SetLength(matrix, height, width);
end;
Like I mentioned, dynamic arrays keep their old values when you resize them:
var
data: array of string;
begin
SetLength(data, 2);
data[1] := 'foo';
SetLength(data, 20);
Assert(data[1] = 'foo');
end;
But if you shorten the array, any elements that resided beyond the new last element are gone forever:
begin
SetLength(data, 20);
data[15] := 'foo';
SetLength(data, 2);
// data[15] does not exist anymore.
SetLength(data, 16);
writeln(data[15); // Should print an *empty* line.
end;
My demonstrations above used strings. Strings are special in Delphi; they're managed by the compiler through reference counts. Because of that, new dynamic-array elements of type string are initialized to be empty. But if I had used integers instead, there would be no guarantee of the values of new elements. They might be zero, but they might be anything else, too, just like the initial values of standalone local variables.
The Delphi 7 help files are very good, I'm told. Please read more about dynamic arrays there. You can find demonstrations of their use throughout the VCL and RTL source code provided in your Delphi installation, as well as in nearly any Delphi code example produced in the last 10 years.
First, here's a general answer to the first part of your question:
If your array is no longer static, you might want to consider using a TList, a TStringList or one of the many container classes in the Contnrs unit.
They may better represent what you are doing, provide additional capabilities you might need, e.g. sorting or name/value pairs, they dynamically grow as you need them, and have been very well optimized.
Then you said:
"Context is filling an array with a linear transformation of the bytes in a file. I don't know how big the file is until someone wants to open it and the files can be of any size."
For your specific problem, I would load the bytes in a file using:
MyFileStream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
Size := MyFileStream.Size - MyFileStream.Position;
SetLength(Buffer, Size);
MyFileStream.Read(Buffer[0], Size);
Then you can easily use a PChar pointer to go through each character or even each byte in the Buffer one by one and transform them the way you need to.