I have a TStack<string> and I need loop all items searching for a value without remove the item of the stack. I have Pop and Extract methods but they remove the items from stack.
How could I loop all items of the stack and check for a specific value without remove it?
procedure test;
var
i: integer;
begin
Stack := TStack<String>.Create;
{ Push some items to the stack. }
Stack.Push('John');
Stack.Push('Mary');
Stack.Push('Bob');
Stack.Push('Anna');
Stack.Push('Erica');
for i:=0 to Stack.Count -1 do begin
item := Stack.Peek; --->How to do it
if item = 'Bob' then dosomething; ----->Here is the problem
end;
end;
If you find that some of your code has to iterate over a stack, maybe the TStack<T> doesn't represent the best choiche for that kind of data.
In any case, the underlying elements can be obtained as a TArray<T>.
Declare a new variable:
var
arr: TArray<string>;
Use the ToArray method of the TStack<T> object.
arr := Stack.ToArray;
Iterate over the arr array.
for i := Low(arr) to High(arr) do
item := arr[i];
You can also iterate directly with the for..in construct as pointed out in a comment since TStack<T> implements TEnumerable<T>.
for item in Stack do
. . .
Related
What is best coding practise in deleting all elements from a list except one element which I would like to keep inside the list?
TMyCLass = Class()
....
end;
MyObjectList = TObjectList <TMyClass>;
var MySaveClass : TMyCLass;
begin
MySaveClass = MyObjectList.items[saveindex];
for i = 1... MyObjectList.count-1 do
if i <> saveindex then
MyObjectList.delete (i); ?? // not working :-)
end;
As TLama said in a comment, Extract the item, Clear the list (if OwnsObjects is false, loop through and free each item first), and then Add the item back in.
var
SavedItem: TMyClass;
...
SavedItem := MyObjectList.Extract(MyObjectList.Items[i]);
// Loop here to free if needed because list doesn't own objects
MyObjectList.Clear;
MyObjectList.Add(SavedItem);
If the Objectlist has OwnsObjects := True then you can just delete the elements.
But care to make it backwards otherwise you might get an error.
for i := MyObjectlist.count -1 downto 0 do
if i = Saveindex then
continue
else
MyObjectList.Delete(i);
I have made a piece of code that works in simple linked list node in pascal. Now I need add a procedure that can delete or remove an element in my simple linked list. I have made many procedures that do test and adding but I can't figure out how to add a procedure that can remove a item. Here is my code:
type
node = ^MyRec;
MyRec = record
value: Integer;
index: integer;
next: node
end;
var
head, tail: node;
i, ix: Integer;
const
el = 14;
procedure Insert;
var
tmp: node;
ix: Integer;
begin
Randomize;
for i := 1 to el do
begin
new (tmp);
inc(ix);
tmp^.value := Random(88);
tmp^.index := ix;
tmp^.next:= nil;
if head = nil
then head := tmp
else
tail^.next := tmp;
tail:= tmp;
end;
end;
//displays the list
procedure Display;
var
tmp: node;
begin
tmp := head;
while tmp <> nil do
begin
WriteLn('Val=', tmp^.value, ' ID=', tmp^.index);
tmp := tmp^.next
end;
end;
begin
head := nil;
ReadLn;
end.
I'm going to give you some information and pseudocode so you will be able to fix this problem yourself.
You can see a linked list as a chain of items:
Head -> Item1 -> Item2 -> Item3 -> Item4 <- Tail
If you need to delete Item3, you need to change the next pointer of Item2 so it points to Item4 (The item following Item3). Then you can release Item3.
But there are some special cases:
If you want to delete Item1 (the first item), you need to change the Head pointer, so it points to Item2.
If you want to delete Item4 (the last item), you need to change the Tail pointer, so it points to Item3.
If the list contains only one item and you want to delete that, you need to change both the Head and the Tail to nil.
If the list is empty, you don't need to delete anything, but your code should not crash because of that.
So in order to fix all these, you can use 2 pointers to walk the list, (current and previous).
Current start at the first item (Head) and previous start at nil.
As long as current is not nil and it does not point to the item to be deleted, set previous to the value of current and set current to the value of the next item.
If current is nil, then the item is not found and there is nothing to delete.
If current points to the correct item, and previous is nil, you need to change the head pointer.
If current points to the correct item and previous is not nil, set the next pointer of previous to the next pointer of current.
If the next pointer of current is nil (you are at the tail), you also set the tail pointer to previous.
If all pointers are changed, you can dispose of the current item.
A side note on your code. You have an insert procedure that inserts several random elements. A better idea is to have a separate procedure to insert a single element and a separate procedure to add several items like:
procedure Insert(index, value: Integer);
var
tmp: node;
begin
new (tmp);
tmp^.value := value;
tmp^.index := index;
tmp^.next:= nil;
if head = nil
then head := tmp
else
tail^.next := tmp;
tail:= tmp;
end;
procedure FillList;
var
ix: Integer;
begin
Randomize;
for i := 1 to el do
begin
inc(ix);
Insert(ix, Random(88));
end;
end;
I have an ObjectList that is populated. Then details changed in the objects. Now I need to free the ObjectList but when I do it also frees the objects in the list. How can I free this list without freeing the objects themselfs?
Example code:
{Gets starting cards and put them into the correct rows}
//***************************************************************************
procedure TFGame.GetStartingCards;
//***************************************************************************
const
ManaTypes : array [0..3] of string = ('Lava','Water','Dark','Nature');
var
i: integer;
z:integer;
Cards: TObjectList<Tcard>;
begin
Cards := TObjectList<TCard>.Create;
z:=0;
{add all tcards (Desgin ) to this list in order Lava,water,dark,nature }
cards.Add(cardLava1);
cards.Add(cardlava2);
cards.Add(cardlava3);
cards.Add(cardlava4);
cards.Add(cardwater1);
cards.Add(cardwater2);
cards.Add(cardwater3);
cards.Add(cardwater4);
cards.Add(carddark1);
cards.Add(carddark2);
cards.Add(carddark3);
cards.Add(carddark4);
cards.Add(cardnature1);
cards.Add(cardnature2);
cards.Add(cardnature3);
cards.Add(cardnature4);
//get data from DB
for i := 0 to Length(ManaTypes) - 1 do
begin
with adoquery1 do
begin
close;
sql.Clear;
sql.Add('SELECT TOP 4 * FROM Cards WHERE Color = "'+ManaTypes[i]+'" ORDER BY Rnd(-(1000*ID)*Time())');
open;
end;
{return the result of everything for giving mana type.. }
if adoquery1.RecordCount = 0 then
Showmessage('Error no cards in db');
adoquery1.First;
while not adoquery1.Eof do
begin
cards[z].Cname := adoquery1.FieldByName('Name').AsString;
cards[z].Ccost := adoquery1.Fieldbyname('Cost').AsInteger;
cards[z].Ctext := adoquery1.FieldByName('Text').AsString;
cards[z].Ccolor := adoquery1.FieldByName('Color').AsString;
cards[z].Cinplay := false; //in the play area
if adoquery1.fieldbyname('Power').asstring <> '' then
cards[z].Cpower := adoquery1.FieldByName('Power').AsInteger;
if adoquery1.fieldbyname('Def').asstring <> '' then
cards[z].Cdef := adoquery1.FieldByName('Def').AsInteger;
if adoquery1.FieldByName('Type').AsString = 'Spell' then
cards[z].Cspell := true
else
cards[z].Cspell := false;
if adoquery1.FieldByName('Target').AsString = 'yes' then
cards[z].SetTargetTrue
else
cards[z].settargetfalse;
//based on color change background
cards[z].Background.LoadFromFile(format('%s\pics\%s.png',[maindir,cards[z].Ccolor]));
adoquery1.Next;
cards[z].repaint;
z:=z+1;
end;
end;
//cards.Free; if i free this it removes all the cards added to it..
end;
The constructor for TObjectList<T> receives a parameter named AOwnsObjects which specifies who is to be responsible for freeing the objects. If you pass True then the list has that responsibility. Otherwise, the responsibility remains with you.
constructor Create(AOwnsObjects: Boolean = True); overload;
The parameter has a default value of True and I presume that you are calling the constructor without specifying that parameter. Hence you get the default behaviour – the list owns its members.
The documentation says it like this:
The AOwnsObjects parameter is a boolean that indicates whether object entries are owned by the list. If the object is owned, when the entry is removed from the list, the object is freed. The OwnsObjects property is set from the value of this parameter. The default is true.
Now, if you want this particular list instance not to free its members, then there is little to be gained from using TObjectList<T>. You may as well use plain old TList<T>.
Does iterating over a dynamic array using for ... in ... do create a copy of the item in the array? For example:
type
TSomeRecord =record
SomeField1 :string;
SomeField2 :string;
end;
var
list: array of TSomeRecord;
item: TSomeRecord;
begin
// Fill array here
for item in list do
begin
// Is item here a copy of the item in the array or a reference to it?
end;
end;
Will item in the loop be a copy of the item in the array or a reference to it?
If it is a copy is it possible to iterate over the array without a copy being created?
Thanks,
AJ
The loop variable of a for/in loop is a copy of the value held by the container over which the loop is iterating.
Since you cannot replace the default enumerator for dynamic arrays, there is no way for you to create an enumerator that returns references rather than copies. If you were to wrap up the array inside a record, you could create an enumerator for the record that would return references.
Obviously you can use a traditional indexed for loop if you wish to avoid making copies.
One might ask, since there appears to be no documentation of the above statement, why the compiler does not choose to implement such for/in loops using references rather than copies. Only the designers can answer that definitely, but I can offer a justification.
Consider a custom enumerator. The documentation describes the mechanism as follows:
To use the for-in loop construct on a class or interface, the
class or interface must implement a prescribed collection pattern. A
type that implements the collection pattern must have the following
attributes:
The class or interface must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class,
interface, or record type.
The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext()
method must return a Boolean. The for-in loop calls this method
first to ensure that the container is not empty.
The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The
type of the Current property must be the type contained in the
collection.
The custom enumerator returns each value from the collection via the Current property. And that implies that the value is copied.
So, this means that custom enumerators always use copies. Imagine if the built-in enumerator for arrays could use references. That would result in a significant semantic difference between the two types of enumerators. It's surely plausible that the designers opted for consistency between the semantics of difference types of enumerators
#David answered that the enumerator is a copy of the record.
He also said that wrapping a dynamic array inside a record would allow for a custom enumerator.
Here is an example of doing that:
program ProjectCustomEnumerator;
{$APPTYPE CONSOLE}
type
PSomeRecord = ^TSomeRecord;
TSomeRecord = record
SomeField1 :string;
SomeField2 :string;
end;
TSomeRecordArray = record
private type
TSomeRecordDynArray = array of TSomeRecord;
// For x in .. enumerator
TSomeRecordArrayEnumerator = record
procedure Create( const AnArray : TSomeRecordDynArray);
private
FCurrent,FLast : Integer;
FArray : TSomeRecordDynArray;
function GetCurrent : PSomeRecord; inline;
public
function MoveNext : Boolean; inline;
property Current : PSomeRecord read GetCurrent;
end;
public
List : TSomeRecordDynArray;
// Enumerator interface
function GetEnumerator : TSomeRecordArrayEnumerator; inline;
end;
procedure TSomeRecordArray.TSomeRecordArrayEnumerator.Create(
const AnArray: TSomeRecordDynArray);
begin
FCurrent := -1;
FLast := Length(AnArray)-1;
FArray := AnArray;
end;
function TSomeRecordArray.TSomeRecordArrayEnumerator.GetCurrent: PSomeRecord;
begin
Result := #FArray[FCurrent];
end;
function TSomeRecordArray.TSomeRecordArrayEnumerator.MoveNext: Boolean;
begin
Inc(FCurrent);
Result := (FCurrent <= FLast);
end;
function TSomeRecordArray.GetEnumerator: TSomeRecordArrayEnumerator;
begin
Result.Create(Self.List);
end;
var
aList : TSomeRecordArray;
item : PSomeRecord;
i : Integer;
begin
// Fill array here
SetLength(aList.List,2);
aList.List[0].SomeField1 := 'Ix=0; Field1';
aList.List[0].SomeField2 := 'Ix=0; Field2';
aList.List[1].SomeField1 := 'Ix=1; Field1';
aList.List[1].SomeField2 := 'Ix=1; Field2';
i := -1;
for item in aList do
begin
// Item here a pointer to the item in the array
Inc(i);
WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
end;
ReadLn;
end.
Edit
Just to be complete and following up comments, here is a generic container example for any dynamic array of record, with a custom enumerator.
program ProjectCustomEnumerator;
{$APPTYPE CONSOLE}
type
PSomeRecord = ^TSomeRecord;
TSomeRecord = record
SomeField1 :string;
SomeField2 :string;
end;
TRecordArray<T> = record
private type
TRecordDynArray = array of T;
// For x in .. enumerator
TRecordArrayEnumerator = record
procedure Initialize( const AnArray : TRecordDynArray);
private
FCurrent,FLast : Integer;
FArray : TRecordDynArray;
function GetCurrent : Pointer; inline;
public
function MoveNext : Boolean; inline;
property Current : Pointer read GetCurrent;
end;
public
List : TRecordDynArray;
// Enumerator interface
function GetEnumerator : TRecordArrayEnumerator; inline;
end;
procedure TRecordArray<T>.TRecordArrayEnumerator.Initialize(
const AnArray: TRecordDynArray);
begin
FCurrent := -1;
FLast := Length(AnArray)-1;
FArray := AnArray;
end;
function TRecordArray<T>.TRecordArrayEnumerator.GetCurrent: Pointer;
begin
Result := #FArray[FCurrent];
end;
function TRecordArray<T>.TRecordArrayEnumerator.MoveNext: Boolean;
begin
Inc(FCurrent);
Result := (FCurrent <= FLast);
end;
function TRecordArray<T>.GetEnumerator: TRecordArrayEnumerator;
begin
Result.Initialize(Self.List);
end;
var
aList : TRecordArray<TSomeRecord>;
item : PSomeRecord;
i : Integer;
begin
// Fill array here
SetLength(aList.List,2);
aList.List[0].SomeField1 := 'Ix=0; Field1';
aList.List[0].SomeField2 := 'Ix=0; Field2';
aList.List[1].SomeField1 := 'Ix=1; Field1';
aList.List[1].SomeField2 := 'Ix=1; Field2';
i := -1;
for item in aList do
begin
// Item here a pointer to the item in the array
Inc(i);
WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
end;
ReadLn;
end.
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;