Search code examples
javalinked-listruntime-error

How do I iterate over a linked list and add nodes to it at the same time?


I have an algorithm that can be implemented by iteratively modifying a finite integer indexed collection of lists of objects of a certain class X. At every iteration, every object in every list should be visited. At every visit, this object may need to be removed, and some number of other objects may need to be inserted, into the same list or into other lists. At some point it will be detected that an acceptable state has been reached and the iteration will stop, returning the whole collection of lists of X as the result.

In short, the algorithm — in its most straightforward reading — asks of me to do non-trivial deletions from and insertions into a linked list — and the linked list in question may or may not be being iterated over at the time.

I want to implement this algorithm in Java, and I thought I could use LinkedList<State>[] as my state type. I have eventually coerced my code into compiling, but it (not unexpectedly) throws the exception java.util.ConcurrentModificationException. I am not sure how to proceed. The LinkedList of Java seems to be less powerful than a true linked list — or else I wield it wrong.

Solutions I am considering

  • I could roll out my own, simpler implementation of linked lists, that would not be throwing this exception. (There is no reason my algorithm should not work with linked lists in principle — it seems the problem is with the extra safety features forced on me by the existing standard implementation of linked lists.) However, this will increase the cost of writing and maintaining the program.
  • I could create a new array of lists at every step of the iteration and refill it as needed, so that my program always reads from one array of lists and writes into another. However, this will make the code significantly more complicated and twice less memory efficient.

My question

Is there a better way?

* Am I missing some handy standard API that would let me do what I want with the LinkedList I have?

  • Should I choose another standard data structure?
  • Or should I approach the problem in a fundamentally different way that would be better aligned with the qualities of Java?

Simplified example

Here is a simplified example that I made by removing stuff from my actual code. If I can make it work, I should be able to make my actual code work as well. This code compiles but throws the exception java.util.ConcurrentModificationException at run time. The class X here is simply a box for int and the program will hopefully compute the hailstone sequence of the number 39, but what exactly it computes is not important — my actual task is entirely different.

import java.util.LinkedList;
import java.util.ListIterator;

class Example
{
    private LinkedList<Int> state_set;
    private int success_counter = 0;

    public Example (int number)
    {
        this.state_set = new LinkedList<Int> ( );
        this.state_set.add (new Int (number));
    }

    public void example ( )
    {
        while (true)
        {
            for (ListIterator<Int> iterator = this.state_set.listIterator ( ); iterator.hasNext ( );)
            {
                Int focussed_state = iterator.next ( );
                iterator.remove ( );
                if (focussed_state.value == 1)
                {
                    this.success_counter += 1;
                }
                else
                {
                    if (focussed_state.value % 2 == 0)
                    {
                        this.state_set.add (new Int (focussed_state.value / 2));
                    }
                    else
                    {
                        this.state_set.add (new Int (focussed_state.value * 2 + 1));
                    }
                }
            }

            if (this.success_counter >= 1)
                break;
        }
    }
}

class Int
{
    public int value;
    public Int (int value) { this.value = value; }
}

class Main
{
    public static void main (String [] args)
    {
        Example example = new Example (39);
        example.example ( );
        System.out.println ("Done!");
    }
}

Note that this program would succeed for the input 1 because in that case the linked list is never added to.


Solution

  • You seem to be using the linked list like a queue, so instead of iterating over it using an iterator, use the methods in Queue instead. poll elements from the linked list, stopping the loop if poll returns null, and offer new elements to the queue.

    Int focusedState;
    stateSet.listIterator(1).nextIndex()
    while ((focusedState = stateSet.poll()) != null)
    {
        if (focusedState.value == 1)
        {
            this.successCounter += 1;
            break;
        }
        else if (focusedState.value < 0) {
            break;
        }
        else if (focusedState.value % 2 == 0)
        {
            this.stateSet.offer(new Int(focusedState.value / 2));
        }
        else
        {
            this.stateSet.offer(new Int(focusedState.value * 2 + 1));
        }
    }
    

    If you are not purely using the linked list like a queue in your real code, you can just create a new Iterator after the adding an element, e.g.

    // remember where we are
    int currIndex = iterator.nextIndex();
    // add the element
    this.stateSet.add(...);
    // create a new iterator
    iterator = this.stateSet.listIterator(currIndex);
    

    Note that recreating the list iterator starting from currIndex will iterate from one end of the linked list to the specified index under the hood. If that is undesirable, I suggest using an ArrayList instead.

    If you don't need to add the element at the end of the list, you can also call ListIterator.add, which adds the new element just behind the cursor of the iterator (i.e. you can get it by calling previous()).


    There are other problems with the current code (seemingly useless outer while(true) loop, the linked list is only ever going to have at most one element, integer overflow, etc), but I assume this is because this is a simplified version of your real code.