\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{amsfonts, amsmath, enumitem, float, graphicx, hyperref}

\title{Artificial Intelligence - Notes}
\author{Matteo Alberici}
\date{January 2022}

\begin{document}

\maketitle
\newpage
\tableofcontents
\newpage

% ------------------------------------- %
% Section 1: Search Problems Definition %
% ------------------------------------- %
\section{Search Problems}
In order to define a \textbf{search problem}, we need to introduce the following terms:
\begin{itemize}
    \item \textbf{States}: the set of possible configurations
    \item \textbf{Initial state}: the state where the search starts
    \item \textbf{Actions}: the set of possible actions
    \item \textbf{Goal}: the destination of the search
    \item \textbf{Cost function}: gives a cost to the solution path
\end{itemize}
\subsection{Graph Definition}
A search problem is modelled as a \textbf{graph} having the following finite sets:
\begin{center}
    \textbf{nodes}: $(n_1, n_2, ..., n_n)$ \\
    \vspace{0.2cm}
    \textbf{edges}: $((n_i, n_j), ..., (n_k, n_n))$
\end{center}
Given a direct edge $(n_i, n_j)$, we say that $n_j$ is a \textbf{child} of $N_i$. If a node has no children, then it is a \textbf{leaf}. \\
A \textbf{path} $(n_i, n_{i+1}, ..., n_n)$ is a sequence with each couple $n_i, n_{i+1}$ being an edge. If a path contains the same node more times, then it is a \textbf{cycle}. \\
A graph is \textbf{rooted} when a node $n_i$ is the origin of all the paths. Moreover, two nodes are \textbf{connected} if there exists a path from one to the other.
\subsection{Search Algorithms}
\textbf{Search algorithms} take as input a state space and a starting state, then tries to compute a path producing a search tree. The strategy consists of searching which node to add to the \textbf{fringe} and \textbf{expand}, considering all nodes reachable in one action from the current one.
\subsection{Search Tree}
A node in the \textbf{search tree} has five components:
\begin{itemize}
    \item The state
    \item The node that generated it
    \item The action that generates it
    \item The depth of the tree
    \item The cost of the path from the root
\end{itemize}
\subsection{General Search Algorithm}
The following is the procedure of a \textbf{general search algorithm} which takes as input a problem and a strategy and returns a sequence of operators that brings to the goal:
\begin{enumerate}
    \item Initialize the search tree
    \item Push the root node to the fringe
    \item Start the loop
    \item If the fringe is empty, then return failure
    \item Choose the next node according to a strategy
    \item If the node is the goal, then return the solution
    \item Expand the node
    \item Add the children to the fringe
\end{enumerate}
\subsection{Evaluation of Search Strategies}
The strategy determines the order of expansion and is evaluated according to the following criteria:
\begin{itemize}
    \item \textbf{Completeness}: does it always find a solution if it exists?
    \item \textbf{Space Complexity}: how much memory does it require?
    \item \textbf{Time Complexity}: how long does it take to find a solution?
    \item \textbf{Optimality}: does it guarantee the least-cost solution?
\end{itemize}
Time and space complexities are measured in terms of the maximum branching factor $b$, the depth of the least-cost solution $d$, and the maximum depth of the tree $m$. \\
\textbf{Polynomial-time algorithms} solve problems in a time growing polynomially with the input size. If such an algorithm does not solve the problem, then the problem is a \textbf{nondeterministic-polynomial time problem}.

\newpage

% ---------------------------------- %
% Section 2: Blind Search Algorithms %
% ---------------------------------- %
\section{Blind Search Algorithms}
\textbf{Blind search algorithms} use non informed search strategies.
% Algorithm 1: Breadth-first Search %
\subsection{Breadth-first Search Algorithm}
The \textbf{breadth-first search algorithm} expands the shallowest unexpanded node using a \textbf{FIFO} queue: nodes at distance $d$ are expanded before nodes at distance $d + 1$. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Push the root node to the fringe
    \item If the fringe is empty, then return failure
    \item Remove the first node $n$ from the fringe
    \item If $n$ is the goal, then return the solution
    \item If $n$ is in the explored list, then repeat from step 2
    \item Expand $n$
    \item Add the children at the end of the fringe
    \item Add $n$ to the explored list
    \item Repeat from step 2
\end{enumerate}
\vspace{0.2cm}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes
    \item Space Complexity: $O(b^d)$ - all nodes until goal depth are created
    \item Time Complexity: $O(b^d)$ - all nodes until goal depth are created
    \item Optimality: yes, if all steps have the same cost $g(n) = k \cdot depth(n)$
\end{itemize}
\newpage
% Algorithm 2: Uniform Cost Search %
\subsection{Uniform Cost Search Algorithm}
The \textbf{uniform cost search algorithm} associates a cost $g(n)$ to each node and expands the one with the lowest $g(n)$ in the fringe, which is kept sorted in increasing cost order. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Push the root node to the fringe
    \item If the fringe is empty, then return failure
    \item Remove the first node $n$ from the fringe
    \item If $n$ is the goal, then return the solution
    \item If $n$ is in the explored list, then repeat from step 2
    \item Expand $n$
    \item Add the children not in the explored list to the fringe
    \item Sort the fringe by the path cost
    \item Add $n$ to the explored list
    \item Repeat from step 2
\end{enumerate}
If $g(n)$ is equal to the depth of node $n$, then the procedure is the same of the breadth-first search algorithm. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes, if the step cost $\geq \epsilon > 0$
    \item Space Complexity: $O(b^d)$, number of nodes with $g(n) \leq g(\text{solution})$
    \item Time Complexity: $O(b^d)$, number of nodes with $g(n) \leq g(\text{solution})$
    \item Optimality: yes, if all costs are non-negative
\end{itemize}
\newpage
% Algorithm 3: Depth-first Search %
\subsection{Depth-first Search Algorithm}
The \textbf{depth-first search algorithm} expands a node generated in the previous step using a \textbf{LIFO} queue. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Push the root node to the fringe
    \item If the fringe is empty, then return failure
    \item Remove the first node $n$ from the fringe
    \item If $n$ is the goal, then return the solution
    \item If $n$ is in the explored list, then repeat from step 2
    \item Expand $n$
    \item Add the children not in the explored list to the beginning of the fringe
    \item Add $n$ to the explored list
    \item Repeat from step 2
\end{enumerate}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: no, it could proceed to infinity
    \item Space Complexity: $O(b \cdot m)$
    \item Time Complexity: $O(b^m)$
    \item Optimality: no, cannot be sure the first goal is the optimal one
\end{itemize}
\newpage
% Algorithm 4: Depth-limited Search %
\subsection{Depth-limited Search Algorithm}
The \textbf{depth-limited search algorithm} offers a solution to the completeness problem of the depth-first search algorithm by taking a parameter $deep$ in input. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Push the root node to the fringe
    \item If the fringe is empty, then return failure
    \item Remove the first node $n$ from the fringe
    \item If $n$ is the goal, then return the solution
    \item If $n$ not\_in(closed) and $d(n) < deep$, go ahead; otherwise repeat from step $2$
    \item Expand $n$
    \item Add the children not in the explored list to the beginning of the fringe
    \item Add $n$ to the explored list
    \item Repeat from step 2
\end{enumerate}
We can only reach solutions that are at distance $deep$ and we need knowledge to set a fixed $deep$. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes, if we know a bound to the solution depth
    \item Space Complexity: $O(b \cdot deep)$
    \item Time Complexity: $O(b^{deep})$
    \item Optimality: no
\end{itemize}
% Algorithm 5: Iterative-deepening Search %
\subsection{Iterative-deepening Search Algorithm}
The \textbf{iterative-deepening search algorithm} is a depth-limited search where the depth is incremented step by step. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Loop for depth $= 0$ to $\infty$
    \item If Depth-Limited-Search(root, deep) $==$ solution, then return the solution
    \item Return failure
\end{enumerate}
It is the best compromise between breadth-first search and depth-first search. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes, if we know a bound to the solution depth
    \item Space Complexity: $O(b \cdot d)$
    \item Time Complexity: $O(b^d)$
    \item Optimality: yes, if all costs are non-negative
\end{itemize}
\newpage
% Algorithm 6: Bidirectional Search %
\subsection{Bidirectional Search}
A problem related to search is its direction: in forward search, we start from the initial state and proceed to the goal, while in backward search, we start from the goal and go back to the initial state. The \textbf{bidirectional search algorithm} proceeds in both directions until the queues contain a common node. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Initialize two queues, one for forward and one for backward search
    \item Remove the first path from both the queues
    \item Create new paths to the children
    \item Reject a path if it contains a cycle
    \item Add the new paths to the queues
    \item Repeat from step 2 until the queues are not empty and do not share a node
    \item If the queues do not share a node, then return the solution
    \item Return Failure
\end{enumerate}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes
    \item Space Complexity: $O(b^{d/2})$
    \item Time Complexity: $O(b^{d/2})$
    \item Optimality: yes
\end{itemize}

\newpage

% -------------------------------------- %
% Section 3: Heuristic Search Algorithms %
% -------------------------------------- %
\section{Heuristic Search Algorithms}
Exhaustive blind search is not applicable for problems with exponential complexity. \textbf{Heuristic search algorithms} use problem specific strategies with some knowledge and experience to identify the most promising paths. These algorithms do not guarantee completeness and optimality, but find a good solution in a short time.
\subsection{Heuristic Evaluation}
The \textbf{heuristic evaluation} estimates the distance from a state to the goal:
\begin{center}
    $h : s \rightarrow R \ \ $ such that $\ \ h(goal) = 0$
\end{center}
\subsubsection{Admissible Heuristic}
In \textbf{admissible heuristic}, the cost estimated to reach the goal is not higher than the lowest possible cost from the current node. Given a node $n$, a cost $h(n)$ from $n$ to the goal, an optimal cost $h*(n)$ from $n$ to the goal, then we have the following relation:
\begin{center}
    $\forall n \ | \ h(n) \leq h*(n)$
\end{center}
\subsubsection{Monotonic Heuristic}
In \textbf{monotone heuristic}, for each node $n$ and its successor $n'$, the estimated cost of reaching the goal from a node $n$ is no longer greater than the step cost of getting its successor $n'$ plus the estimated cost of reaching the goal from $n'$. \\
Let $f(n)$ be the cost from the root to node $n$ plus the cost estimation from $n$ to the goal, then we have the following relation:
\begin{center}
    $f(n) \leq f(n')$
\end{center}
All monotonic heuristics guarantee local optimality and are admissible, but not all admissible heuristics are monotonic.
\newpage
% Algorithm 7: Best-first Search %
\subsection{Best-first Search Algorithm}
The \textbf{best-first search algorithm} evaluates each node estimating its distance to the goal, then sorts the fringe according to the estimated distances: the node with the best heuristic value is expanded. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Push the root node to the fringe
    \item If the fringe is empty, then return failure
    \item Remove the first node $n$ from the fringe
    \item If $n$ is the goal, then return the solution
    \item If $n$ is in the explored list, then repeat from step $2$
    \item Expand $n$
    \item Add the children not in the explored list to the fringe
    \item Add $n$ to the explored list
    \item Sort the fringe based on the heuristic values
    \item Repeat from step 2
\end{enumerate}
All nodes on the border are kept in memory. An alternative is represented by the beam-search algorithm that keeps in memory only the $k$ states with the best evaluation, where $k$ is the search radius. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: no
    \item Space Complexity: $O(b^{m})$
    \item Time Complexity: $O(b^{m})$
    \item Optimality: no
\end{itemize}
\newpage
% Algorithm 8: A* %
\subsection{A* Algorithm}
A algorithms are best-first search algorithms with an evaluation function:
\begin{center}
    $f(n) = g(n) + h(n)$, where $h(n) \geq 0$ and $h(goal) = 0$
\end{center}
There exist some particular cases:
\begin{itemize}
    \item if $f(n) = g(n)$, then it performs a uniform search
    \item if $f(n) = g(n) = depth(n)$, then it performs a breadth-first search
    \item if $f(n) = h(n)$, then it performs a greedy best first search
\end{itemize}
\textbf{A* algorithm} is an A algorithm with an admissible heuristic function:
\begin{center}
    $f*(n) = g*(n) + h*(n)$, where:
\end{center}
\begin{itemize}
    \item $f*(n) =$ estimated cost of the path from the root to the goal
    \item $g*(n) =$ cost of the path from the root to node $n$
    \item $h*(n) =$ estimated cost of the path from node $n$ to the goal
\end{itemize}
The algorithm expands the node with the smallest $f*(n)$. All nodes are kept in memory. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: yes, unless there are infinite nodes with $f \leq f(G)$
    \item Space Complexity: $O(b^m)$
    \item Time Complexity: $O(b^m)$
    \item Optimality: given a goal $p$
        \begin{enumerate}
            \item Assume that a path $p'$ is the shortest path to the goal
            \item Consider that a partial path $p''$ of $p'$ is on the fringe
            \item Since $p$ is expanded before $p''$, then we have:
                \begin{center}
                    $f(p) \leq f(p'')$
                \end{center}
            \item Since $p$ is a goal, then:
                \begin{center}
                    $g(p) \leq g(p'') + h(p'') $
                \end{center}
            \item Since $h$ is admissible, then for any path $p'$ that extends $p''$:
                \begin{center}
                    $g(p'') + h(p'') \leq g(p')$
                \end{center}
            \item Finally, for any path $p'$ to a goal:
                \begin{center}
                    $g(p) \leq g(p')$,
                \end{center}
                which contradicts point $1$.
        \end{enumerate}
\end{itemize}

\newpage

% ----------------------------------------- %
% Section 4: Constructive Greedy Algorithms %
% ----------------------------------------- %
\section{Constructive Greedy Algorithms}
\textbf{Constructive greedy algorithms} take a data set and fastly produce a feasible solution through the following procedure:
\begin{enumerate}
    \item Start from a random node $n$
    \item Expand $n$
    \item Choose the next node according to a local strategy
    \item Extend the solution with the new node, which becomes the new $n$
    \item Repeat from step $2$ until a feasible solution is computed
\end{enumerate}
% Algorithm 9: Nearest Neighbors %
\subsection{Nearest Neighbors Algorithm}
\textbf{Nearest neighbors algorithm} is one of the most common algorithms used to solve TSP problems. \\
Given $n$ cities, the algorithm performs the following procedure,
\begin{enumerate}
    \item Consider a starting tour consisting of a random city $c_i$
    \item Add the closest new city $c_{i+1} $ to the tour
    \item Repeat step $2$ until no new cities are available
\end{enumerate}
The algorithm is not very efficient: the first edges are short while the final ones are long and the length of the tour with respect to the optimal tour length grows following a $log(n)$ formula. \\
The algorithm is evaluated as follows:
\begin{itemize}
    \item Completeness: no
    \item Space Complexity: $O(n^2)$
    \item Time Complexity: $O(n^2)$
    \item Optimality: no, cannot guarantee optimality
\end{itemize}
\newpage
% Algorithm 10: Multi Fragment %
\subsection{Multi Fragment Algorithm}
\textbf{Multi fragment algorithm} starts with the shortest edge and add the others in increasing order only if they do not create a $3-$degree city. \\
The procedure of the algorithm is the following:
\begin{enumerate}
    \item Sort the edge costs in ascending order
    \item Consider a tour with the the first edge
    \item Add a new edge in incremental order only if it does not create a cycle
    \item Repeat step $3$ until no edge is available
\end{enumerate}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^2log(n))$
    \item Time Complexity: $O(n^2log(n))$
    \item Optimality: no, cannot guarantee optimality
\end{itemize}
% Algorithm 11: Nearest Insertion %
\subsection{Nearest Insertion Algorithm}
Given $n$ cities, the \textbf{nearest insertion algorithm} performs the following procedure:
\begin{enumerate}
    \item Build an initial tour formed by the nearest cities
    \item Choose a new node such that the Euclidean distance between it and two other nodes in the tour is minimum
    \item Insert the new node between the two nodes of the tour
    \item Repeat from step $2$ until no new nodes are available
\end{enumerate}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^2)$
    \item Time Complexity: $O(n^2)$
    \item Optimality: no, cannot guarantee optimality
\end{itemize}
\newpage
% Algorithm 12: Farthest Insertion %
\subsection{Farthest Insertion Algorithm}
Given $n$ cities, the \textbf{farthest insertion algorithm} performs the following procedure:
\begin{enumerate}
    \item Build an initial tour formed by the farthest cities
    \item Choose a new node such that the Euclidean distance between it and two other nodes in the tour is maximum
    \item Insert the new node between the two nodes of the tour
    \item Repeat from step $2$ until no new nodes are available
\end{enumerate}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^2)$
    \item Time Complexity: $O(n^2)$
    \item Optimality: no, cannot guarantee optimality
\end{itemize}

\newpage

% ----------------------- %
% Section 5: Local Search %
% ----------------------- %
\section{Local Search Algorithms}
\textbf{Local search algorithms} optimize solutions through the following procedure:
\begin{enumerate}
    \item Start from a complete solution
    \item Compute the best edge exchange that improves the solution
    \item Execute the edge exchange
    \item Repeat from step $2$ until no improvement is possible
\end{enumerate}
% Algorithm 13: Hill Climbing %
\subsection{Hill Climbing Algorithm}
Given a set of solutions $A$ and the following objective function $f$:
\begin{center}
    $\{f(s) \ | \ s \in A \}$,
\end{center}
then, we define a neighborhood function $N$ that computes a subset of solutions $N(s)$ for each solution $s \in A$:
\begin{center}
    $A \ \rightarrow \ 2^A$,
\end{center}
The \textbf{hill climbing algorithm} explores the neighborhood searching for a better solution. \\
We can perform two different types of search: a greedy search and a simplified version. In the greedy search, if the best solution in $N(current)$ is better than the actual best, then we restart from current, otherwise we stop. In the simplified version, as soon as we find a better neighborhood we continue the search from the associated solution, avoiding to visit all the neighborhoods. \\
The procedure of the algorithm is the following:
\begin{verbatim}
    function Hill-Climbing(s, f, N):
        current = s
        while terminating condition is met:
            next = best solution in N(current)
            if f(next) < f(current):
                current = next
        return current
\end{verbatim}
\newpage
% Algorithm 14: 2-opt %
\subsection{2-opt Algorithm}
The \textbf{2-opt algorithm} removes crossings in the solution path by reversing the path between two nodes. The implementation of the \textbf{2-opt algorithm} is the following:
\begin{verbatim}
    function 2-Opt():
        while best_gain != 0:
            best_gain = 0
            for i = 1 to n:
                for j = 1 to n:
                    gain = Compute-Gain(i,j)
                    if (gain < best_gain):
                        best_gain = gain
                        best_i = i
                        best_j = j
                        if first_improvement == true:
                            break
                if (best_gain < 0 && first_improvement == true):
                    break
            Exchange(best_i, best_j)
\end{verbatim}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^2)$
    \item Time Complexity: $O(n^2)$
\end{itemize}
% Algorithm 15: 2.5-opt %
\subsection{2.5-opt Algorithm}
The \textbf{2.5-opt algorithm} performs the 2-opt algorithm procedure and check for two possible node shifts. A shift consists of moving a node to another position in the tour.
\begin{figure}[H]
    \centering
    \includegraphics[width=7cm]{Screen Shot 2022-01-28 at 18.11.07.png}
\end{figure}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^2)$
    \item Time Complexity: $O(n^2)$
\end{itemize}
\newpage
% Algorithm 16: 3-opt %
\subsection{3-opt Algorithm}
The \textbf{3-opt algorithm} removes three links form the tour, obtaining three segments with which we check for 2-opt swaps and node shifts.
\begin{figure}[H]
    \centering
    \includegraphics[width=7cm]{2.png}
\end{figure}
The algorithm is evaluated as follows:
\begin{itemize}
    \item Space Complexity: $O(n^3)$
    \item Time Complexity: $O(n^3)$
    \item Optimality: TODO
\end{itemize}

\newpage

% -------------------------------- %
% Section 6: Meta-heuristic Search %
% -------------------------------- %
\section{Meta-heuristic Search Algorithms}
Local search algorithms cannot escape from a local minimum and they are inefficient with large neighborhoods, thus either we accept solutions that are worst than the previous, or stochastically explore only a subset of a neighborhood through \textbf{meta-heuristic search algorithms}. \\
Meta-heuristic search algorithms have the following implementation:
\begin{verbatim}
    function Meta-Heuristic-Search(s, f, N):
        current = s
        while terminating condition is met:
            stochastically compute next in N(current)
            Decide whether to continue with current = next
        return current
\end{verbatim}
% Algorithm 17: Simulated Annealing %
\subsection{Simulated Annealing Algorithm}
The \textbf{simulated annealing algorithm} is inspired by the natural annealing of metals and has the following procedure:
\begin{enumerate}
    \item Start from an initial solution
    \item Choose a random new solution from the neighborhood of the previous
        \begin{itemize}
            \item If $f(next) < f(current)$, then $f(next)$ becomes the current node
            \item Otherwise, given a temperature $T$, we use the following probabilistic function:
                \begin{center}
                    $\Delta E = f(next) - f(current)$ \\
                    $e^{-\frac{\Delta E}{T}}$
                \end{center}
        \end{itemize}
    \item Repeat from step $2$ until the solution converges
\end{enumerate}
Given a value $\beta = [1, 100]$, then the steps $L$ from one temperature to the next are computed as follows:
\begin{center}
    $L=\beta \cdot NN_length$
\end{center}
Given the number of nodes $n$, then the temperature $T$ is initialized as follows:
\begin{center}
    $T = \displaystyle\frac{L}{\sqrt{n}}$
\end{center}
Given a value $\alpha \approx 0.95$, then $T$ decreases during the search as follows:
\begin{center}
    $T_{i + 1} = T \cdot \alpha$
\end{center}

\newpage

% ----------------------------- %
% Section 7: Genetic Algorithms %
% ----------------------------- %
\section{Genetic Algorithms}
\textbf{Genetic algorithms} are based on Darwin's evolution theory: individuals are described by chromosomes, which are sets of genes; populations are sets of individuals; generations are sets of populations. \\
Individuals are evaluated using a \textbf{fitness function} representing their adaptation to the environment. \\

Genetic algorithms have the following implementation:
\begin{verbatim}
    function Genetic-Algorithms():
        t = 0
        initialize P(t) with m individuals
        evaluate P(t)
        while terminating condition is not met:
            select parents from P(t)
            generate individuals with reproduction rules
            some individuals die in P(t)
            form population P(t + 1)
            evaluate population P(t + 1)
            t = t + 1
        return best individual
\end{verbatim}
% Algorithm 18: Ant Colony %
\subsection{Ant Colony Algorithm}
The basic principle of the \textbf{ant colony algorithm} is \textbf{stigmergy}, meaning a particular kind of indirect communication through which ants find the shortest paths in dynamic environments:
\begin{itemize}
    \item While moving between nest and food, ants mark their path by pheromone laying
    \item Step-by-step decisions are biased by stigmergy
\end{itemize}
Pheromone is the colony’s collective and distributed memory: it encodes the quality of local routing choices towards the destination target. \\
The implementation of the algorithm is the following:
\begin{verbatim}
    function Ant-Colony():
        while terminating condition is not met:
            randomly position m ants on n cities
            for city = 1 to n:
                for ant = 1 to m:
                    select next city through exploration and exploitation
                    apply local trail updating rule
                compute length l of tour generated by m
            apply global trail updating rule using best ant
\end{verbatim}
\end{document}


% Options for packages loaded elsewhere
\PassOptionsToPackage{unicode}{hyperref}
\PassOptionsToPackage{hyphens}{url}
\PassOptionsToPackage{dvipsnames,svgnames*,x11names*}{xcolor}
%
\documentclass[
]{article}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[T1]{fontenc}
  \usepackage[utf8]{inputenc}
  \usepackage{textcomp} % provide euro and other symbols
\else % if luatex or xetex
  \usepackage{unicode-math}
  \defaultfontfeatures{Scale=MatchLowercase}
  \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
\fi
% Use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\IfFileExists{microtype.sty}{% use microtype if available
  \usepackage[]{microtype}
  \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
}{}
\makeatletter
\@ifundefined{KOMAClassName}{% if non-KOMA class
  \IfFileExists{parskip.sty}{%
    \usepackage{parskip}
  }{% else
    \setlength{\parindent}{0pt}
    \setlength{\parskip}{6pt plus 2pt minus 1pt}}
}{% if KOMA class
  \KOMAoptions{parskip=half}}
\makeatother
\usepackage{xcolor}
\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}}
\hypersetup{
  colorlinks=true,
  linkcolor=blue,
  filecolor=Maroon,
  citecolor=Blue,
  urlcolor=Blue,
  pdfcreator={LaTeX via pandoc}}
\urlstyle{same} % disable monospaced font for URLs
\usepackage{longtable,booktabs}
% Correct order of tables after \paragraph or \subparagraph
\usepackage{etoolbox}
\makeatletter
\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
\makeatother
% Allow footnotes in longtable head/foot
\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
\makesavenoteenv{longtable}
\setlength{\emergencystretch}{3em} % prevent overfull lines
\providecommand{\tightlist}{%
  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\setcounter{secnumdepth}{-\maxdimen} % remove section numbering
\ifluatex
  \usepackage{selnolig}  % disable illegal ligatures
\fi

\author{}
\date{}

\begin{document}

\newcommand{\Ber}{\operatorname{Ber}}
\newcommand{\E}{\operatorname{E}}
\newcommand{\V}{\operatorname{Var}}
\newcommand{\diag}{\operatorname{diag}}

\newcommand{\bi}{\mathbf{i}}
\newcommand{\bj}{\mathbf{j}}
\newcommand{\bt}{\mathbf{t}}

\newcommand{\bw}{{\boldsymbol{w}}}
\newcommand{\bv}{{\boldsymbol{v}}}
\newcommand{\bx}{{\boldsymbol{x}}}
\newcommand{\by}{{\boldsymbol{y}}}
\newcommand{\bb}{{\boldsymbol{b}}}
\newcommand{\bz}{{\boldsymbol{z}}}
\newcommand{\bu}{{\boldsymbol{u}}}
\newcommand{\bX}{{\boldsymbol{X}}}
\newcommand{\bY}{{\boldsymbol{Y}}}
\newcommand{\bZ}{{\boldsymbol{Z}}}

\hypertarget{logistic-regression}{%
\section{Logistic Regression}\label{logistic-regression}}

\hypertarget{maximum-likelihood}{%
\subsection{Maximum Likelihood}\label{maximum-likelihood}}

Suppose that there is a coin which you think is biased. You flip the
coin 10 times and get 7 heads. Based on this \emph{training data} set,
what is the probability that the next flip will show heads? Can you
justify your answer? To provide a rigorous argument for the answer, we
can use \emph{maximum likelihood} estimation.

Let \(y\) be the (unknown) probability that the biased coin shows heads.
Since there are only two possible results, heads or tails, the
experiment of flipping the coin is a \emph{Bernoulli trial}. We
introduce a \emph{Bernoulli random variable} \(T\) such that
\[ T=\begin{cases} 1 & \text{ if the coin shows heads}, \\ 0 & \text{ otherwise}. \end{cases} \]
with probabilities
\[ P(T=1|y)=y \quad \text{ and } \quad P(T=0|y)=1-y, \quad \text{respectively}. \]\\
The probability mass function \(\operatorname{Ber}(t|y)\) of \(T\) can
be written in a compact way
\[ \operatorname{Ber}(t|y)= y^t (1-y)^{1-t}, \quad t=0,1. \]

Suppose that we have a data set \(\mathcal D = \{ t_1, \dots, t_N \}\)
of observed values of \(t \in \{ 0,1 \}\) from \(N\) coin flips. Let
\(p(\mathcal D|y)\) denote the probability of getting the set
\(\mathcal D\), given that \(y\) is the probability of the biased coin.
Since each flip is independent, the probability \(p(\mathcal D|y)\) is
equal to the product of the probabilities of individual flip results,
i.e.~it is equal to
\[ p(\mathcal D| y) = \prod_{n=1}^N \operatorname{Ber}(t_n|y) = \prod_{n=1}^N y^{t_n} (1-y)^{1-t_n}. \]

This is a function of \(y\); as \(y\) varies, the function
\(p(\mathcal D|y)\) produces probabilities of the set \(\mathcal D\)
determined by \(y\). When \(p(\mathcal D|y)\) is maximized at a value
\(y_{\mathrm {ML}}\), we can say that \(y_{\mathrm{ML}}\) has maximum
likelihood based on the data st \(\mathcal D\). This leads to

\[\text{ Task: Estimate a value $y_{\mathrm{ML}}$ that maximizes $p(\mathcal D|y)$}.\]

This is a Calculus problem. We will have to take the derivative of the
function. Before taking the derivative, we notice that the expression of
\(p(\mathcal D|y)\) is a product of exponentials. In such a situation,
logarithmic differentiation can be used to ease the process of
differentiation, and the function can be replace by
\[ \ln p(\mathcal D|y) = \sum_{n=1}^N \{ t_n \ln y + (1-t_n) \ln(1-y)\} .\]

Now we take the derivative of the function with respect to \(y\), set
the derivative equal to \(0\) to obtain a critical value, and check if
the critical value \(y_{\mathrm{ML}}\) indeed maximizes the function.
The result is \[ y_{\mathrm{ML}}= \frac 1 N \sum_{n=1}^N t_n ,\] which
is the \emph{sample mean}. This justifies our guess for a biased coin in
a rigorous way.

\textbf{Exercises}: Provide details to obtain \(y_{\mathrm{ML}}\).

\hypertarget{logistic-regression-1}{%
\subsection{Logistic Regression}\label{logistic-regression-1}}

Suppose that there is a certain test (similar to GRE) for undergraduate
students who apply for a graduate program. The test score is used for
admission decisions as one of the credentials that students provide. The
table below shows the test scores of 10 students and the results of
admission decisions:

\begin{longtable}[]{@{}llcccccccccc@{}}
\toprule
\(x\) & : & \(272\) & \(331\) & \(295\) & \(287\) & \(315\) & \(266\) &
\(303\) & \(294\) & \(317\) & \(309\)\tabularnewline
\midrule
\endhead
\(t\) & : & 0 & 1 & 1 & 0 & 1 & 0 & 0 & 0 & 1 & 1\tabularnewline
\bottomrule
\end{longtable}

\(x\) test score \(t=1\) accepted \(t=0\) rejected

There is a student whose test score is 299. Based on this data, what is
the probability that the student will be accepted? If the probability is
greater than 0.5, we can \emph{classify} the student as \emph{Likely
Accepted}; if the probability is less than 0.5, as \emph{Likely
Rejected}. This type of problem is called \emph{classification} problem
in machine learning.

An answer to this problem would have a procedure to produce probability
\(y\) from a test score \(x\). A typical way to obtain a number between
0 and 1 (that is to be considered as probability) from an arbitrary real
number is to use a function whose graph is an \(S\)-shaped curve, called
a \emph{sigmoid} function. One of the most common sigmoid functions is
the \emph{logistic} function
\[ \sigma(x) = \frac {e^x}{e^x+1}= \frac 1 {1+e^{-x}}.\]

\begin{center}
\includegraphics[scale=0.2]{sigmoid}

Graph of the Logistic Function

\end{center}

Clearly, the logistic function converts \((-\infty, \infty)\) to
\((0,1)\). In particular, the value \(0\) corresponds to \(0.5\). In
order to apply this function to\\
a data set \(\{ x_1, x_2, \dots , x_N \}\) of test scores, we need to
first adjust the values \(x_n\) in accordance with the logistic function
in such a way that the result will best fit for the given data. Here we
make an assumption that the adjustment is done by a linear function of
the form \(w_1 x_n+w_2\) so that the probability is given by
\begin{equation} \label{eqn-y-sigma} y_n=\sigma(w_1 x_n +w_2). \end{equation}
This is the same kind of assumption we made for linear regression.

Now the question is how to find the best values of \(w_0\) and \(w_1\).
Let us consider the likelihood of the given data set. Each score \(x\)
corresponds to probability \(y\) and a student with score \(x\) will be
accepted with probability \(y\) through a Bernoulli trial. It is like
flipping a biased coin with probability \(y\). Given a data set
\(\mathcal D=\{(x_n, t_n) \}\), \(t_n \in \{ 0,1 \}\),
\(n=1, \dots, N\), we compute the likelihood of this data set and obtain
\[ p(\mathcal D|{\boldsymbol{w}}) =  \prod_{n=1}^N \operatorname{Ber}(t_n|y_n) = \prod_{n=1}^N y_n^{t_n} (1-y_n)^{1-t_n}, \]
where \(y_n= \sigma(w_1 x_n+w_2)\) and \({\boldsymbol{w}}=(w_1, w_2)\).
The best choice of \({\boldsymbol{w}}=(w_1,w_2)\) would make this
likelihood the largest possible. Thus our task can be summarized as the
following.

\[\text{Task: Determine ${\boldsymbol{w}}$ that maximize the likelihood $p(\mathcal D|{\boldsymbol{w}})$.}\]

Test scores make only one feature in credentials for admission
decisions. Assume that there are \(k\) different features.\\
Then the \(n^\mathrm{th}\) student provides credentials
\(\{x_{n1}, x_{n2}, \dots , x_{nk} \}\), and as in linear regression,
the credentials of \(N\) student can be arranged into a matrix
\[  \begin{bmatrix} x_{11} & x_{12} & \cdots & x_{1k} \\ x_{21} & x_{22} & \cdots & x_{2k} \\ \vdots & \vdots & & \vdots \\ x_{N1} & x_{N2} & \cdots & x_{Nk} \end{bmatrix} .\]

The function in \eqref{eqn-y-sigma} is now generalized to
\[ y_n=\sigma(w_1 x_{n1}+ w_2 x_{n2} + \cdots + w_k x_{nk}+w_{k+1} ), \]
which can be rewritten in vector notations
\[ y_n= \sigma( {\boldsymbol{x}}_n{\boldsymbol{w}}) ,\] where
\({\boldsymbol{w}}=(w_1, w_2, \dots , w_k, w_{k+1})^\top\) is a column
vector to be determined and
\({\boldsymbol{x}}_n=(x_{n1}, x_{n2}, \dots, x_{nk}, 1)\). With
\(y_n=\sigma({\boldsymbol{x}}_n {\boldsymbol{w}})\), the expression of
the likelihood \(p(\mathcal D| {\boldsymbol{w}})\) does not change.

To make differentiation easier, we take the logarithm of the likelihood
and want to determine \({\boldsymbol{w}}\) which maximizes
\[ \ln p(\mathcal D|{\boldsymbol{w}}) = \sum_{n=1}^N \{ t_n \ln y_n + (1-t_n) \ln (1-y_n)\} ,\]
or equivalently, \({\boldsymbol{w}}\) which minimizes
\[ E({\boldsymbol{w}}):=-\ln p(\mathcal D|{\boldsymbol{w}}) = -\sum_{n=1}^N \{ t_n \ln y_n + (1-t_n) \ln (1-y_n)\} .\]
This problem is called \emph{logistic regression}.

Unlike linear regression, it is difficult to find closed form formulas
for solutions to this problem. Instead, we will use an inductive process
to approximate the vector \({\boldsymbol{w}}\). We will study two
standard methods for this purpose, called \emph{Gradient Descent} and
\emph{Newton's Method}.

\hypertarget{applying-newtons-method-to-the-logistic-regression}{%
\subsection{Applying Newton's method to the logistic
regression:}\label{applying-newtons-method-to-the-logistic-regression}}

\[ E ({\boldsymbol{w}}) = - \ln p(t|{\boldsymbol{w}}) = - \sum_{n=1}^N \{ t_n \ln y_n + (1-t_n) \ln (1-y_n)\},  \]
where
\(y_n=\sigma(a_n)=\sigma({\boldsymbol{x}}_n^\top {\boldsymbol{w}})\).
Note that \(\sigma'(x) =\sigma(x)(1-\sigma(x))\).

\begin{itemize}
\item
  We obtain
  \[ \nabla E({\boldsymbol{w}})= \sum_{n=1}^N (y_n-t_n){\boldsymbol{x}}_n = X ({\boldsymbol{y}}-\mathbf{t}), \]
  where
  \[ X=\begin{bmatrix} 1 &1 & \cdots &1 \\ x_1 & x_2 & \cdots &x_N \\ x_1^2 & x_2^2 & \cdots &x_N^2 \\ \vdots & \vdots & & \vdots \\ x_1^D & x_2^D & \cdots &x_N^D \\\end{bmatrix}, \quad {\boldsymbol{y}}=\begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_N \end{bmatrix}, \quad \text{ and } \mathbf{t}= \begin{bmatrix} t_1 \\ t_2 \\ \vdots \\ t_N \end{bmatrix} . \]
\item
  We also get
  \[ \mathbf H = \sum_{n=1}^N y_n(1-y_n) {\boldsymbol{x}}_n {\boldsymbol{x}}_n^\top = X R X^\top, \]
  where \(R=\operatorname{diag}( y_n(1-y_n))\).
\item
  Then we have
  \[ \boxed{{\boldsymbol{w}}_{k+1} = {\boldsymbol{w}}_k - (X R X^\top)^{-1} X ({\boldsymbol{y}}-\mathbf{t}) },\]
  where \(R\) and \({\boldsymbol{y}}\) are determined by
  \({\boldsymbol{w}}_k\) in each step.
\end{itemize}

\hypertarget{example-test-scores-and-admission-to-a-graduate-school}{%
\subsubsection{Example: Test scores and admission to a graduate
school}\label{example-test-scores-and-admission-to-a-graduate-school}}

\begin{itemize}
\item
  Chose \(D=1\). Then \(a_n=w_0+w_1x_n\). \textbackslash{} From the
  data, we have \[X= \begin{bmatrix}  1 &1 &1 &1 &1 &1 &1 &1 &1 &1 \\
  272&331&295&287&315&266&303&294&317&309 \end{bmatrix}, \] and
  \[ \mathbf{t}=[0,1,1,0,1,0,0,0,1,1]^\top .\]
\item
  Choose \({\boldsymbol{w}}_0=(0,0)^\top\). Then \({\boldsymbol{w}}_k\)
  converges to \[ (-57.2937, 0.1910)^\top .\]
\item
  If \(x=299\), then the probability of admission is
  \[ y= \sigma(-57.2937+ 0.1910*299) \approx 0.454 .\]
\end{itemize}

\end{document}


\subsubsection{Long Bow}\label{weapon:longBow}
Weapon, Bow, Two-Handed, Regular, Ranged\\
Size: L\\
Ammunition: Arrows\\
Cost: 150 Gold

\textbf{Draw/Sheath}\\
6AP draw, 10AP sheath

\textbf{Shoot}\\
4 AP, DE to hit, \passus{150} Reach\\
3d10 + \sfrac{1}{2}\texttimes DE Piercing Damage

\textbf{Reload}\\
3 AP from a back quiver\\
2 AP from a belt quiver\\
The Bow has to be reloaded after every shot.


\lab{Python}{Getting Started}{Getting Started}
\objective{To introduce basic coding procedures and objects usage in Python.}
\label{lab:Essential_Python}

\section*{Prerequisites}
Python 2.7 is required for the labs in this text and can be downloaded and
installed from \url{http://www.python.org/}. Although later versions of
Python are available, they do not have many of the features needed for scientific computing.

There are many IDEs (Integrated Development Environments) and text editors freely
available. We recommend you use the IPython.
IPython provides three different interfaces: commandline, QTConsole, and Notebook.
The commandline interface is the simplest of the three.
It displays code in nice colorized terminal and it very easy to use.
It can be started by simply running \li{ipython}.
The QTConsole is an enriched commandline interface.
It provides some extra features not available in the commandline interface while be just as easy to use.
You can run the QTConsole interface by executing \li{ipython qtconsole}.
The Notebook interface provides the most comprehensive list of features and is display via a web browser.
The Notebook interface can be launched by running \li{ipython notebook}.
This will start the Notebook server and launch a web browser with the interface.

For more information on downloading and installing various packages, please
reference the Appendix.

\section*{Python}
Python is a powerful general-purpose programming language. It is an interpreted
language and can even be used interactively.
It is quickly gaining momentum as a fundamental tool in scientific computing and has several
very nice key features:
\begin{itemize}
\item Clear, readable syntax
\item Full object orientation
\item Complete memory management (via garbage collection)
\item High level, dynamic datatypes
\item Extensibility via C
\item Ability to interface to other languages such as R, C, C++, and Fortran
\item Embeddability in applications
\item Portability across many platforms (Linux, Windows, Mac OSX)
\item Open source
\end{itemize}
In addition to these, Python is freely available and can also be freely distributed.

\section*{Learning Python}
The following examples and problems highlight important functionality and syntax in Python, but they are by no means comprehensive.  As you familiarize yourself with Python and begin to work through the problems, we \emph{strongly} suggest you read the following:
\begin{enumerate}
\item Chapters 3, 4, and 5 of the Official Python Tutorial \\
        (\url{http://docs.python.org/2.7/tutorial/introduction.html}).
\item Section 1.2 of the SciPy Lecture Notes \\
        (\url{http://scipy-lectures.github.io/}).
\item PEP8 - Python Style Guide \\
        (\url{http://www.python.org/dev/peps/pep-0008/}).
\end{enumerate}

Python is a high-level programming language with powerful capabilities.
It has many standard data types including numbers, strings, lists, sets, and dictionaries.

\subsection*{Numbers}
\begin{example}
As its name suggests, number data types store numeric values.
They are immutable data types, meaning that changing the value results in a
newly allocated variable.
There are four different numerical types: \li{int}, \li{long},
\li{float}, and \li{complex}

\begin{lstlisting}
# Comments will frequently be provided in this form.
>>> '''
... It is also possible to use multiline strings as comments.
... These are enclosed by 3 single (or double) quotations at
... the beginning and end.
... '''

# Python can be used as a simple calculator.
>>> 9*8 + 4*5
92

# Integer division returns the floor.
>>> 100/3
33

>>> # Use floating points to ensure the floating point operand.
>>> 100/3.0
33.333333333333336

# Use variables to store values.
>>> x = 12
>>> y = 2 * 6

# It is possible to check equality between two variables.
>>> x == y
True

# It is also possible to assign multiple values to multiple variables
>>> a, b, c = 1, 2, 3
>>> (a + b)**c       # Exponentiation is denoted '**'
27
\end{lstlisting}
\end{example}

\begin{problem}
It is very useful to know how to find help in the official Python documentation  (\url{https://docs.python.org/2.7/}). Use the official Python documentation to find the answer to each of the following:

\begin{enumerate}

\item What are the two ways to create a complex number?
How do you extract just the real part and just the imaginary part?
\item How would you cast an integer as a float?
\item Is there a way to explicitly express integer division when using floats?
\end{enumerate}
\end{problem}

\begin{problem}
Why does \li{7/3} return 2 in Python 2.7?
\end{problem}

\subsection*{Strings}
\begin{example}
A string in Python is an ordered sequence of characters
between quotation marks. Those quotation marks may be single or double quotes, but must be the same at both ends of the string.
Strings can be concatenated using the \li{+} operator. We can also access substrings and individual characters using brackets. To do this we identify the n\textsuperscript{th} character of string, S, by \li{S[n-1]}, or the i\textsuperscript{th} through the j\textsuperscript{th} character by \li{S[i:j+1]}.
The index of a string of length $n$ starts with zero and goes to $n-1$ at the end.
Python allows the use of negative indices to index the from the end of a string.
The last character of any string can be accessed by $n-1$ or $-1$.
All ranges in Python include the origin point and exclude the end point
Also, the string datatype is immutable, meaning it cannot be modified in place once it has been allocated.
Changing a string creates a new copy of the string with the change.
We can cast other values in Python, like integers and  to strings using the \li{str()} function.
\begin{lstlisting}
>>> str1 = "I love"
>>> str2 = "the ACME program"
>>> str3 = "puppies and baby ducks"
>>> str4 = "sleeping"

# The + operator concatenates strings.
>>> str1 + " " + str2 + "!"
'I love the ACME program!'

# To slice, [start:stop:step].
>>> my_string = "T" + str2[1:4] + str3[:9] + "re " + str4 + "."
>>> my_string
'The puppies are sleeping.'
>>> my_string[:-1:2]
'Teppisaesepn'
\end{lstlisting}
\end{example}

\begin{problem}
Answer the following questions. Use the official Python documentation, if necessary.
\begin{enumerate}
\item What does it mean for a string to be an immutable object?
\item What happens in \li{my_string[::3]} and \li{my_string[::-1]} provided
that \li{my_string = "I love the new ACME program\!"}?
\item How can you access the entire string in reverse?
\end{enumerate}
\end{problem}

\subsection*{Lists}
\begin{example}
The list data type is very versatile.
It is represented as comma separated values enclosed within square brackets.
It is a mutable sequence allowing in-place modification after creation.
The values within a list can be different data types and are accessed using the
slice operator (just like strings). There are many methods available to the list data type.
There are various methods for interacting with lists and some are demonstrated below, including \li{append}, \li{insert}, \li{remove}, \li{sort}, and \li{reverse}.

\begin{lstlisting}
>>> my_list = ["Remi", 21, "08/06", 1993]
>>> my_list[0]
'Remi'
>>> my_list[2] =  "07/10"
>>> my_list
['Remi', 21, '07/10', 1993]

# Append adds an item to the end of your list.
>>> my_list.append("student")
>>> my_list
['Remi', 21, '07/10', 1993, 'student']

# The insert method takes an item and inserts it at a given position.
>>> my_list.insert(0, "female")
>>> my_list
['female', 'Remi', 21, '07/10', 1993, 'student']

# The remove method takes an item and removes it from your list.
>>> my_list.remove("student")
>>> my_list
['female', 'Remi', 21, '07/10', 1993]

# Lists can also be sorted in place. This changes the original list.
>>> my_list.sort()
>>> my_list
[21, 1993, '07/10', 'Remi', 'female']

# Lists can also be reversed.
>>> my_list.reverse()

# List comprehensions provide a powerful way to create lists.
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

\end{lstlisting}
The last example, list comprehensions, is very important. Whenever possible, you should try to create your lists using list comprehension, rather than with a for loop (for loops will be discussed later in this lab).
\label{exp:lists}
\end{example}

\begin{problem}
Answer the following questions. Use the official Python documentation, if necessary.

\begin{enumerate}
\item What is the difference between mutable and immutable objects?
\item If \li{my_list = ["mushrooms", "rock climbing", 1947, 1954, "yoga"]}
\begin{enumerate}
\item How would you access ``yoga''?
\item How would you view a copy of \li{my_list}?
\item How would you clear the entire list?
\item How would you find the length?
\item How would you assign ``mushrooms'' (first entry) and ``rock climbing'' (second entry)
of \li{my_list} to ``Peter Pan'' and ``camelbak''?
(This should be done in one line of code and is known as a slice assignment)
\item How would you add ``Jonathan, my pet fish'' to the end of the list?
\end{enumerate}
\item What sequence of commands would you use to implement the following?
\begin{enumerate}
\item Create an empty list.
\item Add 5 integers to your list.
\item Cast the integer at index 3 as a float.
\item Remove the integer at index 2.
\item Sort your list backwards.
\end{enumerate}
\end{enumerate}
\end{problem}

\subsection*{Sets}
\begin{example}
Sets are an unordered collection of distinct objects.
It is a mutable object that allows objects to be added and removed after creation.
Like mathematical sets, Python supports operations like union, intersection,
difference, and symmetric difference.
\begin{lstlisting}
>>> gym_members = set(["Doe, John", "Smith, Jane", "Brown, Bob", "Jones, Sally"])
>>> gym_members
{'Brown, Bob', 'Doe, John', 'Jones, Sally', 'Smith, Jane'}
>>> gym_members.add("Lytle, Josh")
>>> gym_members.add("Doe, John")
>>> gym_members
{'Brown, Bob', 'Doe, John', 'Jones, Sally', 'Lytle, Josh', 'Smith, Jane'}
>>> library_members = set(["Lytle, Jane", "Henriksen, Ian", "Smith, Jane", "Grout, Ryan"])
>>> library_members.discard("Smith, Jane")
>>> library_members.add("Lytle, Josh")
>>> library_members
{'Grout, Ryan', 'Henriksen, Ian', 'Lytle, Jane', 'Lytle, Josh'}

# Note that, by default, set intersection returns a new set object.
>>> inter = set.intersection(gym_members, library_members)
>>> inter
{'Lytle, Josh'}
>>> library_members & gym_members
{'Lytle, Josh'}

# We can also do set comprehensions like we did list comprehensions.
>>> my_set = set(x**2 for x in range(10))
>>> my_set
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
\end{lstlisting}
\end{example}

\begin{problem}
Answer the following questions. Use the official Python documentation, if necessary.
\begin{enumerate}
\item What are the two ways to create sets? How do you create an empty set?
\item How would you take the union of sets?
\item How are sets indexed?
\end{enumerate}
\end{problem}

\subsection*{Dictionaries}
\begin{example}
Dictionaries are another unordered data type.  They are useful for storing key-value pairs.
\begin{lstlisting}
>>> tel = {"accounting":4234, "admissions": 2507, "financial aid": 4104, "marriott": 4121, "math": 2061, "visual arts" : 7321}
>>> tel["math"]
2061
>>> tel.keys()
['visual arts',
 'admissions',
 'financial aid',
 'accounting',
 'marriott',
 'math']
\end{lstlisting}
\end{example}

\begin{problem}
Answer the following questions. Use the official Python documentation, if necessary.
\begin{enumerate}
\item What are the two ways to create dictionaries? How do you create an empty dictionary?
\item In Example \ref{exp:lists} above we created a list using list comprehension. Create a dictionary using a dictionary comprehension to produce the following output:

\begin{lstlisting}
{2: 4, 4: 16, 6: 36, 8: 64, 10: 100}
\end{lstlisting}

(Note that because dictionaries are unordered, your output may not exactly the output displayed above)
\item How do you delete a key-value pair?
\item How do you access a list of all the values in your dictionary?
How do you access a list of all the items?
\end{enumerate}
\end{problem}

\section*{Control Flow Tools}
Control flow blocks control the order in which your code is executed.
Python supports the usual control flow statements used in other languages
including while loops, if statements, for loops, and function definitions.

\subsection*{The while Loop}
\begin{example}
A while loop executes the indented block of code following it \emph{while} the given condition holds. Python uses indentation to identify the beginning and end of blocks of code, so be careful to indent the same amount of space for each line of an execution block.
Most Python code uses a standard of four spaces to indent blocks of code.
Be aware that you can create an infinite loop if the condition is always true.

\begin{lstlisting}
# The Fibonacci sequence can be formulated using a while statement.
>>> a, b = 0, 1
>>> while b < 10:           # The trailing colon is required.
...     print a             # This indented line is executed if b<10.
...     a, b = b, a+b       # This indented line is also executed if b<10.
...
0
1
1
2
3
5
\end{lstlisting}
\end{example}

\subsection*{The if Statement}
\begin{example}
An if statement behaves as expected: if the condition holds, execute the
following indented code. Note again that the indentation is important to Python.
The elif statement is short for ``else if'' and can be used multiple times or not at all.
The else keyword may only be used once at the end (requires no condition),
but may also be omitted.
\begin{lstlisting}
>>> food = "bagel"
>>> if food == "apple":
...     print "72 calories"
... elif food == "banana":
...     print "105 calories"
... elif food == "egg":
...     print "102 calories"
... elif food == "oatmeal":
...     print "147 calories"
... elif food == "pizza":
...     print "298 calories"
... else:
...     print "calorie count unavailable"
...
calorie count unavailable
\end{lstlisting}
\end{example}

\subsection*{The for Loop}
\begin{example}
A for loop iterates over the items in any list, set, dictionary, tuple or other iterable.
The \li{range} function is typically used to generate lists that allow iteration
over a sequence of numbers. Note that Python stops just \emph{before} the given stop value.

\begin{lstlisting}
>>> for i in range(5):
...     print i
...
0
1
2
3
4
>>> my_list = ["Henry XXI", "beta fish", "asparagus"]
>>> for x in my_list:
...     print "King" + " " + x
...
King Henry XXI
King beta fish
King asparagus
>>> for i in range(len(my_list)):
...     my_list.append(i)
...
>>> my_list
['Henry XXI', 'beta fish', 'asparagus', 0, 1, 2]
\end{lstlisting}
\end{example}

\subsection*{Function Definition}
\begin{example}
The \li{def} keyword allows for a function definition. The keyword is followed by
the function name and a parenthesized list of formal parameters.
Once again, indentation is important.
\begin{lstlisting}
>>> def fibonacci(n):
...     a, b = 0, 1
...     while a < n:
...         print a, b
...         a, b = b, a+b
...
>>> fibonacci(10)
0 1
1 1
1 2
2 3
3 5
5 8
8 13
\end{lstlisting}

Let's look at another function.
\begin{lstlisting}
def fn(a, b, c=0):
    print "a: {}, b: {}, c: {}".format(a, b, c)
\end{lstlisting}
The function, fn, has three parameters: a, b, and c.
When we call this function, we need to provide arguments for each of these parameters.
Note, however, that we have defined a default value for c.
Therefore, if we only provide arguments for a and b, Python will use the default value for c (which is 0).
We can pass arguments based on position or by name.
When called with just a list of values, Python will assign the first argument to the first parameter,
the second argument to the second parameter, etc.
This is what is meant by positional arguments.
We can also explicitly assign arguments by using the parameter's name.
These are named arguments.
They are also often called keyword arguments.
There are a variety of ways to call our function.
\begin{lstlisting}
# Call fn with 2 positional arguments (c=0 by default)
>>> fn(1, 2)
a: 1, b: 2, c: 0

# Call fn with 3 positional arguments
>>> fn(4, 5, 6)
a: 4, b: 5, c: 6

# Call fn with 1 positional argument and 1 named argument (c=0 by default)
>>> fn(15, b=16)
a: 15, b: 16, c: 0

# Call fn with 1 positional argument and 2 named arguments
>>> fn(12, c=13, b=14)
a: 12, b: 14, c: 13

# Call fn with 2 named arguments (c=0 by default)
>>> fn(b=10, a=11)
a: 11, b: 10, c: 0

# Call fn with 3 named arguments
>>> fn(b=7, a=8, c=9)
a: 8, b: 7, c: 9
\end{lstlisting}
Notice that in every case, we must define any positional arguments first.
The way we call functions in Python can be very flexible.
However, to avoid confusion, it is common practice to give arguments to a function in the order they are defined in the function definition whenever possible.

\begin{info}
When defining a function, we define the \emph{parameters} of the function.  We then call the function with \emph{arguments}.
While the distinction often isn't very important, in some cases, it might help you avoid confusion.
In practice, though, the two terms are often used interchangeably and are viewed as synonymous.
\begin{lstlisting}
def add_numbers(a, b):
    return a + b
add_numbers(5.2, 10)
\end{lstlisting}
In the example above, $a$ and $b$ are parameters for the function \li{add_numbers}.
The values $5.2$ and $10$ are the arguments that we pass when calling the function.
\end{info}

\begin{problem}
Define the following function in your interpreter.
\begin{lstlisting}
def track(n, a=[]):
    a.append(n)
    return a
\end{lstlisting}
What does the function do?
Try calling the function multiples times with different values of n.
Print the list returned each time.
The behavior of this function demonstrates something very important.
The default values of a function are only evaluated once.
This means that the same list is being shared for every call of this function.
What would be the best way to modify the function so that the list is not shared between calls of the function?
\end{problem}

Sometimes when looking at the documentation of a function or when defining your own function you will see the following:
\begin{lstlisting}
def function(*args, **kwargs):
\end{lstlisting}
This is the most general form of a function definition.
Basically, it means that ``the function takes some arguments and keyword arguments.''
The arguments, args, are stored as a tuple and the keyword arguments, kwargs, are stored in a dictionary.
The function above can accept any number of arguments or keyword arguments.
\begin{lstlisting}
>>> def f(*args, **kwargs):
...     print "Positional: ", args
...     print "Keyword: ", kwargs
...
>>> f("Hello", 2, 1, apples = 3, oranges = 2)
Positional: ('Hello', 2, 1)
Keyword: {'apples': 3, 'oranges': 2}

# The stars are operators and have special meaning in this case
# These operators unpack tuples and dictionaries into arguments or keyword arguments
>>> f(*range(5)) # Equivalent to f(0, 1, 2, 3, 4)
Positional: (0, 1, 2, 3, 4)
Keyword: {}
>>> d = {'a': 1, 'b': 2}
>>> f(**d) # Equivalent to f(a=1, b=2)
Positional: ()
Keyword: {'a': 1, 'b': 2}
\end{lstlisting}
\end{example}

\begin{problem}
Answer the following questions. Use the official Python documentation, if necessary.

\begin{enumerate}
\item Explain what the print and return statements do. How are they different?
\item What is wrong with the following code?
\begin{lstlisting}
Grocery List = ['pineapple', 'orange juice', "avocados", "pesto sauce"]
for i in range(Grocery List)
if i % 2 = 0
print i, Grocery List(i)
\end{lstlisting}
provided you want the following output:
\begin{lstlisting}
0 pineapple
2 avocados
\end{lstlisting}
Note that this code may contain one error, many errors, or no errors at all.
\item When you call the function ``groceries'' below, it returns an error. Why?
\begin{lstlisting}
def groceries(food, drink):
    print food
    print drink

groceries(food="Bananas", "Juice")
\end{lstlisting}
\end{enumerate}
\end{problem}

\section*{Specifications}
We suggest that you submit your solutions in a file called \li{solutions.py}, using the following format.
\lstinputlisting[language=, abovecaptionskip=2pt, belowcaptionskip=2pt]{template.txt}


\section{Flattening}
\label{sec:flattening}
\index{stack!flattening|(}

Let us design a function~\fun{flat/1}\index{flat@\fun{flat/1}} such
that the call \(\fun{flat}(s)\), where \(s\)~is a stack, is rewritten
into a stack containing only the non\hyp{}stack items found in~\(s\),
in the same order. If \(s\)~contains no stack, then \(\fun{flat}(s)
\equiv s\). Let us review some
examples\index{stack!flattening!example} to grasp the concept:
\begin{equation*}
\fun{flat}(\el) \twoheadrightarrow \el; \quad
\fun{flat}([\el,[\el]]) \twoheadrightarrow \el;\quad
\fun{flat}([\el,[1,[2,\el],3],\el]) \twoheadrightarrow [1,2,3].
\end{equation*}
First, let us focus on designing the left\hyp{}hand sides of the
rules, in order to ensure that our definition is \emph{complete} (all
valid inputs are matched). A stack is either empty or not and, in the
latter case, the specific issue at hand appears clearly: we need to
distinguish the items which are stacks themselves from those which are
not. This is very simply achieved by ordering the patterns so that
\(\el\) and \(\cons{x}{s}\) \emph{as items} appear
first:\index{flat@\fun{flat/1}|(}
\begin{equation*}
\begin{array}{r@{\;}l@{\;}l}
\fun{flat}(\el)                   & \xrightarrow{\smash{\psi}}   & \fbcode{CCCCCCC}\,;\\
\fun{flat}(\cons{\el}{t})         & \xrightarrow{\smash{\omega}} & \fbcode{CCCCCCC}\,;\\
\fun{flat}(\cons{\cons{x}{s}}{t}) & \xrightarrow{\smash{\gamma}} & \fbcode{CCCCCCC}\,;\\
\fun{flat}(\cons{y}{t})           & \xrightarrow{\smash{\delta}} & \fbcode{CCCCCCC}\,.
\end{array}
\end{equation*}
We know that \(y\)~in the last line is not a stack, otherwise the
penultimate or antepenultimate pattern would have matched. Almost all
the right\hyp{}hand sides are easy to guess now:
\begin{equation*}
\begin{array}{r@{\;}l@{\;}l}
\fun{flat}(\el)                   & \xrightarrow{\smash{\psi}}   & \el;\\
\fun{flat}(\cons{\el}{t})         & \xrightarrow{\smash{\omega}} & \fun{flat}(t);\\
\fun{flat}(\cons{\cons{x}{s}}{t}) & \xrightarrow{\smash{\gamma}} & \fbcode{CCCCCCC}\,;\\
\fun{flat}(\cons{y}{t})           & \xrightarrow{\smash{\delta}} & \cons{y}{\fun{flat}(t)}.
\end{array}
\end{equation*}
\index{flat@\fun{flat/1}|)}The design of the remaining right\hyp{}hand
side can be guided by two slightly different principles. If we look
back at the definitions of~\fun{rev\(_0\)/1}
\index{rev0@\fun{rev\(_0\)/1}|(}
and~\fun{rev/1}\index{rev@\fun{rev/1}} in section~\ref{sec:reversal},
we see that the former was designed with the result in mind, as if the
arrows would reach a value, which is then decomposed in terms of the
variables of the corresponding left\hyp{}hand side:
\begin{equation*}
\begin{array}{r@{\;}l@{\;}l}
\fun{rev}_0(\el) & \rightarrow & \el;\\
\fun{rev}_0(\cons{x}{s}) & \rightarrow &
\fun{cat}(\fun{rev}_0(s),[x]).
\end{array}
\end{equation*}
\index{rev0@\fun{rev\(_0\)/1}|)}By contrast,
\fun{rev/1}\index{rev@\fun{rev/1}} relies on another function,
\fun{rcat/2}\index{rcat@\fun{rcat/2}}, to accumulate partial results,
as if each arrow covered a short distance, only contributing minimally
to the final value:
\begin{equation*}
\begin{array}{r@{\;}l@{\;}l}
\fun{rev}(s) & \smashedrightarrow{\epsilon} & \fun{rcat}(s,\el).\\
\fun{rcat}(\el,t) & \smashedrightarrow{\zeta} & t;\\
\fun{rcat}(\cons{x}{s},t) & \smashedrightarrow{\eta} &
\fun{rcat}(s,\cons{x}{t}).
\end{array}
\end{equation*}
The first approach might be called \emph{big\hyp{}step
  design}\index{design!big-step $\sim$}, and the other
\emph{small\hyp{}step design}\index{design!small-step $\sim$}. Another
vantage point is to see that the former uses the context of the
recursive call to build the value, whilst the latter relies
exclusively on an argument (the accumulator\index{functional
  language!accumulator}) and is in tail form\index{functional
  language!tail form}. For example, in section~\ref{sec:skipping}, we
may find that the definition of \fun{sfst/2}\index{sfst@\fun{sfst/2}}
follows a big\hyp{}step design, while
\fun{sfst\(_0\)/2}\index{sfst@\fun{sfst/2}} illustrates a
small\hyp{}step design.

\paragraph{Big-step design}
\index{design!big-step $\sim$}
\index{divide and conquer}
\label{big-step}

Abstractly, a big\hyp{}step design means that the right\hyp{}hand
sides are made up of recursive calls on substructures, for instance,
in the case of rule~\(\gamma\), the substructures of
\(\cons{\cons{x}{s}}{t}\) are~\(x\), \(s\), \(t\) and
\(\cons{x}{s}\). By thinking how to make up the value by means of
\(\fun{flat}(\cons{x}{s})\)\index{flat@\fun{flat/1}} and
\(\fun{flat}(t)\), we obtain a new version,
\fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}}, in
\fig~\vref{fig:flat0}.
\begin{figure}
\begin{equation*}
\boxed{%
\begin{array}{r@{\;}l@{\;}l}
\fun{flat}_0(\el)                   & \xrightarrow{\smash{\psi}}
                                    & \el;\\
\fun{flat}_0(\cons{\el}{t})         & \xrightarrow{\smash{\omega}}
                                    & \fun{flat}_0(t);\\
\fun{flat}_0(\cons{\cons{x}{s}}{t}) & \xrightarrow{\smash{\gamma}}
        & \fun{cat}(\fun{flat}_0(\cons{x}{s}),\fun{flat}_0(t));\\
\fun{flat}_0(\cons{y}{t})           & \xrightarrow{\smash{\delta}}
                                    & \cons{y}{\fun{flat}_0(t)}.
\end{array}}
\end{equation*}
\caption{Flattening a stack with \fun{flat\(_0\)/1}
\label{fig:flat0}\index{stack!flattening!definition}}
\end{figure}

Let us consider an example in \fig~\vref{fig:flat0_ex}, where the call
to be rewritten next is underlined in case of ambiguity.
\begin{figure}
\begin{equation*}
\boxed{%
\begin{array}{r@{\;}l@{\;}l}
\fun{flat}_0([\el,[[1],2],3])
& \xrightarrow{\smash{\omega}}
& \fun{flat}_0([[[1],2],3])\\
& \xrightarrow{\smash{\gamma}}
& \fun{cat}(\underline{\fun{flat}_0([[1],2])},\fun{flat}_0([3]))\\
& \xrightarrow{\smash{\gamma}}
& \fun{cat}(\fun{cat}(\underline{\fun{flat}_0([1])},\fun{flat}_0([2])),\fun{flat}_0([3]))\\
& \xrightarrow{\smash{\delta}}
& \fun{cat}(\fun{cat}([1|\underline{\fun{flat}_0(\el)}],\fun{flat}_0([2])),\fun{flat}_0([3]))\\
& \xrightarrow{\smash{\psi}}
& \fun{cat}(\fun{cat}([1],\underline{\fun{flat}_0([2])}),\fun{flat}_0([3]))\\
& \xrightarrow{\smash{\delta}}
& \fun{cat}(\fun{cat}([1],[2|\underline{\fun{flat}_0(\el)}]),\fun{flat}_0([3]))\\
& \xrightarrow{\smash{\psi}}
& \fun{cat}(\fun{cat}([1],[2]),\underline{\fun{flat}_0([3])})\\
& \xrightarrow{\smash{\delta}}
& \fun{cat}(\fun{cat}([1],[2]),[3|\underline{\fun{flat}_0(\el)}])\\
& \xrightarrow{\smash{\psi}}
& \fun{cat}(\fun{cat}([1],[2]),[3])\\
& \twoheadrightarrow & [1,2,3].
\end{array}
}
\end{equation*}
\caption{\(\fun{flat}_0([\el,[[1],2],3]) \twoheadrightarrow [1,2,3]\)
\label{fig:flat0_ex}\index{stack!flattening!example}}
\end{figure}
Note that the call\hyp{}by\hyp{}value\index{functional
  language!call-by-value} strategy (section~\ref{sec:functional}) does
not specify the order of evaluation of the arguments of a function
call: in our example, we delayed the evaluation of
\(\fun{cat}([1],[2])\) \index{cat@\fun{cat/2}|(} after that of the
calls to \fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}}. When
deriving complicated recurrences or traces, we may try instead
counting the number of times each rule is used in any
evaluation. Calling \fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}}
results in
\begin{itemize*}

  \item using rule~\(\omega\) once for each empty stack originally in
    the input;

  \item using rule~\(\psi\) once when the end of the input is reached
    \emph{and} once for each empty stack \(t\) in rule~\(\gamma\);

  \item using rule~\(\delta\) once for each item which is not a stack;

  \item using rule~\(\gamma\) once for each non\hyp{}empty embedded
    stack;

  \item calling \fun{cat/2}\index{cat@\fun{cat/2}|)} once for each
    non\hyp{}empty embedded stack.

\end{itemize*}
We now know the parameters which the cost depends upon:
\begin{enumerate*}

  \item the length of\index{flat0@\fun{flat$_0$/1}}
    \(\fun{flat}_0(s)\), noted~\(n\);

  \item the number of non\hyp{}empty stacks embedded in the input,
    say~\(\Gamma\);

  \item the number of embedded empty stacks, denoted by~\(\Omega\).

\end{enumerate*}
The dependence upon the size of the output, \(n\), is an instance of
\emph{output\hyp{}dependent cost}\index{cost!output-dependent
  $\sim$}. We can reformulate the above analysis in the following
terms: rule~\(\psi\) is used \(1 + \Gamma\)~times, rule~\(\omega\) is
used \(\Omega\)~times, rule~\(\gamma\) is used \(\Gamma\)~times,
rule~\(\delta\) is used \(n\)~times. So the cost due to the rules of
\fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}} alone is \(1 + n +
\Omega + 2\Gamma\). For instance, in the case of
\(\fun{flat}_0([\el,[[1],2],3])\), we correctly find \(1 + 3 + 1 + 2
\cdot 2 = 9 = \len{\omega\gamma^2(\delta\psi)^3}\).

As far as the costs of the calls to \fun{cat/2} are concerned, their
associativity\index{cat@\fun{cat/2}} was proved on
page~\pageref{proof:assoc_cat} and equation~\eqref{ineq:cat_assoc}
\vpageref{ineq:cat_assoc} suggests that there are configurations of
the input to \fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}} that
lead to greater costs, when the parameters \(n\), \(\Omega\)
and~\(\Gamma\) are fixed. A similar pattern of calls to
\fun{cat/2}\index{cat@\fun{cat/2}} with a quadratic cost is generated
from the definition of
\fun{rev\(_0\)/1}\index{rev0@\fun{rev\(_0\)/1}}, as seen in
\fig~\vref{fig:rev0_321}, after the first application of
rule~\(\alpha\). The right\hyp{}hand side of rule~\(\gamma\) is
\(\fun{cat}(\fun{flat}_0(\cons{x}{s}),\fun{flat}_0(t))\)
\index{flat0@\fun{flat$_0$/1}} and it implies that the arguments of
\fun{cat/2}\index{cat@\fun{cat/2}} may be empty stacks.

Given \(\Gamma\) \fun{cat}\hyp{}nodes, \(n\)~non\hyp{}stack nodes
(\(x_1\), \dots, \(x_n\)), what are the abstract syntax trees with
minimum and maximum costs? We found that the minimum cost for
\fun{cat/2}\index{cat@\fun{cat/2}} is achieved when all the
\fun{cat}\hyp{}nodes make up the rightmost
\emph{branch}\index{tree!branch} of the abstract syntax tree. (A
branch is a series of nodes such that one is the parent of the next,
from the root to a leaf.)
\begin{itemize}

  \item If \(\Omega \geqslant \Gamma\), the minimum configuration is
  shown in \fig~\ref{fig:cat_min1}
\begin{figure}[b]
\centering
\subfloat[If \(\Omega \geqslant \Gamma\), the minimum is \(\Gamma\).
\label{fig:cat_min1}]{\includegraphics[bb=66 660 191 722]{cat_min1}}
\quad
\subfloat[If \(\Omega < \Gamma\), the minimum is \(2\Gamma-\Omega\).
\label{fig:cat_min2}]{\includegraphics[bb=70 643 230 721]{cat_min2}}
\caption{Minimum costs for the catenations in \fun{flat\(_0\)/1}
\label{fig:cat_min}}
\end{figure}
  (at least one empty stack must be placed in any non\hyp{}empty stack
  whose flattening results in an empty stack).

\item Otherwise, the abstract syntax tree of minimum cost is found in
  \fig~\ref{fig:cat_min2}, where all the available empty stacks
  (\(\Omega\)) are used for the bottommost \fun{cat}\hyp{}nodes. We
  draw that the minimum cost\index{stack!flattening!minimum cost}
  is\index{flat0@$\B{\fun{flat}_0}{n}$}
  \(\B{\fun{flat}_0}{n,\Omega,\Gamma} = 1 + n + \Omega + 3\Gamma +
  \min\{\Omega,\Gamma\}\).

\end{itemize}

The maximum cost\index{stack!flattening!maximum cost} in
\fig~\ref{fig:cat_max} occurs for the symmetrical tree of
\fig~\ref{fig:cat_min1}. We
have\index{flat0@$\W{\fun{flat}_0}{n,\Omega,\Gamma}$}
\(\W{\fun{flat}_0}{n,\Omega,\Gamma} = \Omega + (n+3)(\Gamma+1) - 2\).
\begin{figure}[t]
\centering
\subfloat[Maximum cost \((n+1)\Gamma\)\label{fig:cat_max}]{
\includegraphics[bb=71 660 188 721]{cat_max}}
\qquad
\subfloat[$\protect\fun{flat}(\cons{\cons{x}{s}}{t})$\label{fig:lift1}]%
{\includegraphics[bb=55 661 122 721]{flat_embed}}
\qquad
\subfloat[$\protect\fun{flat}(\cons{x,s}{t})$\label{fig:lift2}]%
{\includegraphics[bb=57 661 120 721]{flat_lift}}
\caption{Maximum cost and right rotations}
\end{figure}

% Wrapping figure better declared before a paragraph
%
\begin{wrapfigure}[7]{r}[0pt]{0pt}
% [7] vertical lines
% {r} mandatory right placement
\centering
\includegraphics[bb=71 658 220 710]{flat}%[... 721]
\caption{Defining \fun{flat/1}\label{fig:flat}
\index{stack!flattening!definition}}
\end{wrapfigure}

\paragraph{Small-step design}
\index{design!small-step $\sim$}

An alternative method for flattening of a stack consists in
lifting~\(x\) in rule~\(\gamma\) one level up amongst the embedded
stacks, thus approaching little by little a flat stack. In terms of
the abstract syntax trees, this operation is a \emph{right
  rotation}\index{binary tree!rotation} of the tree associated with
the argument, as shown in \figs~\vrefrange{fig:lift1}{fig:lift2},
where the new function is called
\fun{flat/1}\index{flat@\fun{flat/1}|(} and defined in
\fig~\vref{fig:flat}. Let us run the previous example of
\fig~\vref{fig:flat0_ex} again in \fig~\vref{fig:flat_ex} with
\fun{flat/1}. The difference in cost with
\fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}} resides in the number
of times rule~\(\gamma\) is used: once for each item in all the
embedded stacks. Hence the
cost\index{stack!flattening!cost}\label{cost:flat} is \(1 + n + \Omega
+ \Gamma + L\), where \(L\)~is the sum of the lengths of all the
embedded stacks.

\paragraph{Comparison}

Consider the following costs:
\begin{equation*}
\begin{array}{@{}r@{\;}c@{\;}r@{\;}c@{\;}r@{\;}c@{\;}l@{}}
\Call{\fun{flat}([[[[[1,2]]]]])}
  &=& 12 &<& 23 &=& \Call{\fun{flat\(_0\)}([[[[[1,2]]]]])};\\
\Call{\fun{flat}([\el,[[1],2],3])}
  &=& 10 &<& 14 &=& \Call{\fun{flat\(_0\)}([\el,[[1],2],3])};\\
\Call{\fun{flat}([\el,[[1,[2]]],3])}
  &=& 12 &<& 19 &=& \Call{\fun{flat\(_0\)}([\el,[[1,[2]]],3])};\\
\Call{\fun{flat}([[\el,\el,\el]])}
  &=&  8 &>&  7 &=& \Call{\fun{flat\(_0\)}([[\el,\el,\el]])}.
\end{array}
\end{equation*}
\begin{figure}
\begin{equation*}
\boxed{%
\begin{array}{r@{\;}l@{\;}l}
\fun{flat}([\el,[[1],2],3])
& \xrightarrow{\smash{\omega}} & \fun{flat}([[[1],2],3])\\
& \xrightarrow{\smash{\gamma}} & \fun{flat}([[1],[2],3])\\
& \xrightarrow{\smash{\gamma}} & \fun{flat}([1,\el,[2],3])\\
& \xrightarrow{\smash{\delta}} & [1|\fun{flat}([\el,[2],3])\\
& \xrightarrow{\smash{\omega}} & [1|\fun{flat}([[2],3])]\\
& \xrightarrow{\smash{\gamma}} & [1|\fun{flat}([2,\el,3])]\\
& \xrightarrow{\smash{\delta}} & [1,2|\fun{flat}([\el,3])]\\
& \xrightarrow{\smash{\omega}} & [1,2|\fun{flat}([3])]\\
& \xrightarrow{\smash{\delta}} & [1,2,3|\fun{flat}(\el)]\\
& \xrightarrow{\smash{\psi}}   & [1,2,3].
\end{array}}
\end{equation*}
\caption{\(\fun{flat}([\el,[[1],2],3]) \twoheadrightarrow [1,2,3]\)
\label{fig:flat_ex}
\index{stack!flattening!example}}
\end{figure}
Simple algebra shows that\index{flat@$\C{\fun{flat}}{n,\Omega,\Gamma}$}
\index{flat0@$\B{\fun{flat}_0}{n,\Omega,\Gamma}$}
\begin{equation*}
\C{\fun{flat}}{n,\Omega,\Gamma}
\leqslant
\B{\fun{flat}_0}{n,\Omega,\Gamma}
\Leftrightarrow
\begin{cases}
  L \leqslant 3\Gamma - \Omega,
                         & \text{if \(\Omega \geqslant \Gamma\)};\\
  L \leqslant 2\Gamma,   & \text{otherwise.}
\end{cases}
\end{equation*}
\index{flat@\fun{flat/1}|)} This criterion is impractical to check and
it is inconclusive if the inequalities on the right\hyp{}hand side
fail. Things worsen if we settle for \(L \leqslant 2\Gamma \Rightarrow
\smash[t]{\C{\fun{flat}}{n,\Omega,\Gamma}} \leqslant
\smash[t]{\C{\fun{flat}_0}{n,\Omega,\Gamma}}\), because this condition
is too strong when \(\Omega\)~is large. Let us examine what happens at
the other extreme, when \(\Omega = 0\). Considering an example like
\([0,[1,[2]],3,[4,5,[6,7],8],9]\), it may occur to us that if there
are no empty stacks, the length of each stack is lower than or equal
to the number of non\hyp{}stack items in it and its embeddings (it is
equal if there is no further embedded stack). Therefore, summing up
all these inequalities, the following inequality ensues:
\begin{equation}
  \Omega = 0 \Rightarrow C - L \geqslant 0, \label{eq:CL}
\end{equation}
where \(C\)~is the cost of rewriting the calls
to~\fun{cat/2}\index{cat@\fun{cat/2}}
in~\fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}}. Because
\(\C{\smash{\fun{flat}_0}}{n,\Omega,\Gamma} = (1 + n + \Omega +
\Gamma) + (C + \Gamma)\) and \(\C{\fun{flat}}{n,\Omega,\Gamma} = (1 +
n + \Omega + \Gamma) + L\),
\begin{equation*}
  \C{\smash{\fun{flat}_0}}{n,\Omega,\Gamma} -
  \C{\fun{flat}}{n,\Omega,\Gamma} =
  (C - L) + \Gamma.
\end{equation*}
From equation~\eqref{eq:CL}, we deduce that, in the absence of empty
stacks, we have the inequality
\(\C{\smash{\fun{flat}_0}}{n,\Omega,\Gamma} -
\C{\fun{flat}}{n,\Omega,\Gamma} \geqslant L\), that is,
\fun{flat/1}\index{flat@\fun{flat/1}} is faster.

\mypar{Termination}
\label{flattening:termination}
\index{stack!flattening!termination|(}

As we have seen with the simplified Ackermann's function
(section~\ref{par:ackermann}, \vpageref{par:ackermann}), termination
follows from finding a well\hyp{}founded order \((\succ)\) on the
recursive calls, which is entailed by the rewrite relation
\((\rightarrow)\), that is, \(x \rightarrow y \Rightarrow x \succ
y\). One well\hyp{}founded order for stacks is the \emph{immediate
  subterm order}, satisfying \(\cons{x}{s} \succ s\) and \(\cons{x}{s}
\succ x\). Since big\hyp{}step design uses recursive calls to subterms
(section~\ref{par:well-founded}, \vpageref{par:well-founded}), it
eases termination proofs based on such an ordering.

For instance, let us recall the definition of
\fun{flat\(_0\)/1}\index{flat0@\fun{flat$_0$/1}|(} in
\fig~\ref{fig:flat0} on page~\pageref{fig:flat0}. Since
\fun{cat/2}\index{cat@\fun{cat/2}} is independent of
\fun{flat\(_0\)/1}, we prove its termination in isolation by using the
proper subterm order on its first argument. Assuming now that
\fun{cat/2} terminates, let us prove the termination of
\fun{flat\(_0\)/1}. Because the recursive calls of \fun{flat\(_0\)/1}
contain only (stack) constructors, we can try to order their arguments
\citep{ArtsGiesl_1996}. Again, the same order works:
\begin{itemize*}

  \item \(\cons{y}{t} \succ t\) by rule~\(\delta\) and rule~\(\omega\)
    when \(y=\el\),

  \item \(\cons{\cons{x}{s}}{t} \succ t\) and

  \item \(\cons{\cons{x}{s}}{t} \succ \cons{x}{s}\) by
    rule~\(\gamma\).

\end{itemize*}
Termination ensues.\hfill\(\Box\)

Let us further recall the definition of
\fun{flat/1}\index{flat@\fun{flat/1}} in \fig~\ref{fig:flat} and
prove its termination. Here, the order we used for \fun{flat\(_0\)/1}
fails:
\begin{equation*}
  \cons{\cons{x}{s}}{t} \nsucc \cons{x,s}{t} =
  \cons{x}{\cons{s}{t}}.
\end{equation*}
We could try the more general \emph{proper subterm
  order}\index{induction!proper subterm order}, that is, the strict
inclusion of a term into another, but, despite \(\cons{x}{s} \succ
x\), we have \(t \nsucc \cons{s}{t}\). One way out is to define a
\emph{measure}\index{termination!measure} on the stacks
\citep{Giesl_1995a}.\index{flat0@\fun{flat$_0$/1}|)}

A measure \(\measure{\cdot}\) is a map from terms to a
well\hyp{}ordered set (\(A,\succ\)), which is \emph{monotone} with
respect to rewriting:
\begin{equation*}
  x \rightarrow y \Rightarrow \measure{x} \succ \measure{y}.
\end{equation*}
Actually, we will only consider \emph{dependency pairs}
\citep{ArtsGiesl_2000}\index{termination!dependency pair}, that is,
pairs of calls whose first component is the left\hyp{}hand side of a
rule and the second components are the calls in the righ\hyp{}hand
side of same rule. This is easier than working with \(x\)~and~\(y\) in
\(x \rightarrow y\), as only subterms of~\(y\) are considered. The
pairs are\index{flat@\fun{flat/1}}:
\begin{itemize*}

  \item \((\fun{flat}(\cons{\el}{t}),
    \fun{flat}(t))_\omega\),

  \item \((\fun{flat}(\cons{\cons{x}{s}}{t}),
    \fun{flat}(\cons{x,s}{t}))_\gamma\) and

  \item \((\fun{flat}(\cons{y}{t}), \fun{flat}(t))_\delta\), with \(y
    \not\in S\).
\end{itemize*}
We can actually drop the function names, as all the pairs involve
\fun{flat/1}\index{flat@\fun{flat/1}}. A common class of measures are
monotone embeddings into (\(\mathbb{N},>\)), so let us seek a measure
satisfying
\begin{itemize*}

  \item \(\measure{\cons{\cons{x}{s}}{t}} > \measure{\cons{x,s}{t}}\);

  \item \(\measure{\cons{y}{t}} > \measure{t}\), if \(y \not\in S\) or
    \(y=\el\).

\end{itemize*}
For instance, let us set the following \emph{polynomial
  measure}\index{termination!polynomial measure}:
\begin{itemize*}

  \item \(\measure{\cons{x}{s}} := 1 + 2\cdot\measure{x} +
    \measure{s}\);

  \item \(\measure{y} := 0\), if \(y \not\in S\) or \(y=\el\).

\end{itemize*}
We have, for each stack,
\begin{itemize*}

  \item \(\measure{\cons{\cons{x}{s}}{t}} = 3 + 4\cdot\measure{x} +
    2\cdot\measure{s} + \measure{t}\),

  \item \(\measure{\cons{x,s}{t}} = 2 + 2\cdot\measure{x} +
    2\cdot\measure{s} + \measure{t}\).

\end{itemize*}
Therefore \(\measure{\cons{\cons{x}{s}}{t}} = \measure{\cons{x,s}{t}}
+ 1 + 2\cdot\measure{x}\). Because \(\measure{x} \in \mathbb{N}\),
for all~\(x\), we have \(\measure{\cons{\cons{x}{s}}{t}} >
\measure{\cons{x,s}{t}}\). The second inequality yields faster:
\(\measure{\cons{y}{t}} = 1 + \measure{t} > \measure{t}\). This
entails the termination of
\fun{flat/1}\index{flat@\fun{flat/1}}.\index{stack!flattening!termination|)}\hfill\(\Box\)

\cite{Giesl_1997} tackled the termination of mutually recursive
functions. Functional programs, as special cases of
term\hyp{}rewriting systems, have been considered by
\cite{Giesl_1995b} and \cite{GieslWaltherBrauburger_1998}.

% Wrapping figure better declared before a paragraph
%
\begin{wrapfigure}[6]{r}[0pt]{0pt}
% [6] vertical lines
% {r} mandatory right placement
\centering
\includegraphics[bb=71 644 227 708]{flat1}% Originally [... 721]
\caption{Variant flattening\label{fig:flat1}
\index{stack!flattening!definition}}
\end{wrapfigure}
\paragraph{Exercises}

\begin{enumerate*}

  \item Define \fun{gamma/1}, \fun{lambda/1} and \fun{omega/1}, which
    compute \(\Gamma\), \(L\) and~\(\Omega\), respectively.

  \item Compare the costs of \fun{flat/1}\index{flat@\fun{flat/1}} and
    \fun{flat\(_1\)/1} \index{flat1@\fun{flat$_1$/1}} defined in
    \fig~\ref{fig:flat1} (see~(\(\leadsto\))).

\end{enumerate*}
\index{stack!flattening|)}


% Master File: ps10.tex
\input ../6001mac

\newcommand{\Code}[1]{\mbox{\tt #1}}

\begin{document}

\psetheader{Sample Problem Set}{Compilation}
\begin{center}
{\bf Register Machines and Compilation}
\end{center}

\medskip

Register machines provide a means of customizing code for particular
processes.  In principle, customization leads to more efficient code,
since one can avoid the overhead that comes from a compiler's obligation
to handle more general computations\footnote{For example, the compiler in Chapter
5 generates code in which arguments of procedures are maintained as a list
in the {\tt argl} register.  On the other hand, a register machine
customized for a procedure of, say, three arguments might usefully keep
the arguments in three separate registers.}.
In this problem set you will handcraft two simple machines and compare
them to the compiler in Chapter 5 of the notes.

The register machines you define should use only the following few
primitives:
\beginlisp
+ - * / inc dec = < > zero?  not true false
cons car cdr pair? null? list eq? symbol? write-line
\endlisp
Even though code generated by the compilers uses more complex primitives
such as
\beginlisp
lookup-variable-value extend-environment \ldots,
\endlisp
your hand-crafted code should turn out to run more efficiently.

Remember that register machine instructions are only of the
following types:
\beginlisp
test branch assign goto save restore perform
\endlisp
In this problem set, you should not need to use {\tt perform}.
Remember also that the only values that can be assigned to registers
or tested in branches are constants, fetches from registers, or
primitive operations applied to fetches from registers.  No nested
operations are permitted\footnote{For example,
(assign val (op inc) ((op *) (reg a) (reg b)))
is {\em not} permitted, since a call to {\tt *} is nested inside a
call to {\tt inc}.}.

\medskip
Here are the definitions of two Scheme procedures for deleting all
occurrences of an element, {\tt x}, from a list, {\tt l}:
\beginlisp
(define (delq1 x l)
  (cond ((null? l) '())
        ((eq? x (car l)) (delq1 x (cdr l)))
        (else (cons (car l) (delq1 x (cdr l))))))
\endlisp

\medskip
\beginlisp
(define (delq2 x l)
  (define (delete-reverse maybe-xs no-xs)
    (cond ((null? maybe-xs) no-xs)
          ((eq? x (car maybe-xs)) (delete-reverse (cdr maybe-xs) no-xs))
          (else (delete-reverse (cdr maybe-xs) (cons (car maybe-xs) no-xs)))))
  (delete-reverse (delete-reverse l '()) '()))
\endlisp

\paragraph{PreLab exercise 1A:}
For each of these procedures, say what order of growth in time (total
machine operations) and space (maximum stack depth) you expect them to use.

\paragraph{PreLab exercise 1B:}
Implement both of these procedures as register machines.  You should show
the controllers for all the machines, but you need show the data paths for
only one of them.

\section{To do in lab}

In lab, you will use the register machine simulator to test the register
machines you designed in exercise 1B.  To use the simulator, load the code
for problem set 10 and type in your machine definitions:

\beginlisp
(define my-machine
  (make-machine
   '(x l val \ldots)
   standard-primitives          ; + - * / inc, etc.
   '((test \ldots)
     \vdots
     )))
\endlisp

You'll find it convenient to define test procedures that load an input
into a machine, run the machine, print some statistics, and return the
result computed by the machine.  For example:

\beginlisp
(define (test-machine x l)
  (set-register-contents! my-machine 'x x)
  (set-register-contents! my-machine 'l l)
  (my-machine 'initialize-stack)
  (my-machine 'initialize-ops-counter)
  (start my-machine)
  (my-machine 'print-stack-statistics)
  (my-machine 'print-ops)
  (get-register-contents my-machine 'val))
\endlisp

In addition to routines that gather statistics for stack usage and
total number of operations, there are some procedures to help you
debug your machines.  {\tt trace-reg-on} will show all
assignments to a specified machine register as they occur. Evaluating:
\beginlisp
(trace-reg-on my-machine 'l)
\endlisp
before running your test procedure will show you all the changes to the
{\tt l} register. To see even more stuff, try:

\beginlisp
(trace-on my-machine)
\endlisp

which will print each machine instruction as it is executed.  To get rid
of these traces, use {\tt trace-reg-off} and {\tt trace-off}.

\paragraph{Lab exercise 2A:}
Debug your machines, run them on some representative inputs, and make a
table that records the total number of machine operations, total number of
stack pushes, and maximum stack depth as a function of the length of the
list from which an element is being deleted.

\paragraph{PostLab exercise 2B:}
Try to derive formulas for the total number of machine operations, total
number of pushes, and maximum stack depth used by your machines, as
functions of the length of the list.  In most cases, the functions will
turn out to be polynomials in the list length, in which case you should be
able to exhibit exact formulas, not just orders of growth.

\subsubsection{Running the Compiler}

There are two ways to run the compiler.  First, you may simply compile an
expression and obtain the list of machine instructions as a result, so
that you can study it.  For instance,

\beginlisp
(define test-expression '(define (f x y) (* (+ x y) (- x y))))

(define result (compile test-expression 'val 'return))

(pp result)
\endlisp

A way to look just at the produced code even more easily is:

\beginlisp
(compile-and-display test-expression)
\endlisp
which does exactly the same call to {\tt compile} as above.

The second way to run the compiler is to apply the procedure {\tt
compile-and-go} to the expression.  This compiles the expression and
executes it in the environment of the explicit control evaluator machine
{\tt eceval}.  When evaluation is complete, you are left in the
read-eval-print loop talking to the explicit control evaluator.  Then you
can experiment with the compiled expression by evaluating further
expressions.

\subsubsection{Running the Evaluator}

The evaluator for this problem set is the explicit control evaluator of
section 5.4.  For your convenience, we have extended it to handle {\tt
cond} and {\tt let}.

To evaluate an expression in the {\tt eceval} read-eval-print loop,
type the expression after the prompt, followed by {\tt ctrl-X ctrl-E}.
After each evaluation, the simulator will print the number of stack
and machine operations required to execute the code.\footnote{These
counts may include a few extra operations needed to run the driver
loop itself.  This is a small constant overhead that you can ignore
when you collect statistics.}

Here is an example:

\beginlisp
(compile-and-go
 '(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))))
(total-pushes = 0 maximum-depth = 0)
(machine-ops = 12)
;;; EC-Eval value:  (the-unspecified-value)
\endlisp

\beginlisp
;;; EC-Eval input: (fact 4)          <== you type this and ctrl-X ctrl-E
(total-pushes = 31 maximum-depth = 14)
(machine-ops = 278)
;;; EC-Eval value: 24
\endlisp

\beginlisp
;;; EC-Eval input: (fact (fact 3))   <== you type this
(total-pushes = 68 maximum-depth = 20)
(machine-ops = 594)
;;; EC-Eval value: 720
\endlisp

\beginlisp
;;; EC-Eval input: fact
(total-pushes = 0 maximum-depth = 0)
(machine-ops = 13)
;;; EC-Eval value: <compiled-procedure>
\endlisp

\beginlisp
;;; EC-Eval input:
(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) ;<== fact gets redefined
(total-pushes = 3 maximum-depth = 3)
(machine-ops = 45)
;;; EC-Eval value: (the-unspecified-value)
\endlisp

\beginlisp
;;; EC-Eval input: fact
(total-pushes = 0 maximum-depth = 0)
(machine-ops = 13)
;;; EC-Eval value:
;;;  (compound-procedure (n)
;;;    ((if (= n 0) 1 (* n (fact (- n 1))))) <procedure-env>)
\endlisp

\beginlisp
;;; EC-Eval input:
(fact 4)                ;<== redefined fact gets interpreted -- slower!
(total-pushes = 144 maximum-depth = 20)
(machine-ops = 1572)
;;; EC-Eval value: 24
\endlisp

To exit back to regular Scheme type {\tt ctrl-C ctrl-C}.  To reenter the
evaluator with the previous global environment, you may do another {\tt
compile-and-go}, or you may simply evaluate {\tt (eval-loop)} in Scheme.
To start the evaluator with a reinitialized global environment, evaluate
{\tt start-eceval}.

\paragraph{Lab exercise 3A:}
Compile and run the (Scheme) definitions of the {\tt delq1} and {\tt
delq2} procedures, and make tables to record statistics.

\paragraph{Lab exercise 3B:}
Now redefine them within the {\tt eceval} read-eval-print loop and record
corresponding statistics for the interpreted definitions.

\paragraph{PostLab exercise 3C:}
Derive formulas for the total number of machine operations, total number
of pushes, and maximum stack depth required, as functions of the length of
the list, for the compiled and interpreted delete procedures.

\bigskip

The file {\tt naivecom.scm} contains the simple code generator
described in lecture which omits the stack optimizations carried out
in the Notes.  Because its procedures satisfy a simplified
``contract'' slightly different from that in the Notes, the naive
compiler is not compatible with the evaluator.  We display its code
using {\tt naive-compile-and-display}, and run naive code using {\tt
naive-compile-and-go}.  For example: \beginlisp (define (fibexp n)
  `(begin
     (define (fib m)
       (if (< m 2)
           m
           (+ (fib (- m 1)) (fib (- m 2)))))
     (fib ,n)))
\endlisp

\beginlisp
(naive-compile-and-go (fibexp 8))
(total-pushes = 1469 maximum-depth = 27)
(machine-ops = 6544)
;value of expression: 21
;Value: \#[useless-value]
\endlisp

Compare this to:
\beginlisp
(compile-and-go (fibexp 8))
(total-pushes = 332 maximum-depth = 23)
(machine-ops = 2772 )
;;; EC-Eval value: 21
\endlisp

So in this case, the optimized code executes about 40\% as many
instructions and uses 85\% of the stack space as the code generated by
the naive compiler.

\paragraph{Lab exercise 4:}  Repeat exercise 3 for the naively compiled
delete procedures.

\paragraph{Post Lab exercise 5:} We'll consider the time used for a
computation to be the total number of machine operations, and the space
used to be the maximum stack depth.  For each of your list-deletion
procedures, determine the limiting ratio, as the list length becomes
large, of the time and space requirements for your hand-coded machines,
versus the time and space requirements for the compiled, naive-compiled,
and interpreted code.

\paragraph{Lab exercise 6A:} Make listings of the code generated by the
compiler from the Notes for the definitions of {\tt delq1} and {\tt
delq2}.

\paragraph{PostLab exercise 6B:}
Compare the listings with your hand-coded versions to see why the
compiler's code is less efficient than yours.  Suggest one improvement to
the compiler that could lead it to do a better job.  Write one or two
clear paragraphs indicating how you might go about implementing your
improvement.  You needn't actually carry out the the implementation, but
your description should be reasonably precise.  For example, you should
say what new information the compiler should keep track of, what new data
structures may be required to maintain this information, and how the
information should be used in generating the new, improved code.

\end{document}