4

I'm having issues getting the logic behind using an iterator. I need to be able to remove elements from an ArrayList inside the loop, so I thought I'd use an Iterator object. However, I'm not sure how I can transform my original code to an iterator.

Original loop:

ArrayList<Entity> actorsOnLocation = loc.getActors();
int size = actorsOnLocation.size();

if (size > 1) {
    for(Entity actor: actorsOnLocation) {
        // Loop through remaining items (with index greater than current)
        for(int nextEnt=index+1; nextEnt < size-1; nextEnt++) {
            Entity opponent = actorsOnLocation.get(nextEnt);
            // Here it's possible that actor or opponent "dies" and
            // should be removed from the list that's being looped
        }
    }
}

I understand that I'd have to use while-loops, which would work for the first loop. But how can you convert the second loop's conditions to something that works with an Iterator? This post says you can obtain the iterator at any point, but in the documentation I can't find anything such as a .get method.

ArrayList<Entity> actorsOnLocation = loc.getActors();
int size = actorsOnLocation.size();
Iterator actorsIterator = actorsOnLocation.iterator();

// How to get size of an iterator?
if (size > 1) {
    while(actorsIterator.hasNext()) {
        Entity actor = (Entity) actorsIterator.next();
        // How to get current index?
        int index = actorsIterator.indexOf(actor);
        // How to convert these conditions to Iterator?
        for(int nextEnt=index+1; nextEnt < size-1; nextEnt++) {
            Entity opponent = actorsOnLocation.get(nextEnt);
            // Here it's possible that actor or opponent "dies" and
            // should be removed from the list that's being looped

            // If the actor dies, the outer loop should skip to the next element
        }
    }
}

Final question: if you access an element in an iterator, that element isn't a copy of the original one, right? In other words, I can set properties to that element, and then access those changed properties by accessing the original Collection?

As Eran pointed out, this might not be as straight forward as it seems because the two loops traverse the same list and might interfere with one another. The question is, then, how do I solve this issue?

Community
  • 1
  • 1
Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239

1 Answers1

0

it's possible that actor or opponent "dies" and should be removed from the list

You would be able to achieve removal of the actor through ListIterator<T>, but removing the opponent would be illegal, because actor's iterator will be invalidated. This would cause ConcurrentModificationException.

You need to change your algorithm: since you need removals from two locations, you need to convert the inner loop, not the outer, to using iterators:

outerLoop:
for(int i = 0 ; i < actorsOnLocation.length()-1 ; i++) {
    Entity actor = actorsOnLocation.get(i);
    // Loop through remaining items (with index greater than current)
    ListIterator<Entity> oppIter = actorsOnLocation.listIterator(i+1);
    while (oppIter.hasNext()) {
        Entity opponent = oppIter.next();
        // Here it's possible that actor or opponent "dies" and
        // should be removed from the list that's being looped
        if (opponent.mustDie()) {
            oppIter.remove();
        } else if (actor.mustDie()) {
            // The following operation invalidates oppIter
            actorsOnLocation.remove(i);
            // so we must continue the outer loop
            continue outerLoop;
        }
    }
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    Not tested, but shouldn't the first loop be `i < actorsOnLocation.size() - 1`, because you always need to be able to get the actor's index + 1 for the opponent? (`size()` because it's an ArrayList.) I only now see this, but this was also an error in my own code. – Bram Vanroy Dec 22 '15 at 13:35
  • Second, if the opponent dies, it isn't removed from `actorsOnLocation`, so it's still the next item in the outer loop, correct? Maybe the best way to deal with the issue is to add a boolean to actors so that when they die it returns false, and then at the beginning of the loop check if they are "alive", if not `continue;`? What do you think? – Bram Vanroy Dec 22 '15 at 13:46
  • @BramVanroy opponent is removed from `actorsOnLocation` when `remove()` is called on a list iterator belonging to the list. – Sergey Kalinichenko Dec 22 '15 at 14:26
  • Oh, so if I understand correctly: iterator is basically a shadow copy of an original collection, which passes through changes that have been made to itself? Also, why is the whole iterator removed? Shouldn't only the opponent be removed, because now the next objects in line aren't looped anymore? – Bram Vanroy Dec 22 '15 at 14:31
  • @BramVanroy Iterator is not a copy, it's a generalized index into the original collection. For an array list it's essentially a glorified `int`: when you call `oppIter.next()`, it performs `++` on the `int` it maintains, and returns you `list.get(internal_index)`. When you perform `oppIter.remove();` it calls `list.remove(internal_index)`, and so on. Iterator's advantage over `int` index with `ArrayList` is purely aesthetic. Once you switch to `LinkedList`, however, you get huge performance boost, because indexing into a linked list is expensive O(n). – Sergey Kalinichenko Dec 22 '15 at 14:37