202 lines
6.7 KiB
Python
202 lines
6.7 KiB
Python
"""CSC111 Winter 2021 Assignment 1: Linked Lists, Part 1
|
|
|
|
Instructions (READ THIS FIRST!)
|
|
===============================
|
|
|
|
This Python module contains three linked list subclasses corresponding to the three
|
|
moving heuristics described on the assignment handout.
|
|
|
|
You need to complete their implementations by overriding the __contains__ method
|
|
(and only for CountLinkedList, additional methods as required).
|
|
|
|
Copyright and Usage Information
|
|
===============================
|
|
|
|
This file is provided solely for the personal and private use of students
|
|
taking CSC111 at the University of Toronto St. George campus. All forms of
|
|
distribution of this code, whether as given or with any changes, are
|
|
expressly prohibited. For more information on copyright for CSC111 materials,
|
|
please consult our Course Syllabus.
|
|
|
|
This file is Copyright (c) 2021 David Liu and Isaac Waller.
|
|
"""
|
|
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional
|
|
|
|
from a1_linked_list import LinkedList, _Node
|
|
|
|
|
|
################################################################################
|
|
# Heuristic 1 (move to front)
|
|
################################################################################
|
|
class MoveToFrontLinkedList(LinkedList):
|
|
"""A linked list implementation that uses a "move to front" heuristic for searches.
|
|
|
|
Representation Invariants:
|
|
- all items in this linked list are unique
|
|
"""
|
|
def __contains__(self, item: Any) -> bool:
|
|
"""Return whether item is in this linked list.
|
|
|
|
If the item is found, move it to the front of this list.
|
|
|
|
>>> linky = MoveToFrontLinkedList([10, 20, 30, 40, 50, 60])
|
|
>>> linky.__contains__(40)
|
|
True
|
|
>>> linky.to_list()
|
|
[40, 10, 20, 30, 50, 60]
|
|
"""
|
|
if not self._first:
|
|
return False
|
|
if self._first.item == item:
|
|
return True
|
|
cur = self._first
|
|
while cur.next:
|
|
if cur.next.item == item: # found
|
|
cur.next = cur.next.next # delete
|
|
|
|
node = _Node(item, self._first)
|
|
self._first = node # insert to front
|
|
return True
|
|
cur = cur.next
|
|
return False
|
|
|
|
|
|
################################################################################
|
|
# Heuristic 2 (swap)
|
|
################################################################################
|
|
class SwapLinkedList(LinkedList):
|
|
"""A linked list implementation that uses a "swap" heuristic for searches.
|
|
|
|
Representation Invariants:
|
|
- all items in this linked list are unique
|
|
"""
|
|
def __contains__(self, item: Any) -> bool:
|
|
"""Return whether item is in this linked list.
|
|
|
|
If the item is found, swap it with the item before it, if any.
|
|
You may do this by reassigning _Node item or next attributes (or both).
|
|
|
|
>>> linky = SwapLinkedList([10, 20, 30, 40, 50, 60])
|
|
>>> linky.__contains__(40)
|
|
True
|
|
>>> linky.to_list()
|
|
[10, 20, 40, 30, 50, 60]
|
|
"""
|
|
if not self._first:
|
|
return False
|
|
if self._first.item == item:
|
|
return True
|
|
cur = self._first
|
|
while cur.next:
|
|
if cur.next.item == item: # found
|
|
temp = cur.item # swap
|
|
cur.item = cur.next.item
|
|
cur.next.item = temp
|
|
return True
|
|
cur = cur.next
|
|
return False
|
|
|
|
|
|
################################################################################
|
|
# Heuristic 3 (count)
|
|
################################################################################
|
|
# NOTE: this heuristic requires a new kind of _Node that has an additional "count" attribute.
|
|
@dataclass
|
|
class _CountNode(_Node):
|
|
"""A node in a CountLinkedList.
|
|
|
|
Instance Attributes:
|
|
- item: The data stored in this node.
|
|
- next: The next node in the list, if any.
|
|
- access_count: The number of times this node has been accessed (used by the count heuristic)
|
|
"""
|
|
next: Optional[_CountNode] = None
|
|
access_count: int = 0
|
|
|
|
|
|
class CountLinkedList(LinkedList):
|
|
"""A linked list implementation that uses a "swap" heuristic for searches.
|
|
|
|
Representation Invariants:
|
|
- all items in this linked list are unique
|
|
- nodes are sorted in non-increasing order by access_count
|
|
|
|
NOTE: In order to make use of the _CountNode class above, you'll need to override every
|
|
LinkedList method in a1_linked_list.py that creates new _Node objects to create _CountNode
|
|
objects instead. Your code for the overridden methods should be otherwise identical.
|
|
"""
|
|
_first: Optional[_CountNode]
|
|
|
|
def __contains__(self, item: Any) -> bool:
|
|
"""Return whether item is in this linked list.
|
|
|
|
If the item is found, increase its count and reorder the nodes in
|
|
non-increasing count order---see assignment handout for details.
|
|
|
|
>>> linky = CountLinkedList([10, 20, 30, 40, 50, 60])
|
|
>>> linky.__contains__(40)
|
|
True
|
|
>>> linky.to_list()
|
|
[40, 10, 20, 30, 50, 60]
|
|
"""
|
|
if not self._first:
|
|
return False
|
|
if self._first.item == item:
|
|
self._first.access_count += 1
|
|
return True
|
|
count = 0
|
|
cur = self._first
|
|
while cur.next:
|
|
if cur.next.item == item:
|
|
count = cur.next.access_count + 1
|
|
cur.next = cur.next.next # delete node
|
|
break # break to prevent huge nesting
|
|
cur = cur.next
|
|
|
|
if count == 0: # not found
|
|
return False
|
|
|
|
if count > self._first.access_count:
|
|
node = _CountNode(item, self._first, count)
|
|
self._first = node # insert to front
|
|
return True
|
|
|
|
cur = self._first
|
|
while (not cur.next) or cur.next.access_count < count:
|
|
cur = cur.next
|
|
cur.next = _CountNode(item, cur.next.next, count) # insert node
|
|
|
|
def append(self, item: Any) -> None:
|
|
"""Add the given item to the end of this linked list.
|
|
"""
|
|
new_node = _CountNode(item, None, 0)
|
|
|
|
if self._first is None:
|
|
self._first = new_node
|
|
else:
|
|
curr = self._first
|
|
while curr.next is not None:
|
|
curr = curr.next
|
|
|
|
# After the loop, curr is the last node in the LinkedList.
|
|
assert curr is not None and curr.next is None
|
|
curr.next = new_node
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import python_ta
|
|
python_ta.check_all(config={
|
|
'max-line-length': 100,
|
|
'disable': ['E1136'],
|
|
'extra-imports': ['a1_linked_list'],
|
|
'max-nested-blocks': 4
|
|
})
|
|
|
|
import python_ta.contracts
|
|
python_ta.contracts.check_all_contracts()
|
|
|
|
import doctest
|
|
doctest.testmod()
|