List

CSCI 1913 – Introduction to Algorithms, Data Structures, and Program Development
Adriana Picoral

List

An ordered, finite, mutable data structure

  • Plan out, in the abstract, the “object nature” and what behaviors you need to support
  • Write some example/test code

List

  • What is a list?
  • How do we use lists?
  • What methods do we need?

List

  • An ordered, finite, mutable data structure
  • We manipulate it (create list, add, append, remove, insert, get length, set an individual value)
  • append, prepend, insert, getLength, set, search

What about sort?

Interface

An interface class is a class that is 100% abstract (no concrete members).

There’s special keyword for it: interface

  • Like abstract methods in an abstract class, interface method signatures form a contract with the classes that implement that interface (another keyword: implements)

Comparable Interface

“This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class’s natural ordering, and the class’s compareTo method is referred to as its natural comparison method.”

Here’s what the Comparable interface looks like:

public interface Comparable<T> {
    int compareTo(T obj);
}

Example Comparable implementation

public class Grade implements Comparable<Grade> {
    private double grade;

    public Grade(double grade) {
        this.grade = grade;
    }

    public double getGrade() {
        return grade;
    }
    
    public int compareTo(Grade o) {
        if (o.getGrade() > this.getGrade()) {
            return -1;
        }
        if (o.getGrade() < this.getGrade()) {
            return 1;
        }
        return 0;
    }
}

Example of comparable use

import java.util.Arrays;

public class UseGradeComparison {
    public static void main(String[] args) {
        Grade[] grades = new Grade[]{new Grade(10.3), new Grade(0.3), new Grade(3)};
        Arrays.sort(grades); // this works because Grade implements Comparable
        for (Grade g : grades) {
            System.out.println(g.getGrade());
        }
    }
}

List methods

How do we do lists?

create list of a specific size, append, prepend, remove, insert, getLength, search, sort

Runtime Summary

Front of List Middle of List End of List
get O(1) O(1) O(1)
set O(1) O(1) O(1)
insert O(n) O(n) O(1)
append N/A N/A O(1)
prepend O(n) N/A N/A
pop O(n) O(n) O(1)

What happens when we try to append to a list/array that is already full?

List – let’s start with the basics

Here’s how we are setting up the private instance variable: private T[] data;

Constructor:

public List() {
  data = (T[]) new Object[size]; // define an arbitraty size
}

List – let’s start with the basics

Methods:

  • getter get(int index) – throw an error if out of range
  • length() returns how many items in the list
  • set(int index, T value) – changes the value of item at index (return a bollean if value was set successfully)
  • override toString() for testing purposes

Changing size of the list – adding to list

Methods:

  • append
  • prepend
  • insert

append

public void append(T value) {
  if (length < size) {
    data[length] = value;
    length++;
  }
}

What happens when length is the same as size?

Expand the size of our array

private void expandArray() {
  size *= 2;
  T[] newData = (T[]) new Object[size];
  for (int i = 0; i < length; i++) {
    newData[i] = data[i];
  }
  data = newData;
}

append implementation

public void append(T value) {
  if (length == size) {
    expandArray();
  }
  data[length] = value;
  length++;
}

Runtime of array Expand

The arrayExpand process is \(O(n)\) (gotta copy the array!) BUT it doesn’t run every time, so how do we handle the affect on append?

Best case: \(O(1)\) Worst case: \(O(n)\)

Runtime of array Expand

What we often care about is not the runtime of myList.append(4); but instead:

for (int i = 0; i < N; i++) {
  myList.append(i);
}

Runtime of array Expand

double array vs. increase array size by 10 plots

Why does it work this way

One way to think about it:

  • After an expand that costs N operations:
  • We get N “free” appends
  • Then we do 2N operations for next expand. (and so forth)

Runtime of array Expand

  • It is not technically true that append is \(O(1)\)
  • It IS true that \(O(n)\) appends costs \(O(n)\) time
  • We can divide that through and call append \(O(\frac{n}{n}) = O(1)\)
  • We can say “amortized runtime” \(O(1)\) instead of “runtime \(O(n)\)

prepend

public void prepend(T value) {
  shiftRight(0); // need to shift all the other values
  data[0] = value;
}

insert

public void insert(int index, T value) {
  shiftRight(index);
  data[index] = value;
}

Changing size of the list – removing from list

  • pop
  • remove

Solution

public class List<T> {
    private T[] data;
    private int size = 50;
    private int length = 0;

    public List() {
        data = (T[]) new Object[size];
    }

    public T get(int index) {
        if (index < length) {
            return data[index];
        }
        throw new RuntimeException("index out of range");
    }

    public int length() {
        return length;
    }

    public boolean set(int index, T value) {
        if (index < length) {
            data[index] = value;
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        String message = "";
        for (int i = 0; i < length; i++) {
            message += data[i] + " ";
        }
        return message.trim();
    }

    public void append(T value) {
        if (length == size) {
            expandArray();
        }
        data[length] = value;
        length++;
    }

    private void expandArray() {
        size *= 2;
        T[] newData = (T[]) new Object[size];
        for (int i = 0; i < length; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    private void shiftRight(int startIndex) {
        length++;
        if (length == size) {
            expandArray();
        }

        for (int i = length-1; i > startIndex; i--) {
            data[i] = data[i-1];
        }
    }

    public void prepend(T value) {
        if (length == size) {
            expandArray();
        }
        shiftRight(0);
        data[0] = value;
    }

    public void insert(int index, T value) {
        if (length == size) {
            expandArray();
        }
        shiftRight(index);
        data[index] = value;
    }

    private void shiftLeft(int startIndex) {
        for (int i = startIndex; i < length; i++) {
            data[i] = data[i+1];
        }
        length--;
    }

    public T pop() {
        T value = data[0];
        shiftLeft(0);
        return value;
    }

    public void remove(int index) {
        shiftLeft(index);
    }

}