Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411651 Posts in 69395 Topics- by 58451 Members - Latest Member: Monkey Nuts

May 15, 2024, 11:23:39 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Array splicing in a 'for' loop
Pages: [1]
Print
Author Topic: Array splicing in a 'for' loop  (Read 7111 times)
Craig Stern
Level 10
*****


I'm not actually all that stern.


View Profile WWW
« on: June 02, 2010, 04:00:10 PM »

So, I've got this code which goes through an array and removes entries which don't meet a certain standard:

Code:
function cullBadMoves () {

for (i = 0; i < numberComparisonArray.length; i++) {

if (numberComparisonArray[i][0] < minTargetNum) { //if it doesn't have the minimum target value,
numberComparisonArray.splice(i, 1); //delete the entry
}

etc.

Simple enough, right? The problem is that every time it splices an entry from the array, the elements below it move up, meaning that the next entry gets skipped as 'i' in the for loop increments. Suppose i is 12; numberComparisonArray[12] gets spliced, and the old numberComparisonArray[13] then gets moved up to the 12 position to fill the gap. It isn't ever going to get checked, though, because i then increments to 13, continuing the for loop.

I've tried two methods to fix the skipping problem, and for some reason, both cause Flash to freeze:

1) stick i-- on the line after each numberComparisonArray.splice(i, 1); call
2) count backwards instead, using for (i = numberComparisonArray.length; i >= 0; i--)

Has anyone dealt with this before? Any ideas on how I could resolve this issue in a way that doesn't make Flash commit harakiri?
Logged

John Nesky
Level 10
*****


aka shaktool


View Profile WWW
« Reply #1 on: June 02, 2010, 04:27:21 PM »

Both of your suggested methods sound good. I don't know why they wouldn't work without more info.

This is how I do it:
Code:
for (var i: int = 0; i < array.length;) {
  if (array[i].removeMe) {
    array.splice(i, 1);
  } else {
    i++;
  }
}
Logged
increpare
Guest
« Reply #2 on: June 02, 2010, 04:30:14 PM »

you should start from length-1, not length, when going backwards.

If you're in as3, you can use filter, which constructs a new container given an old one and a predicate on it.  It's tidier than the methods you suggest - not sure about performance though.
Logged
Kunal
Level 1
*


is feeling Bit.Core.Trippy


View Profile WWW
« Reply #3 on: June 02, 2010, 04:31:01 PM »

Not sure why your method isn't working. Might it have something to do with the fact that you are using a multidimensional array ?

Probably a stupid suggestion, but is numComparisonArray going to be small enough for you to create another array (with only the valid elements) and then copy that over ?

Logged

raiten
Guest
« Reply #4 on: June 02, 2010, 04:34:10 PM »

That's weird, I tried copying and pasting your code and slapping on i--, works fine for me. Doesn't this code work for you?

Code:
numberComparisonArray=[[5],[6],[14],[5],[19],[15],[5],[3],[25]]
minTargetNum=12
for (var i = 0; i < numberComparisonArray.length; i++) {
if (numberComparisonArray[i][0] < minTargetNum) { //if it doesn't have the minimum target value,
numberComparisonArray.splice(i, 1); //delete the entry
i--
}
}
Logged
agj
Level 10
*****



View Profile WWW
« Reply #5 on: June 02, 2010, 04:38:59 PM »

Update your Flex SDK (if you're using that)? I had some issues with arrays in older versions.
Logged

st33d
Guest
« Reply #6 on: June 03, 2010, 12:41:40 AM »

I do this all the time:

Code:
/* Maintain FX */
private function updateFX():void{
for(i = 0; i < fx.length; i++){
if(fx[i].active && onScreen(fx[i].x, fx[i].y, this, fx[i].blit.width)) fx[i].main();
else {
fx.splice(i, 1);
i--;
}
}
}

You use i-- after the splice.

If that doesn't work, then "i" is being modified somehow during the for loop. I have many of these self managing lists in my game so I'm pretty confident I've presented a working example.
Logged
Jonathan Whiting
Level 2
**



View Profile WWW
« Reply #7 on: June 03, 2010, 03:28:56 AM »

you should start from length-1, not length, when going backwards.

If you're in as3, you can use filter, which constructs a new container given an old one and a predicate on it.  It's tidier than the methods you suggest - not sure about performance though.

Though I haven't run tests I'd be very surprised if the performance of filter was significantly worse than a more manual method.  It will presumably add numberComparisonArray.length function calls, but that's not a big deal.

I use AS3's filter significantly for say, weeding out 'dead' objects from particle system arrays.  So, I run it every single frame, with larg-ish arrays and without issues.  Sure beats hand-rolling the functionality.
Logged

st33d
Guest
« Reply #8 on: June 03, 2010, 04:51:46 AM »

I was wary of filter because of the overhead Flash incurs on method calls.

Code:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.getTimer;

public class Main extends Sprite {

public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}

private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);



var t:int, i:int, a:Array, j:int;
var callBack:Function = function(item:int, index:int, array:Array):Boolean{return item == 1; };

t = getTimer();
for(i = 0; i < 10000; i++) {
a = [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1];
for(j = 0; j < a.length; j++) {
// run the usual code on each object
}
a = a.filter(callBack);
}
trace(getTimer() - t);

t = getTimer();
for(i = 0; i < 10000; i++) {
a = [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1];
for(j = 0; j < a.length; j++) {
// run the usual code on each object
if(!(a[j])){
a.splice(j, 1);
j--;
}
}
}
trace(getTimer() - t);
}

}

}

gives:

124
269

Seems filter is actually twice as fast as running splice calls on the fly.

Good to know!  Smiley
Logged
increpare
Guest
« Reply #9 on: June 03, 2010, 05:05:22 AM »

That's mighty aesthetically convenient!
Logged
Jonathan Whiting
Level 2
**



View Profile WWW
« Reply #10 on: June 03, 2010, 05:08:51 AM »

Seems filter is actually twice as fast as running splice calls on the fly.

Not all that surprising either.  I suspect filter has been carefully implemented, and splice isn't as work free as it appears.  What that code also shows is that both versions are more than fast enough to imply that the operation is not worth worrying about from an optimization perspective unless you're dealing with ludicrously large arrays.

As always, never assume a custom method will be faster than the generic one, and only optimize if/when you *know* something is slow.

All interesting stuff though Smiley
Logged

st33d
Guest
« Reply #11 on: June 03, 2010, 05:30:46 AM »

I think it's more to do with the array searching routines in Flash being built in to the player, like the BitmapData methods that make copyPixelling fast.

I tried a speed test with array.every() a while ago, and that turned out to be much slower than using a for loop to iterate through all the items.

Testing the above code without any items to prune it turns out that filter ends up being twice as slow as merely iterating through all the array items.

So I would advise in using filter for lots of object creation and destruction and stick to splicing for occasional pruning.
Logged
increpare
Guest
« Reply #12 on: June 03, 2010, 07:05:58 AM »

So I would advise in using filter for lots of object creation and destruction and stick to splicing for occasional pruning.
I would advise doing whatever you feel personally is clearest until you notice otherwise (especially given that the OP is about a bug, not optimisation).
Logged
agj
Level 10
*****



View Profile WWW
« Reply #13 on: June 03, 2010, 09:09:57 AM »

Seems filter is actually twice as fast as running splice calls on the fly.

Good to know!  Smiley

Yeah; I've never even used filter().
Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #14 on: June 03, 2010, 01:59:20 PM »

Filter is not fast. But I've heard, splice is dead slow. For this sort of work, hand implemented linked lists will outperform arrays.
Logged
Craig Stern
Level 10
*****


I'm not actually all that stern.


View Profile WWW
« Reply #15 on: June 03, 2010, 08:14:05 PM »

Thanks for the feedback, guys.
Logged

raigan
Level 5
*****


View Profile
« Reply #16 on: June 03, 2010, 08:29:15 PM »

Personally, my rule of thumb is "never modify a list while iterating over it"; rather than pruning objects from a list, it's much easier to e.g build a second list by iterating over the input list and skipping the objects you want to "remove", copying the rest.



Logged
bateleur
Level 10
*****



View Profile
« Reply #17 on: June 04, 2010, 02:40:53 AM »

Filter is not fast. But I've heard, splice is dead slow. For this sort of work, hand implemented linked lists will outperform arrays.

Further to which: at the point at which your application's performance depends critically on a list it is often correct to use a tree instead. What kind of data structure is best will depend a lot on the kind of access you need to your elements and how often you insert/remove relative to how often you seek.
Logged

Craig Stern
Level 10
*****


I'm not actually all that stern.


View Profile WWW
« Reply #18 on: June 06, 2010, 09:33:36 AM »

Probably a stupid suggestion, but is numComparisonArray going to be small enough for you to create another array (with only the valid elements) and then copy that over ?

Not stupid at all--this worked wonderfully. Thanks! Smiley

Code:
culledNumberArray = new Array ();

for (var i = 0; i < numberComparisonArray.length; i++) {

if (numberComparisonArray[i][0] >= minTargetNum) { //if it meets the minimum target value,
culledNumberArray.push(numberComparisonArray[i]);
}
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic