What way do you use to remove items from list when using for n = 0 to last

Discuss any general programming issues here
Post Reply
Bugala
Posts: 1390
Joined: Sun Feb 14, 2010 7:11 pm

What way do you use to remove items from list when using for n = 0 to last

Post by Bugala »

A typical problem I am facing in my coding is something similar to these two examples (same thing twice, but with foreach and for-next both shown):

Code: Select all

list = {0, 1, 2, 3, 4, 5}

DebugPrint("ForEach")
ForEach(list, Function(ID, value)
	DebugPrint(ID..": "..value)
	If value=3 Then RemoveItem(list, 3)
		EndFunction)

list = {0, 1, 2, 3, 4, 5}

DebugPrint("For-Next")
For n = 0 To TableItems(list)-1
	DebugPrint(n..": "..list[n])
	If list[n] = 3 Then RemoveItem(list, n)
Next
Notice that For-Next version has additional problem, as when item is removed in middle of for-next loop, then when going to the last n value, the list[n] doesnt exist anymore.

Basically I solve this problem by using RemoveList, wich is executed afterwards:

Code: Select all

list = {0, 1, 2, 3, 4, 5}

DebugPrint("For-Next")
Local ToBeRemoved = {}
For n = 0 To TableItems(list)-1
	DebugPrint(n..": "..list[n])
	If list[n] = 1 Then InsertItem(ToBeRemoved, n)
	If list[n] = 3 Then InsertItem(ToBeRemoved, n)
Next

For n=TableItems(ToBeRemoved)-1 To 0 Step-1
	RemoveItem(List, ToBeRemoved[n])
Next

DebugPrint("After Removal")
For n=0 To TableItems(list)-1
	DebugPrint(n..": "..list[n])
Next
Do notice the step -1 in the removal for-next loop, point being that as I am adding the RemovedItems in order, they need to be executed in opposite order, as from last to first, since if I remove the first Item first, then the second Item will change its location, and I would be removing a wrong Item.


This, however, isnt perfect yet.

First of all, quite often I am in situation where the table has Functions to execute inside them.

Problem is that, first of all, it is this executable which will decide if item is to be removed or not, and another problem is that only solution that I have been able to think is one where the executin happens after the loop which checks them, while it would be much handier to do the execution inside the loop.

To demonstrate it a bit:

Code: Select all

list = { {execute=True, Func=Func1}, {Execute=False, Func=Func2}, {Execute=False, Func=Func3}, {Execute=True, Func=Func4}, {Execute=False, Func=Func5}

For n = 0 To TableItems(list)-1
	If list[n].Execute = True Then List[n].Func()
Next
This would be the ideal way to handle it, right when it sees in the list that it is to be executed, it would execute it.

However, since that execution might result in a removal of the item (or even addition of another), it cant be done there in the middle, but I have to postpone the execution to a later point:

Code: Select all

list = { {execute=True, Func=Func1}, {Execute=False, Func=Func2}, {Execute=False, Func=Func3}, {Execute=True, Func=Func4}, {Execute=False, Func=Func5}

Local ToBeExecuted = {}
For n = 0 To TableItems(list)-1
	If list[n].Execute = True Then InsertItem(ToBeExecuted, list[n].func)
Next

For n = 0 To TableItems(ToBeExecuted)
	ToBeExecuted[n]()
Next
But now if this executable results in destroying the item, I need to have some system to know which item to destroy from the list.

So far only way I have been able to figure out, is to use UniqueID value for each item, as in instead of:
{Execute=True, Func=Func1}, I will have {Execute=True, Func=Func1, UniqueID=GetUniqueID()}

because this way, if after execution it is decided that an item needs to be destroyed, I can add the UniqueID to the list, and using this I can find the right Item from the list to be destroyed, even if that Items location has changed, either due to removal of another item, or more items added to the list as a result of the execute func.

Naturally this removal needs to be done outside the for-next loop, or it might result in error when n would become higher than the amount of items left on the list.

Here is a working example:

Code: Select all

UniqueID = 0
Function GetUniqueID()
	UniqueID = UniqueID + 1
	Return("Unique"..UniqueID)
EndFunction

Function MyRemoveItem(UniqueID, list)
	Local IDToRemove = -1
	For Local n = 0 To TableItems(list)-1
		If UniqueID = list[n].UniqueID Then IDToRemove = n
	Next
	If IDToRemove > -1 Then RemoveItem(List, IDToremove)
EndFunction

Function FuncTrue()
	Return(True)
EndFunction

Function FuncFalse()
	Return(False)
EndFunction

Func1 = FuncFalse
Func2 = FuncTrue
Func3 = FuncTrue
Func4 = FuncTrue
Func5 = FuncFalse

list = { {UniqueID=GetUniqueID(), execute=True, Func=Func1}, {UniqueID=GetUniqueID(), Execute=True, Func=Func2}, {UniqueID=GetUniqueID(), Execute=False, Func=Func3}, {UniqueID=GetUniqueID(), Execute=True, Func=Func4}, {UniqueID=GetUniqueID(), Execute=False, Func=Func5} }

DebugPrint("list")
For Local n = 0 To TableItems(list)-1
	DebugPrint(n.." - UniqueID:"..list[n].UniqueID)
Next

Local ToBeExecuted = {}
For Local n = 0 To TableItems(list)-1
	If list[n].Execute = True Then InsertItem(ToBeExecuted, CopyTable(list[n]))
Next


For Local n = 0 To TableItems(ToBeExecuted)-1
	Local Remove = ToBeExecuted[n].func()
	If Remove=True Then MyRemoveItem(ToBeExecuted[n].UniqueID, list)
Next

DebugPrint("list after execution")
For Local n = 0 To TableItems(list)-1
	DebugPrint(n.." - UniqueID:"..list[n].UniqueID)
Next
What I would like to do, is practically following:

Code: Select all

For n=0 to tableitems(list)-1
if list.execute=true
	local Remove=list.func()
	if Remove = True then removeItem(list, n)
endif
next
As in whole thing would be handled during one for-next (of foreach) loop, but I dont think that is doable.

What ways have you others used to handle this kind of situation, or is my current way of using UniqueID variable for correct identification at removal, and doing both execution and removal outside the for-loop the only way to do it?
User avatar
Allanon
Posts: 742
Joined: Sun Feb 14, 2010 7:53 pm
Location: Italy
Contact:

Re: What way do you use to remove items from list when using for n = 0 to last

Post by Allanon »

Hello @Bugala,
for the problem you are facing, with remove & add teable items in the middle of a for-next, I use your method, with a reversed for-next like you did, especially when removing items.
But there are also optimized lists that could be easier to use in such cases, have you tried to have a look at CreateList() ?
I've not yet tried it but I think it's the way to go in this scenario :)
----------------------------
[Allanon] Fabio Falcucci | GitHub (leaving) | Gitea (my new house) | My Patreon page | All my links
Bugala
Posts: 1390
Joined: Sun Feb 14, 2010 7:11 pm

Re: What way do you use to remove items from list when using for n = 0 to last

Post by Bugala »

Definitely use the CreateList() option when possible. I myself overlooked that one too until this year, but when I had to optimize my game to run faster, using CreateList() instead of just own table, it made a huge speed difference, especially when list is big.

However, I also noticed that it seems that Hollywood becomes quite slow quite fast actually when using lists, as in, it gets exponentially slower, more items there are. I am actually thinking that next time I have a big list to go through (depending of course what it does) I might limit entries to 1000 only, as in, if I need 2000 entries, then I use two lists instead of one.

But that of course only when speed is an issue, like in case you have 1000 characters moving in a game. If you are just using it keeping records as example, then it doesnt really matter even if 100 thousand happens slightly slower, since you wouldnt notice one time pull anyway, or only once in a while needing to go through the list might not be much of a performance problem.

It is different if you need to go through the list every cycle, then it might be good to limit it to something like 1000 only (just a guess at this point, not properly tested).
Post Reply