Browse files online

## 0 Introduction¶

In these exercises, you will be implementing several versions of a LinkedList, improving its performances with each new version.

### References¶

NOTE: What the book calls UnorderedList, in this lab is just called LinkedList. May look confusing, but in the wild you will never find code called UnorderedList so let’s get rid of the weird name right now!

### What to do¶

• unzip exercises in a folder, you should get something like this:

linked-lists
jupman.py
sciprog.py

• open the editor of your choice (for example Visual Studio Code, Spyder or PyCharme), you will edit the files ending in .py files

### 0.1 Initialization¶

A LinkedList for us is a linked list starting with a pointer called head that points to the first Node (if the list is empty the pointer points to None). Think of the list as a chain where each Node can contain some data retriavable with Node.get_data() method and you can access one Node at a time by calling the method Node.get_next() on each node.

Let’s see how a LinkedList should behave:

[2]:

from linked_list_sol import *

[3]:

ll = LinkedList()


At the beginning the LinkedList is empty:

[4]:

print(ll)

LinkedList:


NOTE: print calls __str__ method, which in our implementation was overridden to produce a nice string you’ve just seen. Still, we did not override __repr__ method which is the default one used by Jupyter when displaying on object without using print, so if you omit it you won’t get nice display:

[5]:

ll

[5]:

<linked_list_sol.LinkedList at 0x7f01b0105fd0>


### 0.2 Growing¶

Main way to grow a LinkedList is by using the .add method, which executes in costant time $$O(1)$$:

[6]:

ll.add('a')


Internally, each time you call .add a new Node object is created which will hold the actual data that you are passing. In this implementation, users of the class are supposed to never get instances of Node, they will just be able to see the actual data contained in the Nodes:

[7]:

print(ll)

LinkedList: a


Notice that .add actually inserts nodes at the beginning :

[8]:

ll.add('b')

[9]:

print(ll)

LinkedList: b,a

[10]:

ll.add('c')

[11]:

print(ll)

LinkedList: c,b,a


Our basic LinkedList instance will only hold a pointer to the first Node of the chain (such pointer is called _next). When you add an element:

1. a new Node is created

2. provided data is stored inside new node

3. the new node _next field is set to point to current first Node

4. the new node becomes the first node of the LinkedList, by setting LinkedList._next to new node

### 0.3 Visiting¶

Any method that needs to visit the LinkedList will have to start from the first Node pointed by LinkedList._next and then follow the chain of _next links from one Node to the next one. This is why the data structure is called ‘linked’. While insertion at the beginning is very fast, retrieving an element at arbitrary position requires a linear scan which in worst case costs $$O(n)$$.

## 1 v1: a slow LinkedList¶

Implement the missing methods in linked_list.py, in the order they are presented in the skeleton. Before implementing, read carefully all this point 1) and all its subsections (1.a,b and c)

### 1.a) Testing¶

You will have two files to look at, the code in linked_list.py and the test code in a separate linked_list_test.py file:

• linked_list.py

• linked_list_test.py

You can run tests with this shell command:

python3 -m unittest linked_list_test


Let’s look inside the first lines of linked_list_test.py code, you will see a structure like this:

from linked_list import *
import unittest

#####  etc #####

def test_01_init(self):
#####  etc #####

#####  etc #####

#####  etc  #####


Note:

• the test automatically imports everything from first module linked_list, so when you run the test, it automatically loads the file you will be working on.) :

from linked_list import *

• there is a base class for testing called LinkedListTest

• there are many classes for testing individual methods, each class inherits from LinkedListTest

• You will be writing several versions of the linked list. For the first one, you won’t need myAssert

• This time there is not much Python code to find around, you should rely solely on theory from the slides and book, method definitions and your intuition

### 1.b) Differences with the book¶

• We don’t assume the list has all different values

• We used more pythonic names for properties and methods, so for example private attribute Node.data was renamed to Node._data and accessor method Node.getData() was renamed to Node.get_data(). There are nicer ways to handle these kind of getters/setters pairs called ‘properties’ but we won’t address them here.

• In boundary cases like removing a non-existing element we prefer to raise an LookupError with the command

raise LookupError("Some error occurred!")


In general, this is the behaviour you also find in regular Python lists.

WARNING: Methods of the class LinkedList are never supposed to return instances of Node.

If you see them returned in the tests, then you are making some mistake. Users of LinkedList are should only be able to get access to items inside the Node data fields.

WARNING: Do not use a Python list to hold data inside the data structure

Differently from the CappedStack exercise, here you can only use Node class. Each Node in the _data field can hold only one element which is provided by the user of the class, and we don’t care about the type of the value the user gives us (so it can be an int, a float, a string, or even a Python list !)

VII COMMANDMENT: You shall also draw lists on paper

Helps a lot avoiding mistakes!

VIII COMMANDMENT You shall never ever reassing self !

Never write horrors such as this:

[12]:

class LinkedList:
def my_method(self):
self = {'my_field':666}    # SIN


VI COMMANDMENT: You shall use return command only if you see written RETURN in function description!

If there is no return in the description, it is intended to return None. In this case you don’t even need to write return None, as Python will do it implicitly for you.

## 2 v2 faster size¶

### 2.1 Save a copy of your work¶

You already wrote a lot of code, and you don’t want to lose it, right? Since we are going to make many modifications, when you reach a point when the code does something useful, it is good practice to save a copy of what you have done somewhere, so if you later screw up something, you can always restore the copy.

• Copy the whole folder linked-lists in a new folder linked-lists-v1

• Add also in the copied folder a separate README.txt file, writing inside the version (like 1.0), the date, and a description of the main features you implemented (for example “Simple linked list, not particularly performant”).

• Backing up the work is a form of the so-called versioning : there are much better ways to do it (like using git) but we don’t address them here.

WARNING: DO NOT SKIP THIS STEP!

No matter how smart you are, you will fail, and a backup may be the only way out.

WARNING: HAVE YOU READ WHAT I JUST WROTE ????

Just. Copy. The. Folder.

### 2.2. Improve size¶

Once you saved your precious work in the copy folder linked-lists-v1, you can now more freely improve the current folder linked-lists, being sure your previous efforts are not going to get lost!

As a first step, in linked-lists/linked_list.py implement a size() method that works in O(1). To make this work without going through the whole list each time, we will need a new _size field that keeps track of the size. When the list is mutated with methods like add, append, etc you will also need to update the _size field accordingly. Proceed like this:

2.2.1) add a new field _size in the class constructor and initialize it to zero

2.2.2) modify the size() method to just return the _size field.

2.2.3) The data structure starts to be complex, and we need better testing. If you look at the tests, very often there are lines of code like self.assertEquals(to_py(ul), ['a', 'b']) in the test_add method:

def test_add(self):
self.myAssert(ul, [])
self.assertEquals(to_py(ul), ['b'])
self.assertEquals(to_py(ul), ['a', 'b'])


Last line checks our linked list ul contains a sequence of linked nodes that once transformed to a python list actually equals ['a', 'b']. Since in the new implementation we are going to mutate _size field a lot, it could be smart to also check that ul.size() equals len(["a", "b"]). Repeating this check in every test method could be quite verbose. Instead, we can do a smarter thing, and develop in the LinkedListTest class a new assertion method on our own:

If you noticed, there is a method myAssert in LinkedListTest class (in the current linked-lists/linked_list_test.py file) which we never used so far, which performs a more thourough check:

class LinkedListTest(unittest.TestCase):

""" Checks provided linked_list can be represented as the given python_list. Since v2.
"""
# check this new invariant about the size


WARNING: method myAssert must not start with test, otherwise unittest will run it as a test!

2.3.4) Now, how to use this powerful new myAssert method? In the test class, just replace every occurence of

self.assertEquals(to_py(ul), ['a', 'b'])


into calls like this:

self.myAssert(ul, ['a', 'b'])


WARNING: Notice the to_py(  ) enclosing ul is gone.

2.3.5) Actually update _size in the various methods where data is mutated, like add, insert, etc.

2.3.6) Run the tests and hope for the best ;-)

python3 -m unittest linked_list_test


## 3 v3 Faster append¶

We are now better equipped to make further improvements. Once you’re done implementing the above and made sure everything works, you can implement an append method that works in $$O(1)$$ by adding an additional pointer in the data structure that always point at the last node. To further exploit the pointer, you can also add a fast last(self) method that returns the last value in the list. Proceed like this:

### 3.1 Save a copy of your work¶

• Copy the whole folder linked-lists in a new folder linked-lists-v2

• Add also in the copied folder a separate README.txt file, writing inside the version (like 2.0), the date, and a description of the main features you implemented (for example “Simple linked list, not particularly performant”).

WARNING: DO NOT SKIP THIS STEP!

### 3.2 add _last field¶

Work on linked_list.py and simply add an additional pointer called _last in the constructor.

Copy this method last into the class. Just copy it, don’t implement it for now.

def last(self):
""" Returns the last element in the list, in O(1).

- If list is empty, raises a ValueError. Since v3.
"""
raise ValueError("TODO implement me!")


### 3.4 test driven development¶

Let’s do some so-called test driven development, that is, first we write the tests, then we write the implementation.

WARNING: During the exam you may be asked to write tests, so don’t skip writing them now !!

### 3.4.1 LastTest¶

Create a class LastTest which inherits from LinkedListTest, and add this method Implement a test for last() method, by adding this to LinkedListTest class:

def test_01_last(self):
raise Exception("TODO IMPLEMENT ME !")


In the method, create a list and add elements using only calls to add method and checks using the myAssert method. When done, ask your instructor if the test is correct (or look at the proposed solution), it is important you get it right otherwise you won’t be able to properly test your code.

### 3.4.2 improve myAssert¶

You already have a test for the append() method, but, how can you be sure the _last pointer is updated correctly throughout the code? When you implemented the fast size() method you wrote some invariant in the myAssert method. We can do the same this time, too. Find the invariant and add the corresponding check to the myAssert method. When done, ask your instructor if the invariant is correct (or look at the proposed solution): it is important you get it right otherwise you won’t be able to properly test your code.

### 3.5 update methods that mutate the LinkedList¶

Update the methods that mutate the data structure (add, insert, remove …) so they keep _last pointed to last element. If the list is empty, _last will point to None. Take particular care of corner cases such as empty list and one element list.

### 3.6 Run tests¶

Cross your fingers and run the tests!

python3 -m unittest linked_list_test


## 4 v4 Go bidirectional¶

Our list so far has links that allow us to traverse it fast in one direction. But what if we want fast traversal in the reverse direction, from last to first element? What if we want a pop() that works in $$O(1)$$ ? To speed up these operations we could add backward links to each Node. Note no solution is provided for this part (yet).

Proceed in the following way:

Once you’re done with previous points, save the version you have in a folder linked-list-v3 somewhere adding in the README.txt comments about the improvements done so far, the version number (like 3.0) and the date. Then start working on a new copy.

### 4.3 Better str¶

Improve __str__ method so it shows presence or absence of links, along with the size of the list (note you might need to adapt the test for str method):

• next pointers presence must be represented with > character , absence with * character. They must be put after the item representation.

• prev pointers presence must be represented with < character , absence with * character. They must be put befor the item representation.

For example, for the list ['a','b','c'], you would have the following representation:

LinkedList(size=3):*a><b><c*


As a special case for empty list you should print the following:

LinkedList(size=0):**


Other examples of proper lists, with 3, 2, and 1 element can be:

LinkedList(size=3):*a><b><c*


This new __str__ method should help you to spot broken lists like the following, were some pointers are not correct:

Broken list, all prev pointers are missing:

Broken list, size = 3 but shows only one element with next pointer set to None:

Broken list, first backward pointer points to something other than None


Update the LinkedList add method to take into account you now have backlinks. Take particular care for the boundary cases when the list is empty, has one element, or for nodes at the head and at the tail of the list.

Implement to_python_reversed method with a linear scan by using the newly added backlinks:

def to_python_reversed(self):
""" Returns a regular Python list with the elements in reverse order,
from last to first. Since v3. """
raise Exception("TODO implement me")


Add also this test, and make sure it pass:

def test_to_python_reversed(self):
pr = to_py(ul)
pr.reverse()  # we are reversing pr with Python's 'reverse()' method
self.assertEquals(pr, ul.to_python_reversed())


By using the method to_python_reversed(), add a new invariant to the myAssert method. If implemented correctly, this will surely spot a lot of possible errors in the code.

### 4.7 Modify other methods¶

Modify all other methods that mutate the data structure (insert, remove, etc) so that they update the backward links properly.

### 4.8 Run the tests¶

If you wrote meaningful tests and all pass, congrats!

## 5 EqList¶

Open file eqlist.py , which is a simple linked list, and start editing the following methods.

### 5.1 eq¶

Implement the method __eq__ (with TWO underscores before and TWO underscores after ‘eq’) !:

def __eq__(self, other):
""" Returns True if self is equal to other, that is, if all the data elements in the respective
nodes are the same. Otherwise, return False.

NOTE: compares the *data* in the nodes, NOT the nodes themselves !
"""


Testing: python -m unittest eqlist_test.EqTest

### 5.2 remsub¶

Implement the method remsub:

def remsub(self, rem):
""" Removes the first elements found in this LinkedList that match subsequence rem
Parameter rem is the subsequence to eliminate, which is also a LinkedList.

Examples:
aabca  remsub ac  =  aba
aabca  remsub cxa =  aaba  # when we find a never matching character in rem like 'x' here,
the rest of rem after 'x' is not considered.
aabca  remsub ba  =  aac
aabca  remsub a   =  abca
abcbab remsub bb  =  acab
"""


Testing: python3 -m unittest eqlist_test.RemsubTest

## 6 Cloning¶

Start editing the file cloning.py, which contains a simplified LinkedList.

### 6.1 rev¶

Implement the method rev(self) that you find in the skeleton and check provided tests pass.

Testing: python3 -m unittest cloning_test.RevTest

def rev(self):
""" Returns a *new* LinkedList, which is the reversed version of this one.
Function must run in O(n), and try to make this function as fast as possible,
without using python lists or extra fields.
"""

[17]:

from cloning_sol import *

[18]:

lla = LinkedList()
print(lla)

LinkedList: a,b,c

[19]:

llb = lla.rev()

[20]:

print(lla)

LinkedList: a,b,c


### 6.2 clone¶

Implement the method clone(self) that you find in the skeleton and check provided tests pass.

def clone(self):
""" Return a *copy* of this LinkedList in O(n)
NOTE: since we are making a copy, the output of this function
won't contain any Node instance from the original list. Still, new Node
instances will point to the same data items of the original list
"""


Testing: python3 -m unittest cloning_test.CloneTest

Example (for more examples look at the tests):

[21]:

orig = LinkedList()
print(orig)

LinkedList: a,b,c

[22]:

cp = orig.clone()
print(cp)

LinkedList: a,b,c

[23]:

cp.remove('b')

[24]:

print(cp)

LinkedList: a,c

[25]:

print(orig)

LinkedList: a,b,c


### 6.3 Slice¶

Implement the method slice:

def slice(self, start, end):
""" RETURN a NEW LinkedList created by copying nodes of this list
from index start INCLUDED to index end EXCLUDED

- if start is greater or equal than end, returns an empty LinkedList
- if start is greater than available nodes, returns an empty LinkedList
- if end is greater than the available nodes, copies all items until the tail without errors
- if start index is negative, raises ValueError
- if end index is negative, raises ValueError

- IMPORTANT: All nodes in the returned LinkedList MUST be NEW
- DO *NOT* modify original linked list
- DO *NOT* add an extra size field
- MUST execute in O(n), where n is the size of the list

"""


Testing: python3 -m unittest cloning_test.SliceTest

Example:

[26]:

from cloning_sol import *

[27]:

la = LinkedList()

[28]:

print(la)

LinkedList: a,b,c,d,e,f,g


Creates a NEW LinkedList copying nodes from index 2 INCLUDED up to index 5 EXCLUDED:

[29]:

lb = la.slice(2,5)

[30]:

print(lb)

LinkedList: c,d,e


Note original LinkedList is still intact:

[31]:

print(la)

LinkedList: a,b,c,d,e,f,g


#### Special cases¶

If start is greater or equal then end, you get an empty LinkedList:

[32]:

print(la.slice(5,3))

LinkedList:


If start is greater than available nodes, you get an empty LinkedList:

[33]:

print(la.slice(10,15))

LinkedList:


If end is greater than the available nodes, you get a copy of all the nodes until the tail without errors:

[34]:

print(la.slice(3,10))

LinkedList: d,e,f,g


Using negative indexes for either start , end or both raises ValueError:

la.slice(-3,4)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-184-e3380bb66e77> in <module>()
----> 1 la.slice(-3,4)

63
64         if start < 0:
---> 65             raise ValueError('Negative values for start are not supported! %s ' % start)
66         if end < 0:
67             raise ValueError('Negative values for end are not supported: %s' % end)

ValueError: Negative values for start are not supported! -3

la.slice(1,-2)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-185-8e09ec468c30> in <module>()
----> 1 la.slice(1,-2)

65             raise ValueError('Negative values for start are not supported! %s ' % start)
66         if end < 0:
---> 67             raise ValueError('Negative values for end are not supported: %s' % end)
68

ValueError: Negative values for end are not supported: -2


## 7 More exercises¶

Start editing the file more.py, which contains a simplified LinkedList.

### 7.1 occurrences¶

Implement this method:

def occurrences(self, item):
"""
Returns the number of occurrences of item in the list.

- MUST execute in O(n) where 'n' is the length of the list.
"""


Testing: python3 -m unittest more_test.CloneTest

**Examples: **

[36]:

from more_sol import *

print(ul)

LinkedList: a,b,c,a

[37]:

print(ul.occurrences('a'))

2

[38]:

print(ul.occurrences('c'))

1

[39]:

print(ul.occurrences('z'))

0


### 7.2 shrink¶

Implement this method in LinkedList class:

def shrink(self):
"""
Removes from this LinkedList all nodes at odd indeces (1, 3, 5, ...),
supposing that the first node has index zero, the second node
has index one, and so on.

'a','b','c','d','e'
a call to shrink will transform the UnorderedList into
'a','c','e'

- MUST execute in O(n) where 'n' is the length of the list.
- Does *not* return anything.
"""
raise Exception("TODO IMPLEMENT ME!")


Testing: python3 -m unittest more_test.ShrinkTest

[40]:

ul = LinkedList()
print(ul)

LinkedList: a,b,c,d,e

[41]:

ul.shrink()
print(ul)

LinkedList: a,c,e


### 7.3 dup_first¶

Implement the method dup_first:

def dup_first(self):
""" MODIFIES this list by adding a duplicate of first node right after it.

For example, the list 'a','b','c' should become 'a','a','b','c'.
An empty list remains unmodified.

- DOES NOT RETURN ANYTHING !!!

"""

raise Exception("TODO IMPLEMENT ME !")


Testing: python3 -m unittest more_test.DupFirstTest

### 7.4 dup_all¶

Implement the method dup_all:

def dup_all(self):
""" Modifies this list by adding a duplicate of each node right after it.

For example, the list 'a','b','c' should become 'a','a','b','b','c','c'.
An empty list remains unmodified.

- MUST PERFORM IN O(n) WHERE n is the length of the list.

- DOES NOT RETURN ANYTHING !!!
"""

raise Exception("TODO IMPLEMENT ME !")


Testing: python3 -m unittest more_test.DupAllTest

### 7.5 mirror¶

Implement following mirror function. NOTE: the function is external to class LinkedList.

def mirror(lst):
""" Returns a new LinkedList having double the nodes of provided lst
First nodes will have same elements of lst, following nodes will
have the same elements but in reversed order.

For example:

>>> mirror(['a'])

>>> mirror(['a','b'])

>>> mirror(['a','c','b'])

"""
raise Exception("TODO IMPLEMENT ME !")


Testing: python -m unittest more_test.MirrorTest

### 7.6 norep¶

Implement the method norep:

def norep(self):
""" MODIFIES this list by removing all the consecutive
repetitions from it.

- MUST perform in O(n), where n is the list size.

For example, after calling norep:

'a','a','b','c','c','c'   will become  'a','b','c'

'a','a','b','a'   will become   'a','b','a'

"""

raise Exception("TODO IMPLEMENT ME !")


Testing: python -m unittest more_test.NorepTest

### 7.8 find_couple¶

Implement following find_couple method.

def find_couple(self,a,b):
""" Search the list for the first two consecutive elements having data equal to
provided a and b, respectively. If such elements are found, the position
of the first one is returned, otherwise raises LookupError.

- MUST run in O(n), where n is the size of the list.
- Returned index start from 0 included

"""


Testing: python3 -m unittest more_test.FindCoupleTest

### 7.9 swap¶

Implement the method swap:

def swap (self, i, j):
"""
Swap the data of nodes at index i and j. Indeces start from 0 included.
If any of the indeces is out of bounds, rises IndexError.

NOTE: You MUST implement this function with a single scan of the list.

"""


Testing: python3 -m unittest more_test.SwapTest

### 7.10 gaps¶

Given a linked list of size n which only contains integers, a gap is an index i, 0<i<n, such that L[i−1]<L[i]. For the purpose of this exercise, we assume an empy list or a list with one element have zero gaps

Example:

 data:  9 7 6 8 9 2 2 5
index:  0 1 2 3 4 5 6 7


contains three gaps [3,4,7] because:

• number 8 at index 3 is greater than previous number 6 at index 2

• number 9 at index 4 is greater than previous number 8 at index 3

• number 5 at index 7 is greater than previous number 2 at index 6

Implement this method:

def gaps(self):
""" Assuming all the data in the linked list is made by numbers,
finds the gaps in the LinkedList and return them as a Python list.

- we assume empty list and list of one element have zero gaps
- MUST perform in O(n) where n is the length of the list

NOTE: gaps to return are *indeces* , *not* data!!!!
"""


Testing: python3 -m unittest more_test.GapsTest

### 7.11 flatv¶

Suppose a LinkedList only contains integer numbers, say 3,8,8,7,5,8,6,3,9. Implement method flatv which scans the list: when it finds the first occurence of a node which contains a number which is less then the previous one, and the less than successive one, it inserts after the current one another node with the same data as the current one, and exits.

Example:

calling flatv should modify the linked list so that it becomes

Note that it only modifies the first occurrence found 7,5,8 to 7,5,5,8 and the successive sequence 6,3,9 is not altered

Implement this method:

def flatv(self):


Testing: python3 -m unittest more_test.FlatvTest

### 7.12 bubble_sort¶

You will implement bubble sort on a LinkedList.

def bubble_sort(self):
""" Sorts in-place this linked list using the method of bubble sort

- MUST execute in O(n^2) where n is the length of the linked list
"""


As a reference, you can look at this example_bubble implementation below that operates on regular python lists. Basically, you will have to translate the for cycles into two suitable while and use node pointers.

NOTE: this version of the algorithm is inefficient as we do not use j in the inner loop: your linked list implementation can have this inefficiency as well.

Testing: python3 -m unittest more_test.BubbleSortTest

[42]:

def example_bubble(plist):
for j in range(len(plist)):
for i in range(len(plist)):
if i + 1 < len(plist) and plist[i]>plist[i+1]:
temp = plist[i]
plist[i] = plist[i+1]
plist[i+1] = temp

my_list = [23, 34, 55, 32, 7777, 98, 3, 2, 1]
example_bubble(my_list)
print(my_list)


[1, 2, 3, 23, 32, 34, 55, 98, 7777]


### 7.13 merge¶

Implement this method:

def merge(self,l2):
sorted in ASCENDING order, and  RETURN a NEW LinkedList with
all the numbers from this and l2 sorted in DESCENDING order

IMPORTANT 1: *MUST* EXECUTE IN O(n1+n2) TIME where n1 and n2 are
the sizes of this and l2 linked_list, respectively

IMPORTANT 2: *DO NOT* attempt to convert linked lists to
python lists!
"""


Testing: python3 -m unittest more_test.MergeTest

### 7.14 couple_sort¶

Implement this method:

def couple_sort(self):
"""MODIFIES the linked list by considering couples of nodes at *even* indexes
and their successors: if a node data is lower than its successor data, swaps
the nodes *data*.

- ONLY swap *data*, DO NOT change node links.
- if linked list has odd size, simply ignore the exceeding node.
- MUST execute in O(n), where n is the size of the list
"""


Testing: python3 -m unittest more_test.CoupleSortTest

[43]:

from more_sol import *
from more_test import to_ll

[44]:

ll = to_ll([4,3,5,2,6,7,6,3,2,4,5,3,2])

[45]:

print(ll)

LinkedList: 4,3,5,2,6,7,6,3,2,4,5,3,2

[46]:

ll.couple_sort()

[47]:

print(ll)

LinkedList: 3,4,2,5,6,7,3,6,2,4,3,5,2


Notice it sorted each couple at even positions. This particular linked list has odd size (13 items), so last item 2 was not considered.

## 8 Last exercises¶

For these exercises, you also need to take care about attributes size and _last pointer.

### 8.1 rotate¶

✪✪ Now implement this method:

Remember to update also _last and _size attributes !!!

def rotate(self):
""" Rotate the list of 1 element, that is, removes last node and
inserts it as the first one.

- MUST execute in O(n) where n is the length of the list
- Remember to also update _last pointer
- WARNING: DO *NOT* try to convert whole linked list to a python list
- WARNING: DO *NOT* swap node data or create nodes, I want you to
"""


Testing: python3 -m unittest last_test.RotateTest

Example:

[49]:

from last_sol import *

[50]:


print(ll)

LinkedList: a,b,c,d

[51]:

ll.rotate()

[52]:

print(ll)

LinkedList: d,a,b,c


### 8.2 rotaten¶

✪✪✪ Implement this method:

def rotaten(self, k):
""" Rotate k times the linkedlist

- k can range from 0 to any positive integer number (even greater than list size)
- if k < 0 raise ValueError

- MUST execute in O( n-(k%n) ) where n is the length of the list
- WARNING: DO *NOT* call .rotate() k times !!!!
- WARNING: DO *NOT* try to convert whole linked list to a python list
- WARNING: DO *NOT* swap node data or create nodes, I want you to
"""


Testing: python3 -m unittest last_test.RotatenTest

IMPORTANT HINT

The line “MUST execute in O( n-(k%n) ) where n is the length of the list” means that you have to calculate m = k%n, and then only scan first n-m nodes!

Example:

[53]:

ll = LinkedList()
print(ll)

LinkedList: a,b,c,d,e,f,g,h

[54]:

ll.rotaten(0)  # changes nothing

[55]:

print(ll)

LinkedList: a,b,c,d,e,f,g,h

[56]:

ll.rotaten(3)

[57]:

print(ll)

LinkedList: f,g,h,a,b,c,d,e

[58]:

ll.rotaten(8)  # changes nothing

[59]:

print(ll)

LinkedList: f,g,h,a,b,c,d,e

[60]:

ll.rotaten(5)

[61]:

print(ll)

LinkedList: a,b,c,d,e,f,g,h

[62]:

ll.rotaten(11)  # 11 = 8 + 3 , only rotates 3 nodes

[63]:

print(ll)

LinkedList: f,g,h,a,b,c,d,e


IMPORTANT: MUST WORK WITH HUGE k

It should terminate real fast even with a humongous k, if you don’t implement it with the required complexity rest assured this will never print 'Finished!'

[64]:

ll.rotaten(11**18)
print('Finished! Result is', ll)

Finished! Result is LinkedList: e,f,g,h,a,b,c,d


### 8.3 plus_one¶

Supposing the list can only hold digits from 0 to 9 as node data, implement this method:

def plus_one(self):
""" MODIFIES the list by summing one to the integer number it represents
- you are allowed to perform multiple scans of the linked list
- remember the list has a _last pointer

- MUST execute in O(N) where N is the size of the list
- DO *NOT* create new nodes EXCEPT for special cases:
a. empty list ( [] -> [5] )
b. all nines ( [9,9,9] -> [1,0,0,0] )
- DO *NOT* convert the digi list to a python int
- DO *NOT* convert the digi list to a python list
- DO *NOT* reverse the digi list
"""


Test: python3 -m unittest last_test.PlusOneTest

Example:

[65]:

from last_sol import *

print(dl)

LinkedList: 2,9,3,7,9,9

[66]:

dl.last()

[66]:

9

[67]:

dl.plus_one()

[68]:

print(dl)

LinkedList: 2,9,3,8,0,0


## Challenge¶

Ready for the challanges? Go on with Challenge worksheet

[ ]: