$ mkdir ~/cs170/labs/lab17 $ cd ~/cs170/labs/lab17
You will get a chance to look over your test results before we get into our main topic for today. Overall, this test average was higher than previous years, by about 10 points. Log into inquire to see comments on your tests.
Today we begin our journey down the
of data structures. You have been using data structures for a while now, even if you might not have realised it. For example, lists are a data structure, just like dictionaries and tuples. Even the objects you have been creating are technically data structures.Linked lists belong to a class known as linear data structures. They store a collection of data in a linear fashion, where access to an arbitrary element is restricted to a traversal through all of the previous elements. This is accomplished by having each node in the structure have a reference to another node of the same type.
A linked list is a sequential collection of data. A singly linked list is a sequential collection of data that can only be traversed in
. This may seem like it might be very restrictive. However, these types of lists are very powerful.Create a file called linked_list.py in your directory. This file will contain everything you do with a linked list. The goal is to flesh this file out over the next couple of days, so that you will have a good linked list definition for future activities and assignments that will be using the linked list class.
In this file, create a class called LinkedList
. This
class needs at least one attribute: head. This attribute
will operate under the invariant that it always points to either the
first element of the list, or None
if the linked list is
currently empty.
Inside this class, create a new class called
Node
. Notice that this class is defined inside of
LinkedList
, meaning to access the Node class you have to
go through the LinkedList class. This inner class should have at
least two attributes: data and next. Data can be anything, but
we are going to operate under the invariant that next either refers to
another instance of Node
, or is None
.
Once you have these classes set up, you should be able to manually construct a linked list like you see in the example below.
>>> my_linked_list = LinkedList() >>> my_linked_list.head = LinkedList.Node() >>> my_linked_list.head.data = "Hello" >>> my_next_node = LinkedList.Node() >>> my_linked_list.head.next = my_next_node >>> my_next_node.data = "World" >>> print(my_linked_list.head.data) Hello >>> print(my_linked_list.head.next.data) World
Notice that my constructor for both a Node and the LinkedList don't take any parameters. you need to define default values for all of the attributes, so that the attributes definitely exist, but contain their associated default values.
The inner class Node can only be accessed through the LinkedList class. This means you have to specify the name of the enclosing class to get access to the inner class definition.
We can string together an arbitrary number of attribute accesses, without needing to specify an intermediate variable to hold the value of the first access. This is because the "." operator is left-associative:
my_linked_list.head.next.data == (((my_linked_list.head).next).data)Which is also equivalent to:
head = my_linked_list.head heads_next = head.next heads_next_data = heads_next.data
That being said, a common cause of issues with getting an attribute error or None type error is by abusing this ability to string many attribute accesses together as possible. It will be much easier to debug if you create intermediate variables for statements that need more than one additional level of access.
As you may have noticed, our singly linked list is very similar to a doubly linked list. The only difference is that nodes should have a previous reference as well. We already know of one mechanism that will make creating these new lists and nodes much easier: inheritance!
Create a DoublyLinkedList
class in the same file. This
class should inherit your LinkedList
class. No real
changes need to be made to the DoublyLinkedList class at this
point. The only thing that needs to be updated is the Node for a
DoublyLinkedList. This Node should inherit LinkedList's node, and
simply add a prev reference.
>>> my_doubly_list = DoublyLinkedList() >>> my_doubly_list.head = DoublyLinkedList.Node() >>> my_doubly_list.head.data = "Hello" >>> my_doubly_list.head.next = DoublyLinkedList.Node() >>> my_doubly_list.head.next.prev = my_doubly_list.head >>> my_doubly_list.head.next.data = "World" >>> print(my_doubly_list.head.data) Hello >>> print(my_doubly_list.head.next.data) World >>> print(my_doubly_list.head.next.prev.data) Hello
It is going to get very tiring very quickly if we have to build our linked lists manually like in the examples above. Instead of doing that, we should have a functionality that can just add things to our list. However, testing this will also be a pain, unless we write a method that will also print our list out.
In your linked_list.py file, you are going to add at
least two methods to your LinkedList
class. The
first is a method called append
, which takes a piece of
data and adds a new node with that data to the end of the list.
The other method you need to write is called __str__
,
which returns a string that will allow us to print the list a little
more easily.
Make sure you test these functions very carefully. What are the border cases you need to test?
>>> my_linked_list = LinkedList() >>> my_linked_list.append("Hello") >>> my_linked_list.append("World") >>> print(my_linked_list) head -> Hello -> World -> None
You need to create a new node inside of your insert method for
the data that gets passed in. You can access the
Node
class from within LinkedList
by
using the self reference:
new_node = self.Node()The data for this new node should be whatever got passed to insert as the data parameter.
If the list is currently empty, you can simply set the head reference to the newly created node. If it is not empty, you need to find the node at the end of the list. The node at the end of the list is the first node which has "None" as its next reference.
To add something to the list, you change the next reference of the last node in the list to the new node.
There are two ways to go about the __str__
method:
a loop within the method, or by writing a recursive
__str__
method for your Node. Either is
acceptable, as long as your functions produce the correct output.
If you did the previous challenge, then you should also modify these methods for your doubly linked list class. The edge cases for this linked list are a little bit more complicated, but are not too terribly difficult.
When you have finished, create a tar file of your lab17
directory. To create a tar file, execute the
following commands:
cd ~/cs170/labs tar czvf lab17.tgz lab17/
To submit your activity, go
to inquire.roanoke.edu. You
should see an available assignment called Lab Assignment
17
. Make sure you include a header
listing the authors of the file.