Lists #
In Python, if we want a sequence of items we use a list.
In Java, we’ve seen three ways to make a sequence so far:
- Arrays
- Last lecture, one of the examples used List.of(…)
- Singly linked lists (Cell)
As we’ll see, there are lots of ways to make a list, and there are advantages and disadvantages to each.
The Java standard library deals with this by making List an interface, not a concrete type: java.util.List
So List is defined by what it can do, not how it’s specifically implemented.
Let’s look at the docs. What can a list do?
- add(item): Stick a new element on the end. That’s already an operation you couldn’t do with just an array.
- get(index): Get the item at a given index.
- set(index, item): Put an item at a given index.
- size(): Gets the number of items in the list.
- contains(item): Check if an object appears in the list.
- iterator(): This allows us to loop through the list with a for-each style loop.
Java also provides several list implementations. Two of them are especially noteworthy:
- ArrayList - Uses an array to store the list items.
- LinkedList - Uses a linked list of cells to store the list items.
Sample Algorithm: Bubble Sort #
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;
public class App {
    public static void main(String[] args) throws InterruptedException {
        // try 20,000 for ArrayList and 2000 for LinkedList
        List<Integer> xs = randomInts(30000);
        //System.out.println("before: " + xs);
        
        long t0 = System.nanoTime();
        bubbleSort2(xs);
        long t1 = System.nanoTime();
        double dt = (t1 - t0) / 1000000000.0;
        
        //System.out.println("after: " + xs);
        System.out.println("sorted? " + (isSorted(xs) ? "yes" : "no"));
        System.out.printf("That took %.03f seconds\n", dt);
    }
    static boolean isSorted(List<Integer> xs) {
        for (int ii = 0; ii < xs.size() - 1; ++ii) {
            if (xs.get(ii) > xs.get(ii + 1)) {
                return false;
            }
        }
        return true;
    }
    static void bubbleSort1(List<Integer> xs) {
        for (int ii = 0; ii < xs.size(); ++ii) {
            for (int jj = 0; jj < xs.size() - 1; ++jj) {
                int vv0 = xs.get(jj);
                int vv1 = xs.get(jj + 1);
                if (vv0 > vv1) {
                    // swap
                    xs.set(jj, vv1);
                    xs.set(jj + 1, vv0);
                }
            }
        }
    }
    static void bubbleSort2(List<Integer> xs) {
        for (int ii = 0; ii < xs.size(); ++ii) {
            ListIterator<Integer> it = xs.listIterator();
            while (it.nextIndex() < xs.size() - 1) {
                int vv0 = it.next();
                int vv1 = it.next();
                    
                if (vv0 > vv1) {
                    // swap
                    it.set(vv0); // 
                    // first previous gets jj+1 again
                    it.previous();
                    // this gets us back to jj
                    it.previous();
                    it.set(vv1);
                    it.next();
                }
                else {
                    it.previous();
                }
            }
        }
    }
    static List<Integer> randomInts(int nn) {
        var rand = new Random();
        var ys = new ArrayList<Integer>();
        for (int ii = 0; ii < nn; ++ii) {
            ys.add(rand.nextInt(1000)); 
        }
        return ys;
    }
}
Questions to discuss:
- How does the time for bubblesort increase as we increase the size of the input array?
- Why?
- How does it change if we switch to a LinkedList?
- Why?
- What if we move to bubbleSort2?
