Issues with removing an object

Hi!
I tried making a program, which makes use of the simplified for loop structure (for(p obj : pro){}) Inside of it, I made a condition stating that if the object obj is outside the screen or has age higher than maxAge, it is erased. However when I tried using:

for(p obj : pro) { 
//updating & drawing the object
if(boolean) pro.remove(this) }

it did nothing. And when I replaced “this” with “obj” it spat out an error.

How do I do this without using the standard for loop for(int i = 0; i < pro.size(); i++) ?

Code
ArrayList<p> pro = new ArrayList<p>();
float dir = 0, n = 7, defaultSpeed =5, angularAcceleration = 0.0001, maxAge = 5;
void setup() { size(600,600); colorMode(HSB); }
void draw() {
  background(0);
  if(frameCount % 15 == 0) for(float i = 0, a = TWO_PI/n; i < n; i++) pro.add(new p(300,300,defaultSpeed,0,angularAcceleration,dir+a*i,floor(i),maxAge));
  for(p obj : pro) {
    obj.update();
    obj.display();
    if(obj.age > obj.mA) println("I SHOULD BE DEAD");
    if(obj.x > width*1.1 || obj.x < -width*0.1 || obj.y > height*1.1 || obj.y < -height*0.1 || obj.age > obj.mA) pro.remove(obj);
  }
  println(pro.size());
}
class  p {
  float x,y,mS,aS,aAcc,rot,src=0,age=0,mA=0;
  p(float x_, float y_, float mS_, float aS_, float aAcc_, float rot_, int source, float maxAge_) {
    x = x_; y = y_; mS = mS_; aS = aS_; aAcc = aAcc_; rot = rot_; src = source; mA = maxAge_;
  }
  void update() {
    age++;
    aS+=aAcc;
    rot+=aS;
    x += cos(rot)*mS;
    y += sin(rot)*mS;
  }
  void display() {
    fill(map(src,0,n,0,255),255,255); noStroke();
    circle(x,y,5);
  }
}

First off, why should you use obj instead of this:

The keyword this always refers to the object that is executing the current piece of code. For example:

class MyClass {

    int x = 0;
    PVector vector = new PVector();

    MyClass() {
        // Access the variable 'x' of the PVector object
        println(vector.x);

        // Access the variable 'x' of this MyClass object.
        // Most of the time, you don't need to use this.
        println(this.x);
    }

}

In your context it refers to the sketch itself, not the current object of the loop. That’s why you need to use obj.

The reason you can’t use a for-each loop in this case is because you are removing objects of the list while simultaneously trying to iterate over it. It just doesn’t work like that. :stuck_out_tongue_winking_eye:

Instead you need to iterate over the list backwards:

for (int i = pro.size() - 1; i >= 0; i--) {     
    p obj = pro.get(i);

    if (...)
        // pro.remove(obj); <-- Slow
        pro.remove(i); // <-- Faster
}

This way you make sure you don’t miss any elements because of their indices changing due to the removal of a preceding object.

3 Likes

It is not actually neccessary. I just used a regular old for loop and works just fine for my purpose

for(int i = 0; i < pro.size(); i++) {
  if(boolean) remove(i);
}

since everytime you erase an object it will shorten the list by 1.

I didn’t notice any imperfections in my program so far : D

But thank you for explaining all of it to me

That might very well be true, as the problem is probably very rare for your use case (it is highly unlikely that two objects that are next to each other in the list need to be removed the same frame), but it can still occur.

Take a look at and run the following piece of code:

void setup() {
  // Create a new list
  ArrayList<String> list = new ArrayList<String>();

  // Fill it with some example elements
  for (int i = 0; i < 10; i++)
    list.add(String.valueOf(i)); // String.valueOf turns an integer into a string

  // Copy and loop over it forwards while removing
  ArrayList<String> forwards = new ArrayList<String>(list);
  for (int i = 0; i < forwards.size(); i++) {
    println(forwards.get(i));
    forwards.remove(i);
  }

  println();

  // Copy and loop over it backwards while removing
  ArrayList<String> backwards = new ArrayList<String>(list);
  for (int i = backwards.size() - 1; i >= 0; i--) {
    println(backwards.get(i));
    backwards.remove(i);
  }
}

As you can see, if you iterate over the list forwards and remove an object, the next one will be skipped. The same applies to your loop, it just happens very rarely.

3 Likes

As @Schred said, it is actually necessary.

Imagine you have the following list:

0: 0
1: 0
2: 1
3: 1
4: 2
5: 2

You want to remove from that array every value that is equal to 1.
If you loop forward, your code will be something like this:

for(int i = 0; i < array.size(); i++) {
  if (array.get(i) = 1) {
    array.remove(i)
  }
}

So let’s run this by hand to see what it does.

First loop:
  i = 0 ; Array(0) = 0  ==>  Nothing happen

Second loop:
  i = 1 : Array(1) = 0  ==>  Nothing happen

Third loop:
  i = 2 : Array(2) = 1  ==> We remove the third element of the list. 
  Now the list look like this:
  0: 0
  1: 0
  2: 1
  3: 2
  4: 2

  There is only 5 elements on list now

Fourth loop:
  i = 3 : Array(3) = 2 ==> Nothing happen

Fifth loop:
  i = 4 : Array(4) = 2 ==> Nothing happen

As you can see, we did get rid of one of the 1 but there is still one remaining.
The key moment to analyse is when we erase the element, it moves all the next elements by one. So the second 1 at position 3 is in position 2 after erasing the first 1. But since we already did the case i = 2, the next turn i = 3 so we miss the second 1.

3 Likes