Python lists are implemented as a "dynamic array", which isn't optimal for all use cases.
😠Inserting an element is slow, especially near front of list:
😠Plus inserting too many elements can require re-creating the entire list in memory, if it exceeds the pre-allocated memory.
A linked list is a chain of objects where each object holds a value and a reference to the next link. The list ends when the final reference is empty.
Linked lists require more space but provide faster insertion.
class Link:
empty = ()
def __init__(self, first, rest=empty):
self.first = first
self.rest = rest
How would we use that?
ll = Link("A", Link("B", Link("C")))
class Link:
"""A linked list."""
empty = ()
def __init__(self, first, rest=empty):
assert rest is Link.empty or isinstance(rest, Link)
self.first = first
self.rest = rest
def __repr__(self):
if self.rest:
rest_repr = ', ' + repr(self.rest)
else:
rest_repr = ''
return 'Link(' + repr(self.first) + rest_repr + ')'
def __str__(self):
string = '<'
while self.rest is not Link.empty:
string += str(self.first) + ' '
self = self.rest
return string + str(self.first) + '>'
It's built-in to code.cs61a.org and you can draw() any Link.
Similar to [x for x in range(3, 6)]
def range_link(start, end):
"""Return a Link containing consecutive integers
from START to END, not including END.
>>> range_link(3, 6)
Link(3, Link(4, Link(5)))
"""
if start >= end:
return Link.empty
return Link(start, range_link(start + 1, end))
Similar to [f(x) for x in lst]
def map_link(f, ll):
"""Return a Link that contains f(x) for each x in Link LL.
>>> square = lambda x: x * x
>>> map_link(square, range_link(3, 6))
Link(9, Link(16, Link(25)))
"""
Similar to [f(x) for x in lst]
def map_link(f, ll):
"""Return a Link that contains f(x) for each x in Link LL.
>>> square = lambda x: x * x
>>> map_link(square, range_link(3, 6))
Link(9, Link(16, Link(25)))
"""
if ll is Link.empty:
return Link.empty
return Link(f(ll.first), map_link(f, ll.rest))
Similar to [x for x in lst if f(x)]
def filter_link(f, ll):
"""Return a Link that contains only the elements x of Link LL
for which f(x) is a true value.
>>> is_odd = lambda x: x % 2 == 1
>>> filter_link(is_odd, range_link(3, 6))
Link(3, Link(5))
"""
Similar to [x for x in lst if f(x)]
def filter_link(f, ll):
"""Return a Link that contains only the elements x of Link LL
for which f(x) is a true value.
>>> is_odd = lambda x: x % 2 == 1
>>> filter_link(is_odd, range_link(3, 6))
Link(3, Link(5))
"""
if ll is Link.empty:
return Link.empty
elif f(ll.first):
return Link(ll.first, filter_link(f, ll.rest))
return filter_link(f, ll.rest)
Attribute assignments can change first and rest attributes of a Link.
s = Link("A", Link("B", Link("C")))
s.first = "Hi"
s.rest.first = "Hola"
s.rest.rest.first = "Oi"
The rest of a linked list can contain the linked list as a sub-list.
s = Link("A", Link("B", Link("C")))
t = s.rest
t.rest = s
s.first
s.rest.rest.rest.rest.rest.first
def insert_front(linked_list, new_val):
"""Inserts NEW_VAL in front of LINKED_LIST,
returning new linked list.
>>> ll = Link(1, Link(3, Link(5)))
>>> insert_front(ll, 0)
Link(0, Link(1, Link(3, Link(5))))
"""
def insert_front(linked_list, new_val):
"""Inserts NEW_VAL in front of LINKED_LIST,
returning new linked list.
>>> ll = Link(1, Link(3, Link(5)))
>>> insert_front(ll, 0)
Link(0, Link(1, Link(3, Link(5))))
"""
return Link(new_val, linked_list)
def add(ordered_list, new_val):
"""Add NEW_VAL to ORDERED_LIST, returning modified ORDERED_LIST.
>>> s = Link(1, Link(3, Link(5)))
>>> add(s, 0)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 3)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 4)
Link(0, Link(1, Link(3, Link(4, Link(5)))))
>>> add(s, 6)
Link(0, Link(1, Link(3, Link(4, Link(5, Link(6))))))
"""
if new_val < ordered_list.first:
elif new_val > ordered_list.first and ordered_list.rest is Link.empty:
elif new_val > ordered_list.first:
return ordered_list
def add(ordered_list, new_val):
"""Add NEW_VAL to ORDERED_LIST, returning modified ORDERED_LIST.
>>> s = Link(1, Link(3, Link(5)))
>>> add(s, 0)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 3)
Link(0, Link(1, Link(3, Link(5))))
>>> add(s, 4)
Link(0, Link(1, Link(3, Link(4, Link(5)))))
>>> add(s, 6)
Link(0, Link(1, Link(3, Link(4, Link(5, Link(6))))))
"""
if new_val < ordered_list.first:
original_first = ordered_list.first
ordered_list.first = new_val
ordered_list.rest = Link(original_first, ordered_list.rest)
elif new_val > ordered_list.first and ordered_list.rest is Link.empty:
ordered_list.rest = Link(new_val)
elif new_val > ordered_list.first:
add(ordered_list.rest, new_val)
return ordered_list
The challenge:
| Version | 10,000 runs | 100,000 runs |
|---|---|---|
| Python list | 2.6 seconds | 37 seconds |
| Link | 0.01 seconds | 0.1 |
Try it yourself on your local machine (Legit Python!): warandpeace.py
Why are Tree and Link considered recursive objects?
Each type of object contains references to the same type of object.
Tree can contain additional instances of Tree, in the branches variable.
Link can contain an additional instance of Link, in the rest variable.
Both classes lend themselves to recursive algorithms. Generally:
Tree: The base case is when is_leaf() is true;branches.
Link: The base case is when the rest is empty;rest.