@Override
 public T first() {
   if (size == 0) {
     return null;
   }
   Node<T> current = head;
   // traverse to lowest level of head sentinel
   while (current.getDown() != null) {
     current = current.getDown();
   }
   return current.getNext().getData();
 }
 @Override
 public Set<T> dataSet() {
   // navigate to bottom level and go through all elements
   Set<T> set = new HashSet<T>();
   Node<T> current = head;
   while (current.getDown() != null) {
     current = current.getDown();
   }
   // now should be on bottom level of head sentinel
   while (current.getNext() != null) {
     current = current.getNext();
     set.add(current.getData());
   }
   return set;
 }
 @Override
 public void put(T data) {
   if (data == null) {
     throw new java.lang.IllegalArgumentException();
   }
   int height = getHeight();
   if (height > head.getLevel()) {
     resizeHead(height);
   }
   Node<T> current = head;
   Node<T> previous = null;
   // go through each level once
   for (int level = head.getLevel(); level > 0; level--) {
     // at max height that should be added
     if (level == height) {
       // traverse until next is null or bigger than data, then add
       while (current.getNext() != null && data.compareTo(current.getNext().getData()) > 0) {
         current = current.getNext();
       }
       // add after current, update pointers
       Node<T> newNode = new Node<>(data, level, current.getNext(), null, null);
       current.setNext(newNode);
       previous = newNode;
       // after adding, drop down a level
       current = current.getDown();
       // now at level below max height of added node, update up/down
     } else if (level < height) {
       // traverse until next is null or bigger again
       while (current.getNext() != null && data.compareTo(current.getNext().getData()) > 0) {
         current = current.getNext();
       }
       // add after current, update next/up/down pointers
       Node<T> newNode = new Node<>(data, level, current.getNext(), previous, null);
       current.setNext(newNode);
       previous.setDown(newNode);
       previous = newNode;
       // now drop again, fine if on bottom level
       current = current.getDown();
       // now at higher level than needs to be added, traverse until drop
     } else {
       while (current.getNext() != null && data.compareTo(current.getNext().getData()) > 0) {
         current = current.getNext();
       }
       current = current.getDown();
     }
   }
   size++;
 }
 @Override
 public T remove(T data) {
   if (data == null) {
     throw new java.lang.IllegalArgumentException();
   }
   T retValue = null;
   // go through each row, if found on row, pointers pass over
   Node<T> current = head;
   for (int level = head.getLevel(); level > 0; level--) {
     while (current.getNext() != null && data.compareTo(current.getNext().getData()) > 0) {
       current = current.getNext();
     }
     // next node either null, bigger than data, or a match
     if (current.getNext() != null && data.equals(current.getNext().getData())) {
       // match found
       retValue = current.getNext().getData();
       current.setNext(current.getNext().getNext());
     }
     current = current.getDown();
   }
   if (retValue != null) {
     size--;
   }
   return retValue;
 }
 @Override
 public T last() {
   if (size == 0) {
     return null;
   }
   Node<T> current = head;
   while (current.getDown() != null) {
     while (current.getNext() != null) {
       current = current.getNext();
     }
     current = current.getDown();
   }
   // now should be on lowest level, traverse to end
   while (current.getNext() != null) {
     current = current.getNext();
   }
   return current.getData();
 }
 @Override
 public T get(T data) {
   if (data == null) {
     throw new java.lang.IllegalArgumentException();
   }
   if (size() == 0) {
     return null;
   }
   Node<T> current = head;
   for (int level = head.getLevel(); level > 0; level--) {
     // traverse until next is null or bigger than data, then drop
     while (current.getNext() != null && data.compareTo(current.getNext().getData()) > 0) {
       current = current.getNext();
     }
     if (current.getNext() != null && data.equals(current.getNext().getData())) {
       return current.getNext().getData();
     }
     current = current.getDown();
   }
   return null;
 }