Lists2 Study Guide
Author: Wayne Li and Kartik Kapur

Lecture Code

Code from this lecture available at https://github.com/Berkeley-CS61B/lectureCode-fa20/tree/master/lists2.

Live QA

Linked here

Check-in Exercise

Linked here.

Overview

Naked Data Structures IntLists are hard to use. In order to use an IntList correctly, the programmer must understand and utilize recursion even for simple list related tasks.

Adding Clothes First, we will turn the IntList class into an IntNode class. Then, we will delete any methods in the IntNode class. Next, we will create a new class called SLList, which contains the instance variable first, and this variable should be of type IntNode. In essence, we have “wrapped” our IntNode with an SLList.

Using SLList As a user, to create a list, I call the constructor for SLList, and pass in the number I wish to fill my list with. The SLList constructor will then call the IntList constructor with that number, and set first to point to the IntList it just created.

Improvement Notice that when creating a list with one value, we wrote SLList list = new SLList(1). We did not have to worry about passing in a null value like we did with our IntList. Essentially, the SLList class acts as a middleman between the list user and the naked IntList.

Public vs. Private We want users to modify our list via SLList methods only, and not by directly modifying first. We can prevent other users from doing so by setting our variable access to private. Writing private IntNode first; prevents code in other classes from accessing and modifying first (while the code inside the class can still do so).

Nested Classes We can also move classes into classes to make nested classes! You can also declare the nested classes to be private as well; this way, other classes can never use this nested class.

Static Nested Classes If the IntNode class never uses any variable or method of the SLList class, we can turn this class static by adding the “static” keyword.

Recursive Helper Methods If we want to write a recursive method in SLList, how would we go about doing that? After all, the SLList is not a naturally recursive data structure like the IntNode. A common idea is to write an outer method that users can call. This method calls a private helper method that takes IntNode as a parameter. This helper method will then perform the recursion, and return the answer back to the outer method.

Caching Previously, we calculated the size of our IntList recursively by returning 1 + the size of the rest of our list. This becomes really slow if our list becomes really big, and we repeatedly call our size method. Now that we have an SLList, lets simply cache the size of our list as an instance variable! Note that we could not do this before with out IntList.

Empty Lists With anSLList, we can now represent an empty list. We simply set first to null and size to 0. However, we have introduced some bugs; namely, because first is now null, any method that tries to access a property of first (like first.item) will return a NullPointerException. Of course, we can fix this bug by writing code that handles this special case. But there may be many special cases. Is there a better solution?

Sentinel Nodes Lets make all SLList objects, even empty lists, the same. To do this, lets give each SLList a sentinel node, a node that is always there. Actual elements go after the sentinel node, and all of our methods should respect the idea that sentinel is always the first element in our list.

Invariants An invariant is a fact about a data structure that is guaranteed to be true (assuming there are no bugs in your code). This gives us a convenient checklist every time we add a feature to our data structure. Users are also guaranteed certain properties that they trust will be maintained. For example, an SLList with a sentinel node has at least the following invariants:

Exercises

C Level

  1. Complete the exercises from the online textbook.

  2. Reexplain what the sentinel node is and why it’s important. Ask yourself if your code would error if you removed the sentinel. Is the sentinel a necessary component of your IntList?

  3. What is the downside of not having a size variable and rather just calculate the size each time?

B Level

  1. Starting from the copy of SLList.java provided to you in the lecture code repository, implement the method deleteFirst, which deletes the first element in your SLList. Don’t forget to maintain the three invariants discussed above.

  2. Starting from the copy of SLList.java provided to you in the lecture code repository, implement a second constructor that takes in an array of integers, and creates an SLList with those integers. Again, remember to maintain your invariants.

  3. If the sentinel node was a null node, would it change anything or would the Intlist be able to function?

A level

  1. Do problem 5 from midterm 1 in Kartik’s textbook

  2. Modify the Intlist class so that every time you add a value you “square” the IntList. For example, upon the insertion of 5, the below IntList would transform from:

    1 => 2 to 1 => 1 => 2 => 4 => 5

    and if 7 was added to the latter IntList, it would become

    1 => 1 => 1 => 1 => 2 => 4 => 4 => 16 => 5 => 25 => 7

    Additionally, you are provided the constraint that you can only access the size() function one time during the entire process of adding a node.