0

I have this piece of code written in python using pygame to display objects on the screen and then detect collisions and remove the objects when a collision takes place.

When I have the code written like this everytime a collision occurs I get this error:

Code:

    for i in range(len(asteroids)):
        asteroidObj = asteroids[i]
        asteroidObj['rect'] = pg.Rect((asteroidObj['x'],
                                       asteroidObj['y'],
                                       asteroidObj['width'],
                                       asteroidObj['height']))
        screen.blit(asteroidObj['surf'], asteroidObj['rect'])
        asteroidObj['x']+= asteroidObj['xChange']
        asteroidObj['y']+= asteroidObj['yChange']
        if asteroidObj['rect'].colliderect(shipObj['rect']):
            del asteroids[i]

Error:

    asteroidObj = asteroids[i]
    IndexError: list index out of range

However if I change the for loop to be:

for i in range(len(asteroids)-1, -1, -1):

The code works as intended and I no longer get an error.

One of the loops iterates from item 0-49 and the other from 49-0 so I am confused as to why one works and one doesn't. Does anyone know why this might be?

Callum
  • 65
  • 1
  • 3
  • 9
  • Because when you delete item 25, what was at index 26 is now at index 25, also index 49 no longer exists so you'll run off the end of your list at the end. – mgilson Feb 10 '17 at 19:12
  • Because if you iterate over the list in order, when you remove an element, the length of your list changes (gets smaller), and since your indices keep increasing monotonically, you eventually will index out of range. See the answer to [this](http://stackoverflow.com/questions/6260089/strange-result-when-removing-item-from-a-list) question for a nice illustration of what is going on. Iterating over the list in reverse avoids the issue. – juanpa.arrivillaga Feb 10 '17 at 19:13

1 Answers1

2

It only works in reverse because you are removing elements from the list.

For instance, suppose you have 50 asteroids, so your list goes from 0 to 49. Asteroid i==48 collides, and you remove it from the list. Now your list has length 49 (ranging from 0 to 48), but range(len(asteroids)) has already been evaluated, and in the next iteration you'll have i==49, but there is no element 49 anymore. It became the new 48 when you removed previous 48.

Doing it reverse, as you figured, is a way to solve your problem, but I'd recommend avoiding removing elements from the middle of a list because it is a O(n) operation. I would do something like:

new_asteroids = []
for asteroidObj in asteroids:
    # Do your stuff here...

    if not asteroidObj['rect'].colliderect(shipObj['rect']):
        new_asteroids.append(asteroidObj)
asteroids = new_asteroids
lvella
  • 12,754
  • 11
  • 54
  • 106