Why is the assignment in the for...in loop disallowed by the compiler?
procedure TForm1.Button1Click(Sender: TObject);
Var
ars : Array [0..10] of Integer;
s : Integer;
ct : Integer;
begin
ct := 0;
for s in ars do
Begin
s := ct; // Does not compile!
Inc(ct);
End;
End;
This is not supported, just as even simple loop iterator variables cannot be modified in a "normal" for loop. Even if this were supported in a for-in, it would not make much sense in this case.
Integers are value types, so in each iteration of the loop all that would be achieved is that s would be initialised to a value from an element the array and then s overwritten by Ct.
But the array contents would not be modified and the net effect of the code would be "no change".
To get what you expect from a for-in you would have to be able to iterate using a suitable reference type (in this case a PInteger - pointer to integer) yielding references to the array elements, rather than copies of the values of those elements. A new value for each element could then be assigned using the dereferenced pointer:
var
ars : array [0..10] of Integer;
s : PInteger;
ct : Integer;
begin
ct := 0;
for s in ars do // << this WON'T yield pointers to the array elements ..
begin
s^ := Ct; // .. but if it did you could then write this
Inc(ct);
end;
end;
But don't get excited - this won't work either, it merely demonstrates the nature of the problem stemming from the difference in a reference vs a value.
I know nothing about Delphi specifically. However, most languages don't allow you to assign to the iteration variable in a foreach. Why do you want to do this?
just use a while loop instead.
procedure TForm1.Button1Click(Sender: TObject);
Var
ars : Array [0..10] of Integer;
i : Integer;
ct : Integer;
begin
ct := 0;
i := 0;
while i < Length(ars) do
Begin
ars[i] := Ct; //Does Compile!
Inc(ct);
inc(i);
End;
End;
To understand this better, I would say, "understand s as being controlled by the for s in .... construct", that is to say, while s is in control of the for loop, a well written compiler for almost any language will block you from doing this. Any compiler that is not well enough written to block this, should be backed up by a compiler warning, or a lint-tool that indicates you are doing something that is at best, terribly bad style, and at worst, perhaps will lead to some "undefined" behavior that would be hard to predict. What happens if you set s to a value that is higher than the Length(ars)? Should the loop abort, or should it continue?
The variable S is just a copy of the value in the array, so changing it would have no meaning. The construct
for s in ars do
is basically equivalent to
for i := low(ars) to high(ars) do
s := ars[i]
so there's no point assigning to S. Do the loop this way
procedure TForm1.Button1Click(Sender: TObject);
Var
ars : Array [0..10] of Integer;
i : Integer;
ct : Integer;
begin
ct := 0;
for i := low(ars) to high(ars) do
Begin
ars[i] := ct;
Inc(ct);
End;
End;
Related
I've attempted to implement a merge sort for strings however I cannot perform the recursive part and I get the error "Invalid Pointer Operation"
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
var i : Integer;
const MyArray : array[1..5]of string = ('hi', 'zebra', 'apple', 'Xylophone', 'dog');
Procedure merge(result, left, right : array of string);
var i, i1, i2 : Integer;
begin
i1 := 0;
i2 := 0;
for i := 0 to Length(result) do
begin
if (i2 >= Length(right)) or (i1 < Length(left)) and (StrComp(PChar(left[i]), PChar(right[i2])) < 0) then
begin
result[i] := left[i1];
inc(i1);
end
else
begin
result[i] := right[i2];
inc(i2);
end;
end;
end;
Procedure mergeSort(OriginalList : array of string);
var left, right : array of string;
i : Integer;
begin
if (Length(OriginalList) >= 2) then
begin
setlength(left, length(OriginalList) div 2);
setlength(right, length(OriginalList) - (length(OriginalList) div 2));
for i := 0 to Length(left) do
begin
left[i] := OriginalList[i];
end;
for i := 0 to Length(right) do
begin
right[i] := OriginalList[i + Length(OriginalList) div 2];
end;
mergeSort(left);
mergeSort(right);
merge(OriginalList, left, right);
end;
end;
begin
writeln('The data before sorting: ');
for i := low(MyArray) to High(MyArray) do
begin
write(MyArray[i]+' ');
end;
writeln;
mergeSort(MyArray);
writeln('The data before sorting: ');
for i := low(MyArray) to High(MyArray) do
begin
write(MyArray[i]+' ');
end;
readln;
end.
On the line in the mereSort function where I recall the merge sort function on the arrays "left" and "right", I get the error message but I don't quite understand why?
There are many different things wrong with this, hopefully these points will help you in the right direction.
Problems with Array Indexes
You are indexing beyond the end of your arrays:
Dynamic arrays are indexed starting from zero so the line
for i := 0 to Length(left) do
should be
for i := 0 to Length(left) - 1 do
or you can use
for i := Low(left) to High(left) do
As you did later.
I would recommend you choose a standard form and use it consistently, and also that you avoid declaring constant arrays with non-zero based indexing unless you have good reason, this way you can use the same forms consistently or change the type of array later without running into trouble
This first fix will stop your program crashing, but you'll notice your sort code isn't changing anything...
Problems with parameter passing
Delphi has several different ways to pass parameters into procedures:
procedure doSomething(a : array of string);
procedure doSomething(var a : array of string);
procedure doSomething(out a : array of string);
procedure doSomething(const a : array of string);
These determine how what happens inside the procedure can affect the original variable passed
This is something you will need to understand, read up in the documentation:
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Parameters_(Delphi)
There is IMO some very confusing behaviour and syntax relating to array parameters and a lot of stuff that seems intuitive is not allowed especially with XE/older version, its worth reading the documentation about the standard data types
In the current state, your merge procedure will have no effect because it only operates on a new copy of the array you pass in, which you have also declared as constant
Other
I would avoid the use of result as a procedure parameter since this is the name used for function return values, it seems like asking for trouble to use it like that.
PS: I haven't looked at the logic of the merging, just the basic language mistakes
I'm adding strings with objects (also strings) to a TComboBox, but getting corrupted strings when trying to retrieve them later.
This is how I'm adding them:
var
i: Integer;
sl: TStringList;
c: Integer;
s: PChar;
begin
for i := 1 to tblCalls.FieldCount do
if tblCalls.Fields[i - 1].Tag = 1 then
ListBox1.Items.Append(tblCalls.Fields[i - 1].FieldName);
sl := TStringList.Create;
try
LoadStyles(TStrings(sl));
for c := 0 to sl.Count - 1 do
begin
s := PChar(sl.Values[sl.Names[c]]);
ComboBox1.Items.AddObject(sl.Names[c], TObject(s));
end;
finally
sl.Free;
end;
end;
procedure LoadStyles(var AStylesList: TStrings);
var
f, n: String;
filelist: TStringDynArray;
begin
f := ExtractFilePath(ParamStr(0)) + 'Styles';
if (not DirectoryExists(f)) then
Exit;
filelist := TDirectory.GetFiles(f);
for f in filelist do
begin
n := ChangeFileExt(ExtractFileName(f), EmptyStr);
AStylesList.Add(n + '=' + f);
end;
end;
..and this is where I'm trying to retrieve a string object:
procedure TfrmOptions.ComboBox1Change(Sender: TObject);
var
si: TStyleInfo;
i: Integer;
s: String;
begin
i := TComboBox(Sender).ItemIndex;
s := PChar(TComboBox(Sender).Items.Objects[i]);
Showmessage(s); // --> Mostly shows a corrupted string (gibberish chars)
if (TStyleManager.IsValidStyle(s, si)) then
begin
if (not MatchStr(s, TStyleManager.StyleNames)) then
TStyleManager.LoadFromFile(s);
TStyleManager.TrySetStyle(si.Name);
end;
end;
I suspect that its something with the way I'm adding them. Perhaps I need to allocate memory at:
s := PChar(sl.Values[sl.Names[c]]);
Not sure. Looking at the help on StrNew, NewStr and StrAlloc, it says that those functions are deprecated. Can you help point out whats wrong?
There's nothing to keep the string alive. When you write:
s := PChar(sl.Values[sl.Names[c]]);
an implicit local variable of type string is created to hold whatever sl.Values[sl.Names[c]] evaluates to. That local variable goes out of scope, as far as the compiler is aware, nothing references it, and the string object is destroyed.
In fact, it's even worse than that. Because the assignment above happens in a loop, there is only one implicit local variable. Each time round the loop, the string that you asked the combo box to remember is destroyed.
You need to find a way to extend the lifetime of the string. You could do it like this:
var
StrPtr: ^string;
....
for c := 0 to sl.Count - 1 do
begin
New(StrPtr);
StrPtr^ := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], TObject(StrPtr));
end;
Then when you need to access the string you can do so like this:
var
StrPtr: ^string;
....
TObject(StrPtr) := TComboBox(Sender).Items.Objects[i];
// do something with StrPtr^
When you clear the combo box you must also run through each item and call Dispose on the pointer.
Having said that, it's going to be much easier not to do it that way. Stop trying to force strings into the TObject data associated with each item. Instead keep a parallel string list containing these strings. When you need to look up a name look it up in that list rather than in the combo box.
I know this is an old question but I came across this problem again and rather than use the separate string list I used an object with a string value (I think someone suggested it in a comment) as follows:
Declare a type as TObject with a string value:
TStringObject = class(TObject)
StringValue : string;
end;
Then when adding your items declare a local var of TStringObject and create a new instance for each item:
var
strObj : TStringObject
begin
...
for c := 0 to sl.Count - 1 do
begin
strObj := TStringObject.Create;
strObj.StringValue := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], strObj);
end;
And when retrieving the values:
s := TStringObject(TComboBox(Sender).Items.Objects[i]).StringValue;
As #Dejan Dozet mentions in the comments - you should always free the data objects before freeing the TStringList!
In pascal, the only way I dared cleaning my array was to simply iterate through it and clear it, but it is extremely inefficient. Can't I simply reinitialize it by assigning an empty array to it?
program arrays;
var
working, empty : array [1..10] of integer;
begin
working[3] := 5;
working:= empty;
end.
Is is ok to do this, can this backfire?
If you want to clear the array, writing:
working:= empty;
will in fact do the clearing, by copying the empty array content into working... in your case empty is void, since it is a global variable, so initialized with 0.
IMHO it is not a good practice to define such global variables. Global variables are evil in most cases (unless you know what you are doing), and in case of declaring them to be initialized with 0 does not make sense.
In fact, if empty is initialized on the stack (i.e. a var within a method), it is filled with whatever is on the stack at this time, i.e. some random data.
If you want to fast initialize an array which does not contain any reference counted types (like string), you can write:
fillchar(working,sizeof(working),0);
And if your array contains managed types, you can write:
finalize(workingWithStringInside); // to safely release internal managed types
fillchar(workingWithStringInside,sizeof(workingWithStringInside),0);
This is the faster code possible (faster than a variable copy), and sounds a better option.
This is absolutely fine. The semantics of the code are exactly what you need. Certainly the Delphi compiler will emit code to perform a simple and efficient memory copy. The compiler is able to do that because you have a fixed length array whose elements are simple value types. I'd be surprised if FPC did not produce very similar code.
Even if your array contained managed types (it doesn't), the assignment operator would result in code that respected those managed types.
As a final comment, the array full of zeros should be a constant.
An easy way is not to set your length of the variable in the type...and use SetLength to initialize the array for you... from Delphi help: When S is a dynamic array of types that must be initialized, newly allocated space is set to 0 or nil.
type
TIntArray = Array of Integer;
procedure WorkArrays(var aWorking: array of integer);
begin
if High(aWorking) >= 0 then
aWorking[0] := 1;
if High(aWorking) >= 3 then
aWorking[3] := 5;
end;
procedure WorkArrays2(var aWorking: array of integer);
begin
if High(aWorking) >= 1 then
aWorking[1] := 4;
if High(aWorking) >= 9 then
aWorking[9] := 7;
end;
procedure WorkArrays3(var aWorking: TIntArray);
begin
SetLength(aWorking, 0);
SetLength(aWorking, 4);
aWorking[0] := 1;
aWorking[3] := 5;
end;
procedure WorkArrays4(var aWorking: TIntArray);
begin
SetLength(aWorking, 0);
SetLength(aWorking, 10);
aWorking[1] := 4;
aWorking[9] := 7;
end;
procedure TForm58.ShowArrays(aWorking: array of integer);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.ShowArrays2(aWorking: TIntArray);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.Button1Click(Sender: TObject);
var
a_MyArray: array of integer;
a_MyArray1: TIntArray;
begin
//SetLength(aWorking, 0);
SetLength(a_MyArray, 3);//note this is a Zero based Array...0 to 2
WorkArrays(a_MyArray);//note aWorking[3] will not show...because High is 2...
ShowArrays(a_MyArray);
SetLength(aWorking, 0);
SetLength(a_MyArray, 10);//note this is a Zero based Array...0 to 9
WorkArrays2(a_MyArray);
ShowArrays(a_MyArray);
WorkArrays3(a_MyArray1);
ShowArrays2(a_MyArray1);
WorkArrays4(a_MyArray1);
ShowArrays2(a_MyArray1);
end;
const
states : array [0..49,0..1] of string =
(
('Alabama','AL'),
('Montana','MT'),
('Alaska','AK'),
('Nebraska','NE'),
('Arizona','AZ'),
('Nevada','NV'),
('Arkansas','AR'),
('New Hampshire','NH'),
('California','CA'),
('New Jersey','NJ'),
('Colorado','CO'),
('New Mexico','NM'),
('Connecticut','CT'),
('New York','NY'),
('Delaware','DE'),
('North Carolina','NC'),
('Florida','FL'),
('North Dakota','ND'),
('Georgia','GA'),
('Ohio','OH'),
('Hawaii','HI'),
('Oklahoma','OK'),
('Idaho','ID'),
('Oregon','OR'),
('Illinois','IL'),
('Pennsylvania','PA'),
('Indiana','IN'),
('Rhode Island','RI'),
('Iowa','IA'),
('South Carolin','SC'),
('Kansas','KS'),
('South Dakota','SD'),
('Kentucky','KY'),
('Tennessee','TN'),
('Louisiana','LA'),
('Texas','TX'),
('Maine','ME'),
('Utah','UT'),
('Maryland','MD'),
('Vermont','VT'),
('Massachusetts','MA'),
('Virginia','VA'),
('Michigan','MI'),
('Washington','WA'),
('Minnesota','MN'),
('West Virginia','WV'),
('Mississippi','MS'),
('Wisconsin','WI'),
('Missouri','MO'),
('Wyoming','WY')
);
function getabb(state:string):string;
var
I:integer;
begin
for I := 0 to length(states) -1 do
if lowercase(state) = lowercase(states[I,0]) then
begin
result:= states[I,1];
end;
end;
function getstate(state:string):string;
var
I:integer;
begin
for I := 0 to length(states) -1 do
if lowercase(state) = lowercase(states[I,1]) then
begin
result:= states[I,0];
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
edit1.Text:=getabb(edit1.Text);
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
edit1.Text:=getstate(edit1.Text);
end;
end.
Is there a bette way to do this?
Should this kind of data be hard coded?
Wouldn't it be better to use something like a XML file or even just a CSV.
Or Name Value Pairs, i.e. IA=Iowa
then loaded into a TStringList to get
States.Values['IA'] = 'Iowa';
Then you just need to write something to search the Values to work backwards like
//***Untested***
//Use: NameOfValue(States, 'Iowa') = 'IA'
function NameOfValue(const strings: TStrings; const Value: string): string;
var
i : integer;
P: Integer;
S: string;
begin
for i := 0 to strings.count - 1 do
begin
S := strings.ValueFromIndex[i];
P := AnsiPos(strings.NameValueSeparator, S);
if (P <> 0) and (AnsiCompareText(Copy(S, 1, P - 1), Value) = 0) then
begin
Result := strings.Names[i];
Exit;
end;
end;
Result := '';
end;
I'm fairly sure its case insensitive too
If you're on D2009 or D2010, use a TDictionary<string, string> from Generics.Collections. Declare the array of constants like you have it, then set up your dictionary by putting each pair in to the dictionary. Then just use the dictionary's default property to do your lookups.
Notice that lowercase(a) = lowercase(b) is slower than sameText(a, b).
In addition, you can speed up the procedure further by storing the strings in the array as lower-case only, and then in the look-up routine start with converting the input to lower-case as well. Then you can use the even faster function sameStr(a, b). But of course, when a match is found, you then need to format it by capitalizing the initial letters. This speed-up approach is probably not very important for such a small list of strings. After all, there are not too many states in the US.
Also, you should declare the functions using const arguments, i.e. write
function getabb(const state:string):string;
instead of
function getabb(state:string):string;
(unless you want to change state in the routine).
Finally, you could make the code more compact and readable by omitting the begin and end of the for loops.
I would have your lists sorted. That way you can use a binary search to cut the lookup times down. It all depends on the number of iterations you will be exercising. Around 50 items doesn't seem like much, until your iterating over the list a few thousand times looking for the last item in the list.
Also you should ALWAYS bail from your loops as soon as you get get a match if you know the rest of the list will not match.
Arrays are fine, and depending on how your using the data, you might need to add some of the "territories" that also have abbreviations (PR = PUERTO RICO, GU = GUAM, etc.).
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..