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:
- 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.
Exercises
C Level
-
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?
B Level
-
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?
A level
-
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.