08b: List ADT variants

ICS211, Spring 2013
Dr. Zach

(switch view)

Status

Last time

List ADT variant: Sorted list

Add method for SortedList (singly-linked)

class SortedList<E extends Comparable<E>> {

  private Node<E> head;
  private int size;
  
  public void add(E item) {
    Node<E> curr = head;
    Node<E> beforeCurr = null;
    //advance curr until null or pointing to first node >= item
    while (curr != null && curr.getData().compareTo(item) < 0) {
      beforeCurr = curr;
      curr = curr.getNext();
    }
    if (beforeCurr == null) {  //or: if (curr == this.head) {
      //adding at head of list
      this.head = new Node<E>(item, this.head);
    }else {
      beforeCurr.setNext(new Node<E>(item, curr));
    }
    this.size++;
  }
  
  //...other methods...
}

List variant: Sentinel node

List variant: Doubly-linked nodes

public class DLNode<E> {

  private E data;
  private DLNode<E> next;
  private DLNode<E> prev;

  public DLNode(DLNode<E> prev, E data, DLNode<E> next) {
    this.data = data;
    this.prev = prev;
    this.next = next;
  }

  public DLNode(E data) {
    this(data, null);
  }

  public E getData() {
    return data;
  }

  public void setData(E data) {
    this.data = data;
  }

  public DLNode<E> getNext() {
    return next;
  }

  public void setNext(DLNode<E> next) {
    this.next = next;
  }
  
  public DLNode<E> getPrevious() {
    return prev;
  }
  
  public void setPrevious(DLNode<E> prev) {
    this.prev = prev;
  }
}

Alternatively: could have extended Node to just add previous details.

DLNode as subclass

public class DLNode<E> extends Node<E> {

  private DLNode<E> prev;

  public DLNode(DLNode<E> prev, E data, DLNode<E> next) {
    super(data, next);
    this.prev = prev;
  }

  public DLNode(E data) {
    this(null, data, null);
  }

  public DLNode<E> getPrevious() {
    return prev;
  }
  
  public void setPrevious(DLNode<E> prev) {
    this.prev = prev;
  }
}

This can be a little problematic to use in practice, though, since getNext() returns a Node while getPrevious() returns a DLNode. This means you can't do something like node.getNext().getPrevious() without casting.

Doubly-linked List

List variant: tail pointer

List variant: Circular list

  public String toString() {
    String str = "";
    DLNode<E> curr = this.sentinel.getNext();
    while (curr != this.sentinel) {
      str += curr.getData() + " ";  //crude toString formatting
      curr = curr.getNext();
    }
    return str;
  }

List variant: Array-based linked list

Iterators

Why we need them

  List<String> list = new List<String>();
  //...add some values...
  for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));  
  }

java.util.Iterator<E> interface

Basically an object that moves along the list

Our Iterator

A basic Iterator

public class List<E> {
  //...current contents...

  public class LinkedListIterator implements java.util.Iterator<E> {
    //Note: did not redeclare <E>

    Node<E> next;  //Node of item to return on next call to next()

    public LinkedListIterator() {
      this.next = head;  //refers's to List's head.  Explicit: List.this.head
    }

    public boolean hasNext() {
      return next != null;
    }

    public E next() {
      if (next == null) {
        throw new java.util.NoSuchElementException();
      }else {
        E item = next.getData();
        next = next.getNext();
        return item;
      }
    }

    public void remove() {
      //optional: not implemented here
      throw new UnsupportedOperationException();
    }
  }

}//end of List

How to build a new iterator?

Iterable List

class List<E> implements Iterable<E> {
  //...current contents...

  public java.util.Iterator<E> iterator() {
    return new LinkedListIterator();  //or: new List<E>.LinkedListIterator();
  }
  
  //...nested LinkedListIterator class here...
}

Iterator and Iterable use

  List<Integer> list = new List<Integer>();
  for (int i = 1; i < 10; i++) {
    list.add(i - 1, i);
  }

  //iterator for-each loop  (thanks to List implementing Iterable)
  for (int item : list) {
    System.out.print(item + " ");
  }
  System.out.println();

  //basically equivalent using iterator directly
  int item;
  for (java.util.Iterator<Integer> iter = list.iterator(); iter.hasNext(); ) {
    item = iter.next();
    System.out.print(item + " ");
  }
  System.out.println();

An Iterator with remove() implemented

  public class LinkedListIterator implements java.util.Iterator<E> {

    Node<E> next;  //Node of item to return on next call to next()
    Node<E> last;  //Node of item returned by last call to next()
    Node<E> beforeLast;  //Node before last, needed for remove()

    public LinkedListIterator() {
      this.next = head;  //refers's to List's head.  Explicit: List.this.head
      this.last = null;
      this.beforeLast = null;
    }

    public boolean hasNext() {
      return next != null;
    }

    public E next() {
      if (next == null) {
        throw new java.util.NoSuchElementException();
      }else {
        E item = next.getData();

        //advance along list
        if (last != null) {
          beforeLast = last;
        }
        last = next;
        next = next.getNext();

        return item;
      }
    }

    public void remove() {
      if (this.last == null) {
        //next() not called yet or already removed since call
        throw new IllegalStateException();
      }else {
        if (beforeLast == null) {
          //haven't returned second item of list yet, so removing first
          head = next;
        }else {
          beforeLast.setNext(next);
        }
        last = null;  //so we know we removed since last call to next()
      }
    }
  }

Wouldn't need beforeLast if we had a doubly-linked list.

For next time