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);
Related
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
. . .
i have some list inside a list view i want to prevent Adding items that already exist and allow only items that's not exist i search about that before i post my question i find some codes that removes the duplicated items but thats not my point , a little example of what aim trying to achieve , example
listview1.Items.Add.caption := 'item1'
listview1.Items.Add.subitems.add:= 'content'
listview1.Items.Add.caption := 'item2'
listview1.Items.Add.subitems.add:= 'content2'
listview1.Items.Add.caption := 'item3'
listview1.Items.Add.subitems.add:= 'content3'
//duplicated line
listview1.Items.Add.caption := 'item1'// here what i want to ignore if exist and add any other items comes below
listview1.Items.Add.subitems.add:= 'content'
listview1.Items.Add.caption := 'item4'
listview1.Items.Add.subitems.add:= 'content4'
any idea on how to achieve that ignore exist items and add what ever other items ?
Current code
if Command = 'CallThis' then
begin
if Assigned(MS) then
begin
SL := TStringList.Create;
try
SL.LoadFromStream(MS);
for I := 0 to SL.Count -1 do
begin
Line := SL.Strings[I];
ExplodeLine(Line, item, content, num);
with vieform.list.Items.Add do
begin
Caption := StripHTML(item);
Subitems.Add(content);
Subitems.Add(num)
end;
end;
finally
SL.Free;
end;
MS.Free;
end;
end;
You should not use the visual controls to store and manage your data. Have a list for all the data and present the data in the listview or any other control you like.
// class to store data (shortend)
TMyData = class
constructor Create( const Item, Content : string );
property Item : string;
property Content : string;
end;
// list to organize the data
MyList := TObjectList<TMyData>.Create(
// comparer, tell the list, when are items equal
TComparer<TMyData>.Construct(
function ( const L, R : TMyData ) : integer
begin
Result := CompareStr( L.Item, R.Item );
end ) );
// create an item
MyData := TMyData.Create( 'item1', 'content1' );
// check for duplicate in list
if not MyList.Contains( MyData ) then
MyList.Add( MyData )
else
MyData.Free;
// present the list in a ListView
ListView1.Clear;
for MyData in MyList do
begin
ListItem := ListView1.Items.Add;
ListItem.Data := MyData; // store a reference to the data item
ListItem.Caption := MyData.Item;
ListItem.SubItems.Add( MyData.Content );
end;
Thats all
Just write your own procedure which does all the work for you. Also helps with your sub items, except I'm not sure what you were trying to do in your code (This is what I'm assuming you were attempting to do)...
procedure TForm1.Add(const Caption, Sub: String);
var
I: TListItem;
X: Integer;
begin
for X := 0 to ListView1.Items.Count-1 do
if SameText(ListView1.Items[X].Caption, Caption) then Exit;
I:= ListView1.Items.Add;
I.Caption:= Caption;
I.SubItems.Add(Sub);
end;
Then, you simply call it like this:
Add('Item1', 'Content');
Add('Item2', 'Content2');
Add('Item3', 'Content3');
Add('Item1', 'Content1');
That would result in 3 items in the list, because the 4th already exists.
Please note however that this may not actually solve your real underlying issue. If you feel the need to perform this check, then it's probably a good time to re-think your design. The approach you're using makes me believe you're using the TListView to store data. UI controls should never be the container of actual data, it should only provide the interface to the user.
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>.
I was wondering how you get a position of a certain object in a list that is created.
Lets say it is like a graphical list where you can click on objects.
Lets say you right click on a object and click "Refresh", how do I get the position of that object so that after the whole list is refreshed (refreshes with a clearlist for some reason) i go back to the same position in the list? This is if the list is say 1000 objects long which makes it bothersome to try and scroll down to the same position after the refresh.
The code uses Tobject but can i do something like
position:=integer(TObject."pointerinfo???");
And after that when the program refreshes
like set the position of the view to the pointer
like currentview(pointer) or something like that?
Actually it doesn't have to be the same object, but the same "view" of the list would be even better.
Thanks in advance
If refreshing is going to give you the same list again, and in the same order, then don't bother discovering anything about the object. Just store the list control's ItemIndex property, which indicates the currently selected item.
If refreshing might give you a different list, then the object you want might not be at the same position afterward, so just remembering ItemIndex won't be enough. In that case, you'll need to find the new position of the object. How to do that depends on the capabilities of the list control and how it exposes the objects associated with each position. If you have a TListBox, for example, then the Items property is a TStrings object, and you can inspect each value of the Objects array until you find the object you want. Store the value of the object reference, and then search for that. Something like this:
procedure Refresh;
var
CurrentSelection: TObject;
ObjectPosition: Integer;
i: Integer;
begin
if List.ItemIndex >= 0 then
CurrentSelection := List.Strings.Objects[List.ItemIndex]
else
CurrentSelection = nil;
RefreshList;
ObjectPosition := -1;
if Assigned(CurrentSelection) then begin
for i := 0 to Pred(List.Count) do begin
if List.Strings.Objects[i] = CurrentSelection then begin
ObjectPosition := i;
break;
end;
end;
end;
if ObjectPosition = -1 then
// Object isn't in refreshed list
else
// It is.
end;
A final possibility is that refreshing doesn't actually keep the same objects at all. All the previous objects are destroyed, and a new list of objects is generated. In that case, you'll have to remember certain identifying characteristics of the original object so you can find the new object that represents the same thing. Something like this:
var
CurrentObject, Person: TPerson;
CurrentName: string;
i, ObjectPosition: Integer;
begin
if List.ItemIndex >= 0 then begin
CurrentObject := List.Strings.Objects[List.ItemIndex] as TPerson;
CurrentName := CurrentObject.Name;
end else
CurrentName = '';
RefreshList;
ObjectPosition := -1;
if CurrentName <> '' then begin
for i := 0 to Pred(List.Count) do begin
Person := List.Strings.Objects[i] as TPerson;
if Person.Name = CurrentName then begin
ObjectPosition := i;
break;
end;
end;
end;
if ObjectPosition = -1 then
// Object isn't in refreshed list
else
// It is.
end;
In all these cases, you should realize that the object's position in the list is not actually a property of the object. Rather, it's a property of the list, which is why the list plays such a bigger role than the object in all that code. There isn't really any "pointerinfo" to get from the object because the object, in general, has no idea it's even in a list, so it certainly won't know its position relative to all the other items in the list.
Once you've determined the position of the object in the list, you need to scroll it into view. For TListBox, a simple way is to set its TopIndex property:
List.TopIndex := ObjectPosition;
And finally, if you don't actually care about the current object at all, but just want to restore the current scroll position, then that's even easier:
procedure Refresh;
var
CurrentPosition: Integer;
begin
CurrentPosition := List.TopIndex;
RefreshList;
List.TopIndex := CurrentPosition;
end;
So I think with the help I got I answered my own question.
What I did was write something that took the x and y position of the listview and later after I did the refresh with a clearlist, I used the scroll function to get back to the same function. My program looks something like this.
procedure Refresh(Sender: TObject);
var
horzpos:integer;
vertpos:integer;
begin
horzpos:=ListView1.ViewOrigin.X;
vertpos:=ListView1.ViewOrigin.Y;
RefreshListView(ListView1); //A function that refreshes the list and clears it
ListView1.Scroll(horzpos, vertpos);
end;
Maybe I should've stated earlier that it was a listview type and that I wanted to get back to the same position again after the "clearlist".
Thanks for all the help, this community rocks!