Review

In class last time, we begun actually implementing our version of Java’s LinkedList with our LinkedIntList. So far, we have defined the following classes:

// ListNode.java
public class ListNode {
    public int data;
    public ListNode next;

    public ListNode() {
        this(0, null);
    }

    public ListNode(int data) {
        this(data, null);
    }

    public ListNode(int data, ListNode next) {
        this.data = data;
        this.next = next;
    }
}

// LinkedIntList.java
// Hunter Schafer, CSE 143
// Represents a list of integers.
public class LinkedIntList {
    private ListNode front;

    // post: Adds the given value to the end of this list
    public void add(int value) {
        if (front == null) {
            front = new ListNode(value);
        } else {
            ListNode current = front;
            while (current.next != null) {
                current = current.next;
            }
            current.next = new ListNode(value);
        }
    }

    // post: Prints the list contents in one line, separated by spaces
    public void printList() {
        ListNode current = front;
        while (current != null) {
            System.out.print(current.data + " ");
            current = current.next;
        }
        System.out.println();
    }
}

As a reminder, we implemented add as a case study to look at how to modify the list. We saw that there are only two ways to actually change the list: change front or change a .next in the list. Our first version of add didn’t change the list since it said current = new ListNode(value).

Case Analysis

We also explored that linked list programming is a great practice in case analysis. In our add method, we need to account for the fact that in the case that the list is empty (i.e., front == null), we need to have special code to change front. When working in any linked list problem, think carefully about what special cases you might need to consider.

In the last quiz section, we identified there are three common cases that you will might have to consider for linked list programming:

  • The front case for changing a node reference at the front of the list. This is the first if branch in the add method above.
  • The middle case for changing a node reference in the middle of the list. This isn’t applicable in our add method.
  • The end case for changing a node reference at the end of the list. This is the else branch in the add method above.

It’s not always the case that you will have all three cases. In fact, it’s sometimes possible to write one case that could work in both the middle and end cases. This is just a heuristic to help you consider if your code works in all cases and is not a general rule!

max Constructor

To practice this idea of writing cases, let’s add another constructor to the LinkedIntList. We will define a constructor that takes an int max and builds up the list of numbers from 0 to max in decreasing order. So for example, the client might use this constructor as in:

LinkedIntList list = new LinkedIntList(3);
list.printList();  // 3 2 1 0

In terms of strategy, it’s clear we want to keep adding nodes to the list but the question is how. I would argue it would be a lot of work to keep adding numbers at the end of the list since that might require traversing the list multiple times. Instead, what if we build the list up in reverse order? Start by adding 0 at the front, then add 1 before that, then 2, and so on.

Sometimes, it’s not always clear how to write a solution to a problem, so you might start by just writing out some code to work through a small example.

public class LinkedIntList {
    private ListNode front;

    public LinkedIntList(int max) {
        // Make front start with 0
        front = new ListNode(0);

        // Make a new node with 1 and add that to the front
        // Remember the temp.next = front or else you drop the node with 0!
        ListNode temp = new ListNode(1);
        temp.next = front;  // important to remember!
        front = temp;

        // And keep repeating
        ListNode temp2 = new ListNode(2);
        temp2.next = front;
        front = temp2;

        // ...
    }
}

This exercise can help us spot patterns in that adding new values to the front use the same code each time except a number. So now that we can see a pattern in adding some of these numbers, let’s start writing a loop to help reduce redundancy and to make this code work more generally.

public LinkedIntList(int max) {
    front = new ListNode(0);

    for (int i = ???; ???; ???) {
        ListNode temp = new ListNode(i);  // changed this to i
        temp.next = front;                // important to remember!
        front = temp;
    }
}

We’re almost there, but just need to decide the loop bounds. Since our written-out code above started with 1 for this snippet and then 2, we will want to start i at 1 and have it go to max. So then our next version of this constructor will be:

public LinkedIntList(int max) {
    front = new ListNode(0);

    for (int i = 1; i <= max; i++) {
        ListNode temp = new ListNode(i);  // changed this to i
        temp.next = front;                // important to remember!
        front = temp;
    }
}

Note: There are a couple of code quality problems with this code, but we will discuss them in class for today. So our next version of the program that we will have in class today will fix these code quality errors and explain why there are there. Can your form an idea of where the errors are?