\documentclass[11pt]{article}
\usepackage{amsmath}
\usepackage[utf8]{inputenc}
\usepackage[margin=0.75in]{geometry}

\title{CSC111 Assignment 3: Graphs, Recommender Systems, and Clustering}
\author{Azalea Gui \& Peter Lin}
\date{\today}

\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{\code}[1]{\texttt{#1}}

\begin{document}
\maketitle

\section*{Part 1: The book review graph and simple recommendations}

\begin{enumerate}

\item[1.]
Complete this part in the provided \texttt{a3\_part1.py} starter file.
Do \textbf{not} include your solution in this file.

\item[2.]
Running Time Analysis for \texttt{load\_review\_graph}:

Let $n$ be the number of lines in \texttt{book\_names\_file}, let $m$ be the number of lines in \texttt{reviews\_file}.

There are two operations that involves iteration in the function, one reads the book names file and creates the \texttt{mp} dictionary, and the other one reads the reviews file and adds vertices to the graph.

In creating $mp$, the program first opened the file and created a \texttt{csv.reader}, which are both constant-time operations. Then, the dictionary comprehension statement loops through all $n$ lines, running only constant-time operations in each iteration for adding the book id and name pair into the dictionary, resulting in a running time of $\Theta(n)$. Summing up all the operations for creating $mp$ and ignoring constant-time operations, the running time would be $\in \Theta(n)$.

For adding the vertices, it also opened the file and created a \texttt{csv.reader} in constant time. Then, the loop iterates through all $m$ lines. In each iteration, two vertices and one edge are added, and it also accessed $mp$ to retrieve the book name, which are all constant time operations. Therefore, the total running time would be $\in \Theta(m)$.

Since there are only constant-time operations outside the two iterating operations, the total running time of the function would be $\in \Theta(m + n)$

\item[3.]
Complete this part in the provided \texttt{a3\_part1.py} starter file.
Do \textbf{not} include your solution in this file.

\item[4.]
Complete this part in the provided \texttt{a3\_part1.py} starter file.
Do \textbf{not} include your solution in this file.

\end{enumerate}

\section*{Part 2: Weighted graphs, recommendations, review prediction}

Complete this part in the provided \texttt{a3\_part2\_recommendations.py} and \texttt{a3\_part2\_predictions.py} starter files.
Do \textbf{not} include your solution in this file.

\newpage

\section*{Part 3: Finding book clusters}

\begin{enumerate}

\item[1.]
Complete this part in the provided \texttt{a3\_part3.py} starter file.
Do \textbf{not} include your solution in this file.

\item[2.]
Complete this part in the provided \texttt{a3\_part3.py} starter file.
Do \textbf{not} include your solution in this file.

\item[3.]

\begin{enumerate}
\item[(a)]
Running Time Analysis for \texttt{cross\_cluster\_weight}:

Let $m_1$ be the size of \texttt{cluster1}, and let $m_2$ be the size of \texttt{cluster2}.

There is one nested loop in the function. The inner loop iterates $m_2$ times through all values in \texttt{cluster2}, and the outer loop iterates $m_1$ times through all values in \texttt{cluster1}. Inside the inner loop, there is only one function call \texttt{get\_weight}, which is constant-time because it only involves constant-time operations like dictionary accessing and variable assignment. After the function call, the returned value is added to \texttt{sw}, which is one constant-time operation. Let $c$ be a constant representing the number of constant time operations inside the inner loop. The nested loop will run $m_1 * m_2 * c$ operations in total, which is $\in \Theta(m_1 * m_2)$

Since there are only constant-time operations such as \texttt{len(set)}, variable assignment, and multiplication or division outside the nested loop, the running time of the function will be $\in \Theta(m_1 * m_2)$


\item[(b)]
Running Time Analysis for the inner loop in \texttt{find\_clusters\_random}:

Let $n$ be the number of vertices of \texttt{graph}.

There is one nested loop in the function.

For iteration $i$ of the outer loop, the inner loop iterates through all books in the \texttt{clusters} array, which has $n - i$ entires since each iteration of the outer loop removes an entry at the end. Inside the inner loop, the \texttt{if}, \texttt{is not}, comparisons, and variable assignment operations are all constant-time. For iteration $j$ of the inner loop, the \texttt{cross\_cluster\_weight} function call inside the inner loop has a running time of $\Theta(m_1 * m_j)$ as previously analyzed, where $m_1$ is the size of cluster $c_1$, and $m_j$ is the size of cluster $c_j$ (or $c_2$ for iteration $j$). Therefore, the running time of the function will be: 


\begin{align}
RT_{\text{inner\_loop}} &= \sum_j^{n-i} m_1 * m_j \\
&= m_1 * \sum_j^{n-i} m_j
\end{align}

Since the if statement inside the inner loop ensures $m_1 \neq m_j$, and since we know that $\sum_c |c| = n$, the previously stated $\sum_j^{n-i} m_j$ will be equal to $n - |c_1| = n - m_1$. Therefore, the running time of the function is $m_1 * (n - m_1)$

Since every cluster is initialized to have one element, we know that $m_1 > 0$, and $n - m_1 < n$. Then, since $m_1$ is also $< n$, the running time $m_1 * (n - m_1)$ is bounded by $\cO(n^2)$.

\item[(c)]
Running Time Analysis for \texttt{find\_clusters\_random}:

Let $n$ be the number of vertices of graph, and let $k$ be the value of \texttt{num\_clusters}.

In the worst case scenario: Since the outer loop iterates over a range from $0$ to $n - k$, it iterates $n - k$ times. For each iteration, the inner loop have a running time of $\cO(n^2)$ as previously analyzed. Besides from the inner loop, other statements inside the outer loop includes constant-time operations print, f-string creation, \texttt{random.choice}, and varaible assignment. After that, all elements in the set $c_1$ are added to \texttt{best\_c2}, which has a worst-case running time of $\cO(n)$ because $|c_1| <= n$. Also, $c_1$ is removed from the list \texttt{clusters}, which also has a worst-case running time of $\cO(n)$ because \texttt{len(clusters)} $<= n$. Combining all of these operations, the running time of one iteration of the outer loop will be $\in \cO(n^2)$, and the runninng time of $n - k$ iterations will be $\in \cO(n^2 \cdot (n - k))$.

Outside the outer loop, there is only a return statement (constant-time) and a list comprehension that initializes the \texttt{clusters} array, which goes through $n$ iterations, executing a constant-time operation of creating a set for each iteration.

Therefore, the running time of the entire function would be $\in \cO(n^2 \cdot (n - k))$.

\item[(d)]
\emph{Not to be handed in.}
\end{enumerate}

\end{enumerate}
\end{document}
