diff --git a/TT2/q1.py b/TT2/q1.py new file mode 100644 index 0000000..d3ac7b1 --- /dev/null +++ b/TT2/q1.py @@ -0,0 +1,129 @@ +"""CSC110 Fall 2021: Term Test 2, Question 1 (Cryptography) + +Module Description +================== +This module contains instructions for this question. There are THREE +parts of this question, labelled "Part (a)", "Part (b)", etc. +The comments in this file contain instructions on how to complete each part, +so please read those comments carefully. + +At the bottom of the file we've provided code to run doctest and python_ta. +python_ta is not required for grading. + +SUBMIT THIS FILE FOR GRADING! + +Copyright and Usage Information +=============================== + +This file is provided solely for the personal and private use of students +taking CSC110 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 CSC110 materials, +please consult our Course Syllabus. + +This file is Copyright (c) 2021 Mario Badr and Tom Fairgrieve. +""" + + +################################################################################################### +# Encrypting Grade Messages +################################################################################################### +# We can represent your first-year courses grades using a tuple of five integers between 0 and 100, +# inclusive. A *grades message* is a list of grades, i.e., a list[tuple[int, int, int, int, int]]. +# For example: +# +# [(95, 90, 67, 75, 89), (55, 64, 78, 92, 86), (100, 96, 64, 83, 87)] + +################################################################################################### +# Description of the Cryptosystem +################################################################################################### +# We define the following SYMMETRIC-KEY CRYPTOSYSTEM on grade messages: +# +# PLAINTEXT and CIPHERTEXT: +# Grades messages (i.e, list[tuple[int, int, int, int, int]], +# where each int is in 0-100, inclusive. +# +# SECRET KEY: +# The secret key is an integer k. +# +# ENCRYPTION: +# For each index i in the plaintext grade message (starting at i = 0), transform the i-th grade +# tuple by adding (k^(i+1)) to each element of the tuple and then taking the remainder modulo 101. +# +# DECRYPTION: +# Reverse the encryption process. + +################################################################################################### +# Part (a) - encryption +################################################################################################### +# The function below is the start of the encryption algorithm described above. +# Complete it by doing two things: +# 1. Complete the doctest example by showing the expected return value of the function call. +# 2. Implement the function body. You may use loops, comprehensions, and/or helper functions. +# You are not required to add doctests for any helper functions you create. +# +# Note: encryption should NOT mutate the input plaintext. + + +def encrypt_tt2(k: int, plaintext: list[tuple[int, int, int, int, int]]) \ + -> list[tuple[int, int, int, int, int]]: + """Return the ciphertext grade message when plaintext is encrypted with key k. + + Preconditions: + - k and plaintext are valid inputs for encryption, based on the cryptosystem description + + >>> key = 20 + >>> p = [(95, 90, 67, 75, 89), (55, 64, 78, 92, 86)] + >>> encrypt_tt2(key, p) + [(14, 9, 87, 95, 8), (51, 60, 74, 88, 82)] + """ + c = [] + for i in range(len(plaintext)): + incr = k ** (i + 1) + n1, n2, n3, n4, n5 = [(n + incr) % 101 for n in plaintext[i]] + c.append((n1, n2, n3, n4, n5)) + return c + + +################################################################################################### +# Part (b) - decryption +################################################################################################### +# The function below is the start of the decryption algorithm described above. +# Complete it by doing two things: +# 1. Add one new doctest example for this function. +# 2. Implement the function body. You may use loops, comprehensions, and/or helper functions. +# You are not required to add doctests for any helper functions you create. +# It is up to you to determine the correct algorithm for decrypting a ciphertext, +# based on the description of the encryption algorithm. +# +# Note: decryption should NOT mutate the input ciphertext. + +def decrypt_tt2(k: int, ciphertext: list[tuple[int, int, int, int, int]]) \ + -> list[tuple[int, int, int, int, int]]: + """Return the plaintext grade message when ciphertext is decrypted with key k. + + Preconditions: + - k and ciphertext are valid inputs for decryption, based on the cryptosystem description + + >>> key = 20 + >>> c = [(19, 15, 84, 2, 6)] + >>> decrypt_tt2(key, c) + [(100, 96, 64, 83, 87)] + """ + p = [] + for i in range(len(ciphertext)): + incr = k ** (i + 1) + n1, n2, n3, n4, n5 = [(n - incr) % 101 for n in ciphertext[i]] + p.append((n1, n2, n3, n4, n5)) + return p + + +################################################################################################### +# Part (c) - understanding the cryptosystem +################################################################################################### +def why_101() -> str: + """Return a string describing why we use a modulus of 101 instead of 100 in the cryptosystem + defined above. + """ + return "This is because modding by 100 will return the last two digits of the original number" \ + ", which will reveal the original content if the key is a whole number like 10 or 20" diff --git a/TT2/q2.pdf b/TT2/q2.pdf new file mode 100644 index 0000000..1729090 Binary files /dev/null and b/TT2/q2.pdf differ diff --git a/TT2/q2.tex b/TT2/q2.tex new file mode 100644 index 0000000..07a8dbf --- /dev/null +++ b/TT2/q2.tex @@ -0,0 +1,158 @@ +% Copyright and Usage Information +% =============================== + +% This file is provided solely for the personal and private use of students +% taking CSC110 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 CSC110 materials, +% please consult our Course Syllabus. + +% This file is Copyright (c) 2021 Mario Badr and Tom Fairgrieve. +\documentclass{article} + +\setlength{\parindent}{0pt} +\setlength{\parskip}{5pt} + +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsthm} +\usepackage{amsfonts} + +\usepackage[margin=1in]{geometry} + +\title{CSC110 Fall 2021: Term Test 2\\ + Question 2 (Analyzing Algorithm Running Time)} +\author{TODO: INSERT YOUR NAME HERE} +\date{Wednesday December 8, 2021} + +% Some useful LaTeX commands. You are free to use these or not, and also add your own. +\newcommand{\N}{\mathbb{N}} +\newcommand{\Z}{\mathbb{Z}} +\newcommand{\R}{\mathbb{R}} +\newcommand{\cO}{\mathcal{O}} +\newcommand{\floor}[1]{\left\lfloor #1 \right\rfloor} +\newcommand{\ceil}[1]{\left\lceil #1 \right\rceil} +\newcommand{\C}{\texttt} + +\begin{document} +\maketitle + +\subsection*{Question 2, Part 1} + +\noindent +We define the function $g: \N \to \R^{\geq 0}$ as $g(n) = 7n(n-1)^2$. +Consider the following statement: + +\[ +g(n) \in \cO(n^4) +\] + +\begin{enumerate} + +\item[(a)] +Rewrite the statement $g(n) \in \cO(n^4)$ by expanding the definition of Big-O. + +\bigskip + +\textbf{Solution}: + +$\exists c, n_0 \in \R^+ \text{ s.t. } \forall n \in \N, n \ge n_0 \Rightarrow 7n(n-1)^2 \le c \cdot n^4$ + +\item[(b)] +Write the \emph{negation} of the statement from (a), using negation rules to simplify the statement as much as possible. + +\bigskip + +\textbf{Solution}: + +$\forall c, n_0 \in \R^+, \exists n \in \N \text{ s.t. } n \ge n_0 \land 7n(n-1)^2 > c \cdot n^4$ + +\item[(c)] +Which of statements (a) and (b) is true? Provide a complete proof that justifies your choice. + +In your proof, you may not use any properties or theorems about Big-O/Omega/Theta. Work from the expanded statement from (a) or (b). + +\bigskip + +\textbf{Solution}: + +I think statement (a) is true. + +\begin{proof} +Want to show: $\exists c, n_0 \in \R^+ \text{ s.t. } \forall n \in \N, n \ge n_0 \Rightarrow 7n(n-1)^2 \le c \cdot n^4$ \\ +Prove using Induction. \\ +Take $c = 7, n_0 = 1$ \\ +Let $n$ be an arbitrary natural number such that $n \ge (n_0 = 1)$ \\ +What we want to prove becomes: $\forall n \in \N, n \ge 1 \Rightarrow 7n(n-1)^2 \le 7n^4$ \\ +\\ +Since $n \ge 1$, \\ +Multiply both sides by $n^2$, we get $n^3 \ge n^2$ \\ +From this, we also know that $(n - 1)^2 \le n^3$ \\ +Also, since $n \ge 1$, \\ +Multiply the inequality by $-2$, we get $-2n \le -2$ \\ +Adding $1$ to both sides, we get $-2n + 1 \le -1$ \\ +Putting the two inequalities together, we have $n^2 - 2n + 1 \le n^3 - 1 \le n^3$ \\ +Factoring the polynomial on the left, we have $(n - 1)^2 \le n^3$ \\ +Multiply both sides by $7n$, we get $7n(n - 1)^2 \le 7n^4$\\ +Which is what we want to prove. + +\end{proof} + +\end{enumerate} + +\subsection*{Question 2, Part 2} + +\noindent +Consider the function below. + +\begin{verbatim} +def f(nums: list[int]) -> list[int]: # Line 1 + n = len(nums) # Line 2 + i = 1 # Line 3 + new_list = [] # Line 4 + while i < n: # Line 5 + if nums[i] % 2 == 0: # Line 6 + list.append(new_list, i) # Line 7 + else: # Line 8 + new_list = [i * j for j in nums] # Line 9 + i = i * 3 # Line 10 + return new_list # Line 11 +\end{verbatim} + +\begin{enumerate} + +\item[(a)] +Perform an \emph{upper bound analysis} on the worst-case running time of \texttt{f}. +The Big-O expression that you conclude should be \emph{tight}, meaning that the worst-case running time should be Theta of this expression, but you are not required to show that here. + +\textbf{To simplify your analysis}, you may omit all floors and ceilings in your calculations (if applicable). +Use ``at most'' or $\leq$ to be explicit about where a step count expression is an upper bound. + +\textbf{Solution}: + +Let $n$ be the length of the input list \C{nums} + +There is one loop in the function which loops through \C{nums} with $i$ increasing exponentially, which will run $\ceil{log_3(n)}$ times. Inside the loop, if then number is even, it takes $\cO(1)$ to append the item at the end of \C{new\_list}. If the number is odd, it sets \C{new\_list} to a list comprehension which iterates through all number in \C{nums}, performing an $\cO(1)$ multiplication every iteration, which takes exactly $n$ steps, which is a larger running time than if the number is even. Therefore, the inside of the loop will take at most $n$ steps, if all numbers \C{nums[i]} iterated are odd. + +Since there are only constant-time operations outside the loop, the worst-case running time would be $\ceil{log_3(n)}$ iterations multiplied by at most $n$ steps per iteration, which is $n\ceil{log_3(n)}$ steps. + +Since $n\ceil{log_3(n)} \in \cO(n\ceil{log_3(n)})$, we can conclude that $WC_{f}(n) \in \cO(n\ceil{log_3(n)})$ + +\item[(b)] +Perform a \emph{lower bound analysis} on the worst-case running time of \texttt{f}. +The Omega expression you find should match your Big-O expression from part (a). + +\textbf{Hint}: you don't need to try to find an ``exact maximum running-time'' input. \emph{Any} input family whose running time is Omega of (``at least'') the bound you found in part (a) will yield a correct analysis for this part. + +\textbf{Solution}: + +Let $n$ be the length of the input list \C{nums}, let \C{nums} be the list of length $n$ which every number is 1. + +In this case, the if statement inside the loop always runs line 9 that takes $n$ steps, and then the $i = i * 3$ statement, which is 1 step, which is a total of $n + 1$ steps. The loop still iterates $\ceil{log_3(n)}$ times. Since there are only constant-time operations outside the loop, the total number of steps for this input is $(n + 1)\ceil{log_3(n)} + c$ which $c \in \N$ is a constant, which is $WC_{f}(n) \in \Omega(n\ceil{log_3(n)})$ + +\end{enumerate} + +\begin{center} + \textbf{SUBMIT THIS FILE AND THE GENERATED PDF q2.pdf FOR GRADING} +\end{center} +\end{document} diff --git a/TT2/q3.py b/TT2/q3.py new file mode 100644 index 0000000..28d6b98 --- /dev/null +++ b/TT2/q3.py @@ -0,0 +1,191 @@ +"""CSC110 Fall 2021: Term Test 2, Question 3 (Object Oriented Design) + +Module Description +================== +This module contains instructions for this question. There are TWO +parts of this question, labelled "Part (a)", "Part (b)", etc. +The comments in this file contain instructions on how to complete each part, +so please read those comments carefully. + +SUBMIT THIS FILE FOR GRADING! + +Copyright and Usage Information +=============================== + +This file is provided solely for the personal and private use of students +taking CSC110 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 CSC110 materials, +please consult our Course Syllabus. + +This file is Copyright (c) 2021 Mario Badr and Tom Fairgrieve. +""" +from dataclasses import dataclass + + +################################################################################################### +# Modelling a Problem Domain +################################################################################################### +# In this question, you will model a new problem domain by designing classes in a similar style to +# the food delivery system we studied in lecture. + +################################################################################################### +# Description of the Problem Domain +################################################################################################### +# Your client is the University of Toronto. They want new software to manage the grades of their +# students. The grade system will store: +# 1. GRADES that each have the course id (e.g., 'CSC110Y'), term id (e.g., '20219'), and score in +# the course (i.e., an integer ranging from 0 to 100, inclusive). +# 2. STUDENTS that each have a utorid (e.g., 'astudent') and a collection of grades. + +################################################################################################### +# Part (a) - Entity data classes +################################################################################################### +# Your first task is to complete the two data classes below, which represent the two main entities +# in our computational model. Read the provided docstrings and use them to complete each data class +# body. +# +# Do NOT add any additional instance attributes or change the class docstring. +# You do NOT need to add any representation invariants or doctest examples. + +@dataclass +class Grade: + """A grade on Acorn. + + Instance Attributes: + - course: the course (e.g., 'CSC110Y1') this grade was achieved in. + - term: the term (e.g., '20219') this grade was achieved in. + - score: an integer representing the grade achieved. + """ + course: str + term: str + score: int + + +@dataclass +class Student: + """A student who can have courses grades on acorn. + + Instance Attributes: + - utorid: the utorid of this student + - grades: a dictionary mapping a course (e.g., 'CSC110Y1') to the student's Grade in that + course. + """ + utorid: str + grades: dict[str, Grade] + + +############################################################################### +# Part (b) - Manager class +############################################################################### +# The class below is responsible for keeping track of all instances of the entities in our +# model and performing mutating operations on them. +# +# Read through the class and implement the methods that have empty bodies. +# Do NOT change anything else in the class (attributes, methods that we have implemented). + + +class AcornSystem: + """A class that tracks students and their grades.""" + + # Private Instance Attributes: + # - _students: a dictionary mapping utorids to Students + + def __init__(self, initial_students: list[Student]) -> None: + """Initialize a new AcornSystem, adding every student in initial_students to this system. + + Precondition: + - Every student in initial_students has a unique utorid + """ + self._students = {} + for student in initial_students: + self._students[student.utorid] = student + + def student_count(self) -> int: + """Return the number of students in this system.""" + return len(self._students) + + def add_grade(self, utorid: str, grade: Grade) -> bool: + """Add grade to the student with utorid and return whether the grade was added successfully. + + Do not add grade if the student with utorid already has a grade assigned for that course. + + Preconditions: + - A student with utorid exists in this system + """ + student = self._students[utorid] + + if grade.course in student.grades: + return False + else: + student.grades[grade.course] = grade + return True + + def get_grades(self, utorid: str, term: str) -> list[tuple[str, int]]: + """Return a list of tuples of the courses and scores that the student with utorid achieved + in term. + + Preconditions: + - A student with utorid exists in this system + - The student in this system with utorid has a grade for course + + >>> acorn_system = AcornSystem([Student('astudent', {})]) + >>> acorn_system.add_grade('astudent', Grade('CLA204H1', '20209', 76)) + True + >>> acorn_system.get_grades('astudent', '20209') + [('CLA204H1', 76)] + """ + return [(g.course, g.score) for g in self._students[utorid].grades.values() + if g.term == term] + + def amend_grades(self, utorid: str, amendments: dict[str, int]) -> int: + """Update the grades for the student with utorid based on the amendments, which maps courses + to the amended score. + + Return the number of successful amendments made. An amendment is not successful if the + student has no grade on record for the course. No mutation should occur for students who + have no grade on record for course. + + Preconditions: + - A student with utorid exists in this system + + >>> acorn_system = AcornSystem([Student('astudent', {})]) + >>> acorn_system.add_grade('astudent', Grade('CLA204H1', '20209', 76)) + True + >>> acorn_system.amend_grades('astudent', {'CLA204H1': 80}) + 1 + >>> acorn_system.get_grades('astudent', '20209') + [('CLA204H1', 80)] + """ + grades = self._students[utorid].grades + count = 0 + for course in amendments: + if course in grades: + grades[course].score = amendments[course] + count += 1 + return count + + def calculate_averages(self, utorids: list[str]) -> dict[str, float]: + """Return a dictionary mapping a student's utorid to the average grade they achieved for + all courses completed. + + Preconditions: + - Every utorid in amendments is a student in this system + + >>> acorn_system = AcornSystem([Student('astudent', {}), Student('bstudent', {})]) + >>> acorn_system.add_grade('astudent', Grade('CLA204H1', '20209', 76)) + True + >>> acorn_system.calculate_averages(['astudent']) + {'astudent': 76.0} + >>> acorn_system.add_grade('astudent', Grade('CLA205H1', '20219', 98)) + True + >>> acorn_system.calculate_averages(['astudent']) + {'astudent': 87.0} + >>> acorn_system.add_grade('bstudent', Grade('CLA205H1', '20219', 98)) + True + >>> temp = acorn_system.calculate_averages(['astudent', 'bstudent']) + >>> temp == {'astudent': 87.0, 'bstudent': 98.0} + True + """ + return {u: sum(g.score for g in self._students[u].grades.values()) + / len(self._students[u].grades) for u in utorids}