Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ListIterator to move back and forth over a LinkedList in Java

Tags:

I have a LinkedList over which I need to iterate back and forth multiple times. I am using it to keep track of a series of pages in a workflow that will be created dynamically. This does not behave as I would expect. Given this example:

LinkedList<String> navigationCases;
navigationCases.add("page1");
navigationCases.add("page2");
navigationCases.add("page3");
navigationCases.add("page4");

ListIterator navigationItr = navigationCases.listIterator();
navigationItr.next(); // Returns page1
navigationItr.next(); // Returns page2
navigationItr.previous(); //Returns page2 again
navigationItr.next(); //Returns page2 again

I thought perhaps I was building my list incorrectly, or using the Iterator wrong, but after reading the documentation, this seems to be by design:

A ListIterator has no current element; its cursor position always lies between the element that would be returned by a call to previous() and the element that would be returned by a call to next().

And:

(Next) Returns the next element in the list. This method may be called repeatedly to iterate through the list, or intermixed with calls to previous to go back and forth. (Note that alternating calls to next and previous will return the same element repeatedly.)

So after reading this, it is clear why my code is behaving the way it does. I just don't understand why it should work this way. Even remove seems to be bending over backwards to accommodate this implementation:

Note that the remove() and set(Object) methods are not defined in terms of the cursor position; they are defined to operate on the last element returned by a call to next() or previous().

Conceptually, a LinkedList seemed to model my workflow cases pretty well, but I can't use an Iterator that behaves this way. Am I missing something here, or should I just write my own class maintain a list of cases and navigate through them?

like image 345
Kurt Koller Avatar asked Nov 20 '12 22:11

Kurt Koller


2 Answers

This should do your job:

public class Main {
    public static void main(String[] args) {
        final LinkedList<String> list = new LinkedList<String> ();

        list.add ("1"); list.add ("2"); list.add ("3"); list.add ("4");

        final MyIterator<String> it = new MyIterator (list.listIterator());

        System.out.println(it.next());
        System.out.println(it.next ());
        System.out.println(it.next ());
        System.out.println(it.previous ());
        System.out.println(it.previous ());
        System.out.println(it.next ());
    }

    public static class MyIterator<T> {

        private final ListIterator<T> listIterator;

        private boolean nextWasCalled = false;
        private boolean previousWasCalled = false;

        public MyIterator(ListIterator<T> listIterator) {
            this.listIterator = listIterator;
        }

        public T next() {
            nextWasCalled = true;
            if (previousWasCalled) {
                previousWasCalled = false;
                listIterator.next ();
            }
            return listIterator.next ();
        }

        public T previous() {
            if (nextWasCalled) {
                listIterator.previous();
                nextWasCalled = false;
            }
            previousWasCalled = true;
            return listIterator.previous();
        }

    }   
}

And a fiddle for it.

like image 176
ShyJ Avatar answered Sep 21 '22 08:09

ShyJ


ListIterator was designed to behave this way. See the conversation beneath ShyJ's answer for the rationale.

I find this behavior to be beyond idiotic, and have instead written a very simple alternative. Here's the Kotlin code with a extension function for ArrayLists:

class ListIterator<E>(var list: ArrayList<E>) : Iterator<E> {

    private var cursor: Int = 0

    fun replace(newList: ArrayList<E>) {
        list = newList
        cursor = 0
    }

    override fun hasNext(): Boolean {
        return cursor + 1 < list.size
    }

    override fun next(): E {
        cursor++
        return current()
    }

    fun hasPrevious(): Boolean {
        return 0 <= cursor - 1
    }

    fun previous(): E {
        cursor--
        return current()
    }

    fun current(): E {
        return list[cursor]
    }

}

fun <E> ArrayList<E>.listFlippingIterator() = ListIterator(this)

If you wish to include removal functionality, I highly recommend writing the API to explicitly instruct the iterator if it should remove left or right, e.g. by defining those methods as removeNext() and removePrevious().

like image 21
Paul Lammertsma Avatar answered Sep 24 '22 08:09

Paul Lammertsma