Lab 4: Linked Lists
- Due: Wednesday 09/30 @ 11:59pm
- Points: 1
- Download: lab04.zip
Attendance
You need to submit the lab problems in addition to attending to get credit for lab. Students in mega lab only need to submit the lab problems. Preceptor interviews are highly recommended but not required for lab submissions.
If you are in regular lab, your TA will come around during lab to check you in. If you didn't attend for a good reason (such as being sick), fill out this form (within 2 weeks of your lab): attendance form.
Getting Started Videos
These videos may provide some helpful direction for tackling the coding problems on this assignment.
To see these videos, you should be logged into your berkeley.edu email.
Required Questions
Python 3.12 or newer is required for the remainder of the course. Please upgrade if you're using Python 3.11 (or older). See the Install Python section of Lab 0 for instructions.
Review
This drop-down provides an overview of working with linked lists.
Linked Lists
A linked list is a sequence type that is more efficient than a built-in list for
certain operations, such as inserting a value at the front of a long list.
Two linked lists that differ only in their first item can share the same rest.
type LinkedList[T] = Link[T] | tuple[()]
@dataclass
class Link[T]:
first: T
rest: LinkedList[T] = () # rest defaults to an empty linked list
A Link is a non-empty LinkedList, and so has a first and rest. The
rest of a Link should always be a linked list: either another Link or
().
You can make a LinkedList by calling Link.
Link(4)makes a linked list of length 1 containing 4.Link(4, s)makes a linked list that starts with 4 followed by the elements of linked lists.
Printing a Link shows its elements within parentheses: print(Link(3, Link(4))) displays (3 4).
The second element of s is s.rest.first and the third is
s.rest.rest.first.
This linked list visualizer is a great way to understand the structure of a complex linked list.
To check whether a linked list s is empty, use isinstance(s, Link). The VS
Code type checker infers that s is a Link and therefore has first and
rest attributes only when isinstance(s, Link) is true:
def sum_link(s: LinkedList[int]) -> int:
"""Return the sum of the items in linked list s."""
if not isinstance(s, Link):
return 0
return s.first + sum_link(s.rest)
The type LinkedList[int] describes a linked list whose elements are all
integers. A type variable, such as T in def extend[T](s: LinkedList[T], t: LinkedList[T]) -> LinkedList[T], represents any type and expresses that the
inputs and output to this function all contain the same type of items.
Linked Lists
Q1: WWPD: Linked Lists
Read over the Link class in link.py. Make sure you understand the
doctests.
Predict what Python will display when the following lines are entered into an
interactive session in which the Link class has already been defined, then
unlock the test to check your answers. Type ERROR if it errors:
python3 -m pytest -k link_wwpd --unlockUnlocking Examples
>>> from lab04 import Link
>>> a = Link(1000)
>>> a.first
______
>>> a.rest # the empty linked list is ()
______
>>> print(a) # printing a linked list shows all its items in parentheses
______
>>> b = Link(1, Link(2, Link(3)))
>>> print(b)
______
>>> b.first
______
>>> b.rest.rest.first
______
>>> b.rest.rest.rest
______
>>> skip = Link(9001, b.rest.rest)
>>> skip.first
______
>>> skip.rest.first
______
>>> skip.rest.rest
______
>>> b.rest.first
______
>>> print(Link(4, b))
______
>>> c = Link(Link(1), Link(2))
>>> c.first.first
______
>>> c.first.rest
______
>>> c.rest.first
______
>>> print(c)
______
>>> d = Link(5, Link(6, Link(7)))
>>> print(d)
______
>>> print(d.rest.rest)
______
>>> print(Link(8, Link(9, d.rest)))
______
>>> print(Link(10, Link(d.rest.rest, d.rest)))
______
>>> print(Link(5, 6))
______
If you get stuck, try drawing a linked list on a piece of paper or running
python3 -i link.pyand then evaluating different expressions.
Q2: Change
Write a function that takes in a linked list s and two values, old and
new. The function returns a copy of s but with all instances of old
replaced with new.
Hint: Use recursion. It's easier in this case!
def change[T](s: LinkedList[T], old: T, new: T) -> LinkedList[T]:
"""Returns a linked list matching s but with all instances of old (if any)
replaced by new.
>>> s = Link(1, Link(2, Link(3)))
>>> t = change(s, 3, 1)
>>> print(t)
(1 2 1)
>>> u = change(t, 1, 2)
>>> print(u)
(2 2 2)
>>> v = change(u, 5, 1)
>>> print(v)
(2 2 2)
>>> print(s) # The original linked list s is unchanged.
(1 2 3)
"""
"*** YOUR CODE HERE ***" # replace the code below
return ()
python3 -m pytest -k changeQ3: Without One
Implement without, which takes a linked list s and a non-negative integer
index i. It returns a linked list with all of the elements of s except for the
one at index i. (Assume s.first is the element at index 0.)
The original linked list s should not be changed.
If i is greater than or equal to the length of s, then without should return a linked list with the same contents as s.
Hint: Use recursion. It's easier in this case!
def without(s: LinkedList, i: int) -> LinkedList:
"""Return a linked list like s but without the element at index i.
>>> s = Link(3, Link(5, Link(7, Link(9))))
>>> print(without(s, 0))
(5 7 9)
>>> print(without(s, 2))
(3 5 9)
>>> print(without(s, 4)) # There is no index 4, so all of s is retained.
(3 5 7 9)
>>> print(s) # The original linked list s is unchanged.
(3 5 7 9)
>>> without((), 0)
()
"""
"*** YOUR CODE HERE ***" # replace the code below
return ()
python3 -m pytest -k withoutQ4: Print Every Other
Implement print_every_other, which takes a linked list s and prints every
other element of s, starting with the first, each on its own line. It prints
nothing if s is empty. It returns None, which should happen automatically; you don't need a return statement.
def print_every_other(s: LinkedList) -> None:
"""Print every other element of linked list s, starting with the first.
>>> sentence = Link('I', Link('have', Link('love', Link('for', Link('Oski')))))
>>> print_every_other(sentence)
I
love
Oski
>>> print_every_other(Link(1, Link(2, Link(3, Link(4)))))
1
3
>>> print_every_other(Link(5))
5
>>> print_every_other(())
"""
"*** YOUR CODE HERE ***"
python3 -m pytest -k print_every_otherQ5: Store Digits
Write a function store_digits that takes in an integer n and returns a linked
list containing the digits of n in the same order (from left to right).
Important: Do not use any string manipulation functions, such as
strorreversed.
def store_digits(n: int) -> LinkedList[int]:
"""Stores the digits of a positive number n in a linked list.
>>> s = store_digits(1)
>>> s
Link(first=1, rest=())
>>> print(store_digits(2345))
(2 3 4 5)
>>> print(store_digits(876))
(8 7 6)
>>> print(store_digits(2450))
(2 4 5 0)
>>> print(store_digits(20105))
(2 0 1 0 5)
>>> # a check that you do not use str or reversed
>>> import inspect, ast
>>> [n.id for n in ast.walk(ast.parse(inspect.getsource(store_digits)))
... if isinstance(n, ast.Name) and n.id in ('str', 'reversed')]
[]
"""
"*** YOUR CODE HERE ***" # replace the code below
return ()
python3 -m pytest -k store_digitsSubmit Assignment
Submit this assignment by running Provenance: Prepare Submission Bundle from the VS Code command palette and uploading the resulting zip to Gradescope. The zip already contains the files you've edited. Lab 00 has detailed instructions.
Your responses to WWPD questions are not submitted, and they do not need to be. Lab credit is based on the code writing questions.
Optional Questions
These questions are optional. If you don't complete them, you will still receive credit for this assignment. They are great practice, so do them anyway!
Q6: Linked Sum
This was Question 6 on the Summer 2016 61A Midterm.
Implement linked_sum, which takes a linked list of positive integers s and a
non-negative integer total. It returns the number of ways that adding up items
in s can sum up to total. Each item can be used more than once.
def linked_sum(s: LinkedList[int], total: int) -> int:
"""Return the number of combinations of elements in s that
sum up to total.
>>> # Four combinations: 1 1 1 1 , 1 1 2 , 1 3 , 2 2
>>> linked_sum(Link(1, Link(2, Link(3, Link(5)))), 4)
4
>>> linked_sum(Link(2, Link(3, Link(5))), 1)
0
>>> # One combination: 2 3
>>> linked_sum(Link(2, Link(4, Link(3))), 5)
1
"""
if ____________________________:
return 1
elif ____________________________:
return 0
else:
with_first = ____________________________
without_first = ____________________________
return ____________________________
python3 -m pytest -k linked_sumQ7: Max Pair Sum
This was Question 4(e) on the Fall 2023 61A Final.
Definition: A pairing of a sequence s is a list of two-element tuples
(called pairs) that contain adjacent elements of s. There must be at least
one element of s between any two pairs in the pairing. The pairs in the
pairing must be in the same order as they are in s.
The sequence [4, 5, 6, 1, 2, 3, 7, 8] has pairings [(6, 1), (3, 7)] and
[(4, 5), (1, 2), (7, 8)] and [] and many others, but the following are not
pairings of that sequence:
[(5, 1), (3, 7)]contains a pair(5, 1), but 5 and 1 are not adjacent in the sequence.[(5, 6), (1, 2)]contains two pairs with no element between them, since 6 is adjacent to 1 in the sequence.[(1, 2), (4, 5)]contains valid pairs, but those pairs are not in the same order as the sequence.
Implement max_pair_sum, which takes a linked list s (either a Link or
()). It returns the largest possible sum of the values in a pairing of s.
def max_pair_sum(s: LinkedList[int]) -> int:
"""Return the largest sum of values in a pairing for a linked list of positive numbers s.
>>> L = Link # Abbreviate Link
>>> max_pair_sum(L(3, L(4, L(5, L(3, L(4, L(5, L(6)))))))) # 4+5 + 5+6
20
>>> max_pair_sum(L(3, L(4, L(5, L(3, L(4, L(5, L(6, L(3))))))))) # 3+4 + 3+4 + 6+3
23
"""
if ____________________________:
return 0
n = ____________________________
if not isinstance(s.rest.rest, Link):
return n
else:
return max(n + max_pair_sum(____________), max_pair_sum(____________))
python3 -m pytest -k max_pair_sum