Code from this lecture available at https://github.com/Berkeley-CS61B/lectureCode-fa20/tree/master/lists2.
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
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
IntNode with an
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
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
Public vs. Private We want users to modify our list via
only, and not by directly modifying
first. We can prevent other users from
doing so by setting our variable access to
first; prevents code in other classes from accessing and modifying
(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
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
SLList, lets simply cache the size of our list as an instance
variable! Note that we could not do this before with out
Empty Lists With an
SLList, we can now represent an empty list. We simply
0. However, we have introduced some bugs;
first is now
null, any method that tries to access a
first.item) will return a
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
SLList with a sentinel node has at least the following
- The sentinel reference always points to a sentinel node.
- The front item (if it exists), is always at sentinel.next.item.
- The size variable is always the total number of items that have been added.
Complete the exercises from the online textbook.
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?
What is the downside of not having a size variable and rather just calculate the size each time?
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.
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.
If the sentinel node was a null node, would it change anything or would the Intlist be able to function?
Do problem 5 from midterm 1 in Kartik’s textbook
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.