Special Methods and Linked Lists¶
A list is a container for a sequence of values. Recall that “sequence” implies an order.
Another way to think about this: A list is a chain of values, or a linked list.
Each value in the list has something after it: the rest of the sequence. (Recursion!)
How do we know when we reach the end of our list? When the rest of the list is None.
class LinkedList:
"""Implements our own recursive list data structure"""
__slots__ = ['_value', '_rest']
def __init__(self, value=None, rest=None):
self._value = value
self._rest = rest
myList = LinkedList(5, LinkedList(3, LinkedList(11)))
type(myList)
__main__.LinkedList
len(myList) # will this work?
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_85918/2939764030.py in <module>
----> 1 len(myList) # will this work?
TypeError: object of type 'LinkedList' has no len()
Special Methods¶
Suppose we want to support the following built-in functions: len()
, str()
, contains()
, add()
, getitem()
, setitem()
, and eq()
. We might also want to add the method append()
for consistency with regular lists.
class LinkedList:
"""Implements our own recursive list data structure"""
__slots__ = ['_value', '_rest']
def __init__(self, value=None, rest=None):
self._value = value
self._rest = rest
# getters/setters
def getRest(self):
return self._rest
def getValue(self):
return self._value
def setValue(self, val):
self._value = val
def __strElements(self):
# helper function for __str__()
if self._rest is None:
return str(self._value)
else:
return str(self._value) + ", " + self._rest.__strElements()
def __str__(self):
return "[" + self.__strElements() + "]"
# repr() function calls __repr__() method
# return value should be a string that is a valid Python
# expression that can be used to recreate the LinkedList
def __repr__(self):
return "LinkedList({}, {})".format(self._value, repr(self._rest))
# len() function calls __len__() method
def __len__(self):
# base case: i'm the last item
if self._rest is None:
return 1
else:
# same as return 1 + self.rest.__len__()
return 1 + len(self._rest)
# in operator calls __contains__() method
def __contains__(self, val):
if self._value == val:
return True
elif self._rest is None:
return False
else:
# same as calling self.__contains__(val)
return val in self._rest
# + operator calls __add__() method
# + operator returns a new instance of LinkedList
def __add__(self, other):
# other is another instance of LinkedList
# if we are the last item in the list
if self._rest is None:
# set _rest to other
self._rest = other
else:
# else, recurse until we reach the last item
self._rest.__add__(other)
return self
# [] list index notation calls __getitem__() method
# index specifies which item we want
def __getitem__(self, index):
# if index is 0, we found the item we need to return
if index == 0:
return self._value
else:
# else we recurse until index reaches 0
# remember that this implicitly calls __getitem__
return self._rest[index - 1]
# [] list index notation also calls __setitem__() method
# index specifies which item we want, val is new value
def __setitem__(self, index, val):
# if index is 0, we found the item we need to update
if index == 0:
self._value = val
else:
# else we recurse until index reaches 0
# remember that this implicitly calls __setitem__
self._rest[index - 1] = val
# == operator calls __eq__() method
# if we want to test two LinkedLists for equality, we test
# if all items are the same
# other is another LinkedList
def __eq__(self, other):
# If both lists are empty
if self._rest is None and other.getRest() is None:
return True
# If both lists are not empty, then value of current list elements
# must match, and same should be recursively true for
# rest of the list
elif self._rest is not None and other.getRest() is not None :
return self._value == other.getValue() and self._rest == other.getRest()
# If we reach here, then one of the lists is empty and other is not
return False
# append is not a special method, but it is a method
# that we know and love from the Python list class.
# unlike __add__, we do not return a new LinkedList instance
def append(self, val):
# if am at the list item
if self._rest is None:
# add a new LinkedList to the end
self._rest = LinkedList(val)
else:
# else recurse until we find the end
self._rest.append(val)
myList = LinkedList(4)
print(myList)
[4]
myList = LinkedList(5, myList)
print(myList)
[5, 4]
myList = LinkedList(5, LinkedList(3, LinkedList(11)))
print(myList) # testing __str__
[5, 3, 11]
myList # testing __repr__
LinkedList(5, LinkedList(3, LinkedList(11, None)))
len(myList) # testing __len__
3
1 in myList # testing __contains__
False
11 in myList
True
myList2 = LinkedList(4, LinkedList(6, LinkedList(10)))
print(myList2)
[4, 6, 10]
print(myList)
[5, 3, 11]
myList3 = myList + myList2 # testing __add__
print(myList3)
[5, 3, 11, 4, 6, 10]
len(myList3)
6
print(myList3[0]) # testing __getitem__
5
myList3[0]=7 # testing __setitem__
print(myList3)
[7, 3, 11, 4, 6, 10]
myList4 = LinkedList(5, LinkedList(3, LinkedList(11, LinkedList(13))))
myList5 = LinkedList(5, LinkedList(3, LinkedList(11)))
myList6 = LinkedList(5, LinkedList(3, LinkedList(11)))
print(myList4)
print(myList5)
print(myList6)
[5, 3, 11, 13]
[5, 3, 11]
[5, 3, 11]
myList4 == myList5 # testing __eq__
False
myList5 == myList6
True
myList6.append(20) # testing append
myList = LinkedList(5, LinkedList(3, LinkedList(11)))
for item in myList:
print(item)
5
3
11
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_85918/858435769.py in <module>
----> 1 for item in myList:
2 print(item)
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_85918/1700578427.py in __getitem__(self, index)
74 # else we recurse until index reaches 0
75 # remember that this implicitly calls __getitem__
---> 76 return self._rest[index - 1]
77
78 # [] list index notation also calls __setitem__() method
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_85918/1700578427.py in __getitem__(self, index)
74 # else we recurse until index reaches 0
75 # remember that this implicitly calls __getitem__
---> 76 return self._rest[index - 1]
77
78 # [] list index notation also calls __setitem__() method
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_85918/1700578427.py in __getitem__(self, index)
74 # else we recurse until index reaches 0
75 # remember that this implicitly calls __getitem__
---> 76 return self._rest[index - 1]
77
78 # [] list index notation also calls __setitem__() method
TypeError: 'NoneType' object is not subscriptable