"""CSC111 Winter 2021 Assignment 1: Linked Lists Do not change this file -- it will not be submitted, and we will supply our own copy for grading purposes. 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, Iterable, Optional @dataclass class _Node: """A node in a linked list. Note that this is considered a "private class", one which is only meant to be used in this module by the LinkedList class, but not by client code. Instance Attributes: - item: The data stored in this node. - next: The next node in the list, if any. """ item: Any next: Optional[_Node] = None # By default, this node does not link to any other node class LinkedList: """A linked list implementation of the List ADT. """ # Private Instance Attributes: # - _first: The first node in the linked list, or None if the list is empty. _first: Optional[_Node] def __init__(self, items: Iterable) -> None: """Initialize a new linked list containing the given items. """ self._first = None for item in items: self.append(item) def to_list(self) -> list: """Return a built-in Python list containing the items of this linked list. The items in this linked list appear in the same order in the returned list. """ items_so_far = [] curr = self._first while curr is not None: items_so_far.append(curr.item) curr = curr.next return items_so_far def __len__(self) -> int: """Return the number of elements in this list. >>> lst = LinkedList([]) >>> len(lst) # Equivalent to lst.__len__() 0 >>> lst = LinkedList([1, 2, 3]) >>> len(lst) 3 """ curr = self._first len_so_far = 0 while curr is not None: len_so_far += 1 curr = curr.next return len_so_far def __contains__(self, item: Any) -> bool: """Return whether item is in this linked list. """ curr = self._first while curr is not None: if curr.item == item: return True curr = curr.next return False def __getitem__(self, i: int) -> Any: """Return the item stored at index i in this linked list. Raise an IndexError if index i is out of bounds. Preconditions: - i >= 0 """ curr = self._first curr_index = 0 while curr is not None: if curr_index == i: return curr.item curr = curr.next curr_index = curr_index + 1 # If we've reached the end of the list and no item has been returned, # the given index is out of bounds. raise IndexError def pop(self, i: int) -> Any: """Remove and return the item at index i. Raise IndexError if i >= len(self). Preconditions: - i >= 0 >>> lst = LinkedList([1, 2, 10, 200]) >>> lst.pop(1) 2 >>> lst.to_list() [1, 10, 200] """ if i == 0: if self._first is None: raise IndexError else: item = self._first.item self._first = self._first.next return item else: curr = self._first curr_index = 0 while not (curr is None or curr_index == i - 1): curr = curr.next curr_index = curr_index + 1 if curr is None: raise IndexError else: if curr.next is None: raise IndexError else: item = curr.next.item curr.next = curr.next.next return item def append(self, item: Any) -> None: """Add the given item to the end of this linked list. """ new_node = _Node(item) 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