diff --git a/assignments/A1/a1.tex b/assignments/A1/a1.tex new file mode 100644 index 0000000..0dcd1f5 --- /dev/null +++ b/assignments/A1/a1.tex @@ -0,0 +1,44 @@ +\documentclass[fontsize=11pt]{article} +\usepackage{amsmath} +\usepackage[utf8]{inputenc} +\usepackage[margin=0.75in]{geometry} + +\title{CSC111 Assignment 1: Linked Lists} +\author{TODO: FILL IN THE NAME OF EACH OF YOUR GROUP MEMBERS HERE} +\date{\today} + +\begin{document} +\maketitle + +\section*{Part 1: Faster Searching in Linked Lists} + +\begin{enumerate} + +\item[1.] +Complete this part in the provided \texttt{a1\_part1.py} starter file. +Do \textbf{not} include your solution in this file. + +\item[2.] +Complete this part in the provided \texttt{a1\_part1\_test.py} starter file. +Do \textbf{not} include your solution in this file. + +\item[3.] +\begin{enumerate} + + \item[(a)] + TODO: Write your answer here. + + \item[(b)] + TODO: Write your answer here. +\end{enumerate} + +\item[4.] +TODO: Write your answer here. + +\end{enumerate} + +\section*{Part 2: Linked List Visualization} +Complete this part in the provided \texttt{a1\_part2.py} starter file. +Do \textbf{not} include your solution in this file. + +\end{document} diff --git a/assignments/A1/a1_linked_list.py b/assignments/A1/a1_linked_list.py new file mode 100644 index 0000000..722ee23 --- /dev/null +++ b/assignments/A1/a1_linked_list.py @@ -0,0 +1,169 @@ +"""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 diff --git a/assignments/A1/a1_part1.py b/assignments/A1/a1_part1.py new file mode 100644 index 0000000..eae3668 --- /dev/null +++ b/assignments/A1/a1_part1.py @@ -0,0 +1,132 @@ +"""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] + """ + + +################################################################################ +# 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] + """ + + +################################################################################ +# 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 __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() diff --git a/assignments/A1/a1_part1_test.py b/assignments/A1/a1_part1_test.py new file mode 100644 index 0000000..8fa2c95 --- /dev/null +++ b/assignments/A1/a1_part1_test.py @@ -0,0 +1,32 @@ +"""CSC111 Winter 2021 Assignment 1: Linked Lists, Part 1 + +Instructions (READ THIS FIRST!) +=============================== + +Please write your tests for Part 1 in this module. Make sure to review the assignment +instructions carefully for this part! You may find it helpful to consult this +section of the Course Notes: +https://www.teach.cs.toronto.edu/~csc110y/fall/notes/B-python-libraries/02-pytest.html + +While you must include unit tests, you may also use property-based tests in your test suite. + +We will *not* be running PythonTA on this file; however, you should follow good programming +design and style in this file anyway, just like you would for all other work. + +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. +""" +import pytest +from a1_part1 import MoveToFrontLinkedList, SwapLinkedList, CountLinkedList + + +if __name__ == '__main__': + pytest.main(['a1_part1_test.py', '-v']) diff --git a/assignments/A1/a1_part2.py b/assignments/A1/a1_part2.py new file mode 100644 index 0000000..81c152a --- /dev/null +++ b/assignments/A1/a1_part2.py @@ -0,0 +1,256 @@ +"""CSC111 Winter 2021 Assignment 1: Linked Lists, Part 2 + +Instructions (READ THIS FIRST!) +=============================== + +This Python module generates a graphical representation of linked lists. +You need to implement a few functions that visualize different components of +the list as described in the handout, and that make your visualization interactive. + +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. +""" +import random +from typing import Tuple + +import pygame +from pygame.colordict import THECOLORS + +from a1_linked_list import LinkedList, _Node + +################################################################################ +# Graphics constants +################################################################################ +# You should not change SCREEN_SIZE or GRID_SIZE, but may add your own constants underneath. +SCREEN_SIZE = (800, 800) # (width, height) +GRID_SIZE = 8 + + +################################################################################ +# Pygame helper functions (don't change these!) +################################################################################ +def initialize_screen(screen_size: tuple[int, int], allowed: list) -> pygame.Surface: + """Initialize pygame and the display window. + + allowed is a list of pygame event types that should be listened for while pygame is running. + """ + pygame.display.init() + pygame.font.init() + screen = pygame.display.set_mode(screen_size) + screen.fill(THECOLORS['white']) + pygame.display.flip() + + pygame.event.clear() + pygame.event.set_blocked(None) + pygame.event.set_allowed([pygame.QUIT] + allowed) + + return screen + + +def draw_text(screen: pygame.Surface, text: str, pos: tuple[int, int]) -> None: + """Draw the given text to the pygame screen at the given position. + + pos represents the *upper-left corner* of the text. + """ + font = pygame.font.SysFont('inconsolata', 22) + text_surface = font.render(text, True, THECOLORS['black']) + width, height = text_surface.get_size() + screen.blit(text_surface, + pygame.Rect(pos, (pos[0] + width, pos[1] + height))) + + +def draw_grid(screen: pygame.Surface) -> None: + """Draws a square grid on the given surface. + + The drawn grid has GRID_SIZE columns and rows. + You can use this to help you check whether you are drawing nodes and edges in the right spots. + """ + color = THECOLORS['grey'] + width, height = screen.get_size() + + for col in range(1, GRID_SIZE): + x = col * (width // GRID_SIZE) + pygame.draw.line(screen, color, (x, 0), (x, height)) + + for row in range(1, GRID_SIZE): + y = row * (height // GRID_SIZE) + pygame.draw.line(screen, color, (0, y), (width, y)) + + +################################################################################ +# 1. Drawing nodes and links +################################################################################ +def draw_node(screen: pygame.Surface, node: _Node, pos: Tuple[int, int]) -> None: + """Draw a node on the screen at the given position. + + pos represents the coordinates of the *top-left* corner of the node. + + Your drawing of the the node should include: + - A rectangle split vertically into two halves. + - The item stored in the node, displayed in the left half. + You may assume the string representation of item is at most 3 characters + long. You'll need to use the draw_text function we've provided above. + + NOTE: Do not draw the arrow representing the link to the next node in this function. + You'll implement that part of the visualization separately in the draw_link function. + + We strongly recommend initializing new constants at the top of this file to represent + node width, height, and colour. + """ + + +def draw_link(screen: pygame.Surface, start: Tuple[int, int], end: Tuple[int, int]) -> None: + """Draw a line representing a link from `start` to `end`. + + To indicate which end is the "start" of the line, draw a small circle at the starting point + (like the diagrams we've seen in class). + + The rest of your link can be a simple line; you may, but are not required, to draw an + arrowhead at the end of the line. + """ + + +def draw_three_nodes(screen_size: Tuple[int, int]) -> None: + """Draw three nodes on a pygame screen of the given size. + + You may choose the coordinates for the nodes, as long as they do not overlap with each other + and are separated by at least 10 pixels. Each link must start in the CENTRE of the start node's + right half, and must end on the border of the end node (not inside the end node). + This matches the style of node from lecture. + + The third node should link to "None", which you should visualize by calling draw_text. + """ + screen = initialize_screen(screen_size, []) + node1 = _Node(1) + node2 = _Node(2) + node3 = _Node(3) + node1.next = node2 + node2.next = node3 + + # TODO: Complete this function so that it draws the above three nodes and the + # links between them and "None". Once you are done, remove this comment. + ... + + # Don't change the code below (it simply waits until you close the Pygame window) + pygame.display.flip() + pygame.event.wait() + pygame.display.quit() + + +################################################################################ +# 2. Drawing a full linked list +################################################################################ +def draw_list(screen: pygame.Surface, lst: LinkedList, show_grid: bool = False) -> None: + """Draw the given linked list on the screen. + + The linked list nodes should be drawn in a grid pattern with GRID_SIZE rows and columns. + See the assignment handout for details and constraints on your drawing. + + If the show_grid parameter is True, the grid is drawn on the board. You can use + the grid to help you make sure you're drawing nodes in the correct locations. + By default, show_grid is False. + + Preconditions: + - len(lst) < GRID_SIZE * GRID_SIZE + + As with draw_node, we strongly recommend initializing new constants at the top of this file + to store numbers used to position the nodes. + + We have started the linked list traversal pattern for you. Note that we're accessing + a private LinkedList attribute _first, which is generally not a good practice but we're + allowing you to do so here to simplify your code a little. + """ + if show_grid: + draw_grid(screen) + + curr = lst._first + curr_index = 0 + + while curr is not None: + ... + + curr = curr.next + curr_index = curr_index + 1 + + +################################################################################ +# 3. Handling user events +################################################################################ +def run_visualization(screen_size: tuple[int, int], ll_class: type, + show_grid: bool = False) -> None: + """Run the linked list visualization. + + Initialize a screen of the given size, and show the grid when show_grid is True. + ll_class is the type of linked list to use. + + Preconditions: + - ll_class is LinkedList or issubclass(ll_class, LinkedList) + + This function is provided for you for Part 3, and you *should not change it*. + Instead, your task is to implement the helper function handle_mouse_click (and + any other helpers you decide to add). + """ + # Initialize the Pygame screen, allowing for mouse click events. + screen = initialize_screen(screen_size, [pygame.MOUSEBUTTONDOWN]) + + # Initialize a random linked list of length 50. + lst = ll_class(random.sample(range(-99, 1000), 50)) + + while True: + # Draw the list (on a white background) + screen.fill(THECOLORS['white']) + draw_list(screen, lst, show_grid) + pygame.display.flip() + + # Wait for an event (either pygame.MOUSEBUTTONDOWN or pygame.QUIT) + event = pygame.event.wait() + + if event.type == pygame.MOUSEBUTTONDOWN: + # Call our event handling method + handle_mouse_click(lst, event, screen.get_size()) + elif event.type == pygame.QUIT: + break + + pygame.display.quit() + + +def handle_mouse_click(lst: LinkedList, event: pygame.event.Event, + screen_size: Tuple[int, int]) -> None: + """Handle a mouse click event. + + A pygame mouse click event object has two attributes that are important for this method: + - event.pos: the (x, y) coordinates of the mouse click + - event.button: an int representing which mouse button was clicked. + 1: left-click, 3: right-click + + The screen_size is a tuple of (width, height), and should be used together with + event.pos to determine which cell is being clicked. If a click happens exactly on + the boundary between two cells, you may decide which cell is selected. + + Preconditions: + - event.type == pygame.MOUSEBUTTONDOWN + - screen_size[0] >= 200 + - screen_size[1] >= 200 + """ + + +if __name__ == '__main__': + import python_ta + python_ta.check_all(config={ + 'max-line-length': 100, + 'disable': ['E1136'], + 'exclude-protected': ['_first'], + 'extra-imports': ['random', 'pygame', 'pygame.colordict', 'a1_linked_list'], + 'generated-members': ['pygame.*'] + }) + + import python_ta.contracts + python_ta.contracts.check_all_contracts()