Search code examples
rubyloopsdestroylibgosu

Ruby: deleting object while looping over list with that object


So I have multiple lists to keep track of objects in a 2D game, but if these objects go off screen I want to remove these objects so they are no longer updated. What I have below works for me, but this doesn't work in other languages. Usually I have to make another "destroy list" that saves the objects I want to destroy and then loop again to remove them, because you can't remove an object from the list while iterating without some visible glitch.

Is Ruby just not showing any visible glitch while doing this or does Ruby's array work differently when removing multiple possible objects from a list while it's still iterating?

objects = []

objects.each{|o| o.withinBounds ? o.update : objects.delete(o)}

Solution

  • In Ruby you will actually find a glitch if you do what you are saying.

    Try this:

    objects = [1,2,3,4]
    objects.each { |el| objects.delete(el) }
    => [2, 4]
    

    You would expect the result to be an empty array, but is not. We are messing up with the elements of the arr and each gets confused, because the length of the array has changed. The each iterator looks something like this in pseudocode:

    count = 0
    while length(objects) > count
      yield objects[count]
      count + 1
    end
    

    So, in the example I shown above, the reason why we get [2, 4] can be explained on a step by step analysis on what objects.each { |el| objects.delete(el) } is doing:

    1. We start with 4 (length of objects) > 0.
    2. Number 1 is yielded, and deleted.
    3. count = 1
    4. 3 (length of objects) > 1
    5. Number 3 is yielded and deleted.
    6. count = 2
    7. 2 (length of objects) is not bigger than count
    8. We are done, so we have [2, 4]

    There is a better way to do what you are trying, by using delete_if:

    new_objects = objects.delete_if {|o| o.withinBounds }