diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 16f85d1..87fd9f6 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -59,10 +59,10 @@ export default defineConfig({ { text: '4. N²-Knights', link: '/exams/n2-knights/' }, { text: '5. Filetree', link: '/exams/filetree/' }, { text: '6. Text Justification', link: '/exams/justify/' }, - // { text: '7. Photographing Skyscrapers', link: '/exams/filetree' }, - // { text: '8. Non-deterministic Finite Automata', link: '/exams/filetree' }, - // { text: '9. Least Common Anchestor', link: '/exams/filetree' }, - // { text: '10. Building Trees', link: '/exams/filetree' }, + { text: '7. Photographing Skyscrapers', link: '/exams/photo-skyscraper/' }, + { text: '8. Non-deterministic Finite Automata', link: '/exams/finite-automata/' }, + { text: '9. Least Common Anchestor', link: '/exams/least-common-ancestor/' }, + { text: '10. Building Trees', link: '/exams/building-trees/' }, // { text: '11. Square Code', link: '/exams/filetree' }, // { text: '12. Rock, Paper, Scissors', link: '/exams/filetree' }, // { text: '13. Sierpinski Carpet', link: '/exams/filetree' }, diff --git a/exams/building-trees/index.md b/exams/building-trees/index.md new file mode 100644 index 0000000..e5a9a7d --- /dev/null +++ b/exams/building-trees/index.md @@ -0,0 +1,253 @@ +--- +outline: deep +title: "Exam Task: Building Trees" +subtitle: "From: Exam 2 - 2023" +--- + +# Building Trees + + +In this task, you will build a tree from an intial tree and a sequence of edges. Your task is to +iterate through the edge sequence from left to right and expand successively the initial tree. Each +edge is a pair $(s,t)$. To expand the tree by the edge $(s,t)$, you must traverse through the tree +and locate the node $s$. Next, you add $t$ among its children. If $s$ does not occur in the tree, +the edge is ignored. + +For example, suppose that the initial tree is just a single node (a leaf) $1$ and the edge sequence +equals $(1,2),(1,3),(5,7),(2,4),(4,5),(3,6)$. By expanding the initial tree by the edges, we obtain +the following sequence of trees: + + + +Note that the third edge $(5,7)$ was ignored because there is no node $5$ in the currently +constructed tree. So the currently constructed tree remains unchanged. + +## Racket + +In \textbf{Racket}, implement a function `(build-tree init edges)` that takes an initial +tree `init` and a list of edges `edges`, and returns the tree created by expanding +the initial tree by the edges. + +To represent trees in Racket, use the following structures: +```scheme +(struct node (val kids) #:transparent) +(struct leaf (val) #:transparent) +``` +Thus the leaves (nodes without children) are represented as, for instance, `(leaf 6)`. +The nodes with children are represented as, for instance, `(node 1 (list (leaf 2) (leaf 3)))`. + +To implement the function `build-tree`, implement first a function `(add-edge edge tree)` +expanding a given tree `tree` by a single edge `edge`. For example, +```scheme +> (add-edge '(2 4) (node 1 (list (leaf 2) (leaf 3)))) +(node 1 (list (node 2 (list (leaf 4))) (leaf 3))) +``` + +When you add a new leaf to a list of children, prepend it at the front. For example, +```scheme +> (add-edge '(1 3) (node 1 (list (leaf 2)))) +(node 1 (list (leaf 3) (leaf 2))) +``` + +To make the output of the function `build-tree` unique, sort the children of every node +in the resulting tree based on their values. You may assume, that the node values are numbers. +For example, +```scheme +(node 1 (list (node 2 (list (leaf 4))) (leaf 3))) ; correct +(node 1 (list (leaf 3) (node 2 (list (leaf 4))))) ; not correct +``` + +Your file should be called `task3.rkt` and should export the `add-edge` and +`build-tree` functions and the structures `node` and `leaf`. +```scheme +#lang racket + +(provide add-edge + build-tree + (struct-out node) + (struct-out leaf)) + +(struct node (val kids) #:transparent) +(struct leaf (val) #:transparent) + +(define (add-edge edge tree) + ; Implement me! + ) + +(define (build-tree init edges) + ; Implement me! + ) +``` + +### Example + +```scheme +> (build-tree (leaf 1) '((1 2) (1 3) (5 7) (2 4) (4 5) (3 6))) +(node 1 (list (node 2 (list (node 4 (list (leaf 5))))) + (node 3 (list (leaf 6))))) +``` + +### Hints + +To sort the children, use the function `(sort lst less-than?)` sorting a list `lst` +comparing its elements by a function `less-than?`. For example, +```scheme +> (sort '((1 3) (2 2) (3 1)) (lambda (p q) (< (cadr p) (cadr q)))) +'((3 1) (2 2) (1 3)) +``` + +::: details Solution +```scheme +#lang racket + +(provide add-edge build-tree) + +(struct node (val kids) #:transparent) +(struct leaf (val) #:transparent) + +(define (value t) + (match t + [(leaf x) x] + [(node x kids) x])) + +(define (sortnodes ns) + (sort ns (lambda (p q) (< (value p) (value q))))) + + +(define (add-edge edge tree) + (define s (car edge)) + (define t (cadr edge)) + (match tree + [(leaf x) + (if (= x s) + (node x (list (leaf t))) + (leaf x))] + [(node x kids) + (if (= x s) + (node x (sortnodes (cons (leaf t) kids))) + (node x (map (curry add-edge edge) kids)))] + )) + + +(define (build-tree init edges) (foldl add-edge init edges)) + +; (define (build-tree init edges)) +; (add-edge '(2 4) (node 1 (list (le)))) +(add-edge '(2 4) (leaf 2)) +(add-edge '(2 4) (node 1 (list (leaf 2) (leaf 3)))) + + +(build-tree (leaf 1) '((1 2) (1 3) (2 4) (4 5) (3 6))) +(node 1 (list (node 2 (list (node 4 (list (leaf 5))))) (node 3 (list (leaf 6))))) +``` +::: + +## Haskell + +In Haskell, implement a function +`buildTree :: Ord a => Tree a -> [Edge a] -> Tree a` that takes an initial +tree and a list of edges, and returns the tree created by expanding +the initial tree by the edges. + +To represent trees and edges, use the following data types: +```haskell +data Tree a = Leaf { val :: a } + | Node { val :: a, + kids :: [Tree a] } deriving (Eq,Show) + +type Edge a = (a,a) +``` +Thus the leaves (nodes without children) are represented as, for instance, `Leaf {val = 6}`. +The nodes with children are represented as, for instance, +```haskell +Node {val = 1, kids = [Leaf {val = 2,Leaf {val = 3}]} +``` + +To implement the function `buildTree`, implement first a function +```haskell +addEdge :: Eq a => Tree a -> Edge a -> Tree a +``` +expanding a given tree by a single edge. For example, +```haskell +> addEdge (Node {val = 1, kids = [Leaf {val = 2}, Leaf {val = 3}]}) (2,4) +Node {val = 1, kids = [Node {val = 2, kids = [Leaf {val = 4}]}, + Leaf {val = 3}]} +``` + +When you add a new leaf to a list of children, prepend it at the front. For example, +```haskell +> addEdge (Node {val = 1, kids = [Leaf {val = 2}]}) (1,3) +Node {val = 1, kids = [Leaf {val = 3}, Leaf {val = 2}]} +``` + +To make the output of the function `buildTree` unique, sort the children of every node +in the resulting tree based on their values. You may assume, that the node values belongs to +the typeclass `Ord`. For example, + +```haskell + Node {val = 1, kids = [Node {val = 2, kids = [Leaf {val = 4}]}, + Leaf {val = 3}]} -- correct + Node {val = 1, kids = [Leaf {val = 3}, + Node {val = 2, kids = [Leaf {val = 4}]}]} -- not correct +``` + +Your file should be called `Task4.hs` and should export the `buildTree`, +`addEdge` functions and the type `Tree`. + +```haskell +module Task4 (addEdge, buildTree, Tree(..)) where +import Data.List + +data Tree a = Leaf { val :: a } + | Node { val :: a, + kids :: [Tree a] } deriving (Eq,Show) + +type Edge a = (a,a) + +addEdge :: Eq a => Tree a -> Edge a -> Tree a +-- implement me! + +buildTree :: Ord a => Tree a -> [Edge a] -> Tree a +-- implement me! +``` + +### Example + +```haskell +> buildTree (Leaf {val=1}) [(1,2),(1,3),(5,7),(2,4),(4,5),(3,6)] +Node {val = 1, kids = [Node {val = 2, kids = [Node {val = 4, + kids = [Leaf {val = 5}]}]}, + Node {val = 3, kids = [Leaf {val = 6}]}]} +``` + +### Hints + +To sort the children, use the function `sortOn :: Ord b => (a -> b) -> [a] -> [a]` +from the library `Data.List` sorting a list by converting them into a values of +a type inside `Ord`. For example, +```haskell +> sortOn snd [(1,3),(2,2),(3,1)] +[(3,1),(2,2),(1,3)] +``` + +::: details Solution +```haskell +import Data.List + +data Tree a = Leaf { val :: a } + | Node { val :: a, + kids :: [Tree a] } deriving Show +type Edge a = (a,a) + +tr = Node {val = 1, kids = [Leaf {val = 2},Leaf {val = 3}]} + +addEdge (Leaf x) (s,t) | x == s = Node x [Leaf t] + | otherwise = Leaf x +addEdge (Node x kids) (s,t) + | x == s = Node x (sortOn val ((Leaf t):kids)) + | otherwise = Node x [addEdge n (s,t) | n <- kids] + + +buildTree tree edges = foldl addEdge tree edges +``` +::: diff --git a/exams/building-trees/task.hs b/exams/building-trees/task.hs new file mode 100644 index 0000000..9435ef5 --- /dev/null +++ b/exams/building-trees/task.hs @@ -0,0 +1,17 @@ +import Data.List + +data Tree a = Leaf { val :: a } + | Node { val :: a, + kids :: [Tree a] } deriving Show +type Edge a = (a,a) + +tr = Node {val = 1, kids = [Leaf {val = 2},Leaf {val = 3}]} + +addEdge (Leaf x) (s,t) | x == s = Node x [Leaf t] + | otherwise = Leaf x +addEdge (Node x kids) (s,t) + | x == s = Node x (sortOn val ((Leaf t):kids)) + | otherwise = Node x [addEdge n (s,t) | n <- kids] + + +buildTree tree edges = foldl addEdge tree edges diff --git a/exams/building-trees/task.rkt b/exams/building-trees/task.rkt new file mode 100644 index 0000000..21dc9f1 --- /dev/null +++ b/exams/building-trees/task.rkt @@ -0,0 +1,41 @@ +#lang racket + +(provide add-edge build-tree) + +(struct node (val kids) #:transparent) +(struct leaf (val) #:transparent) + +(define (value t) + (match t + [(leaf x) x] + [(node x kids) x])) + +(define (sortnodes ns) + (sort ns (lambda (p q) (< (value p) (value q))))) + + +(define (add-edge edge tree) + (define s (car edge)) + (define t (cadr edge)) + (match tree + [(leaf x) + (if (= x s) + (node x (list (leaf t))) + (leaf x))] + [(node x kids) + (if (= x s) + (node x (sortnodes (cons (leaf t) kids))) + (node x (map (curry add-edge edge) kids)))] + )) + + +(define (build-tree init edges) (foldl add-edge init edges)) + +; (define (build-tree init edges)) +; (add-edge '(2 4) (node 1 (list (le)))) +(add-edge '(2 4) (leaf 2)) +(add-edge '(2 4) (node 1 (list (leaf 2) (leaf 3)))) + + +(build-tree (leaf 1) '((1 2) (1 3) (2 4) (4 5) (3 6))) +(node 1 (list (node 2 (list (node 4 (list (leaf 5))))) (node 3 (list (leaf 6))))) diff --git a/exams/building-trees/trees.tex b/exams/building-trees/trees.tex new file mode 100644 index 0000000..0d1060a --- /dev/null +++ b/exams/building-trees/trees.tex @@ -0,0 +1,47 @@ +\documentclass[border=0pt]{standalone} +\usepackage{graphicx} +\usepackage{fancyvrb} +\usepackage{xcolor} +\usepackage{bm} +\usepackage{pgf} +\usepackage{tikz} +\usetikzlibrary{arrows,automata,positioning} + +\definecolor{myGray}{rgb}{0.85,0.85,0.85} + +\begin{document} + +\begin{tikzpicture} + \node (1) {$1$}; + + \node[right=of 1] (12) {$1$} + child {node {$2$}}; + + \node[right=1.5cm of 12] (123) {$1$} + child {node {$2$}} + child {node {$3$}}; + + \node[right=2cm of 123] (123') {$1$} + child {node {$2$}} + child {node {$3$}}; + + \node[right=2cm of 123'] (1234) {$1$} + child {node {$2$} + child {node {$4$}}} + child {node {$3$}}; + + \node[right=2cm of 1234] (12345) {$1$} + child {node {$2$} + child {node {$4$} + child {node {$5$}}}} + child {node {$3$}}; + + \node[right=2cm of 12345] {$1$} + child {node {$2$} + child {node {$4$} + child {node {$5$}}}} + child {node {$3$} + child {node {$6$}}}; + +\end{tikzpicture} +\end{document} diff --git a/exams/filetree/index.md b/exams/filetree/index.md index bad1deb..4b46b18 100644 --- a/exams/filetree/index.md +++ b/exams/filetree/index.md @@ -9,7 +9,7 @@ subtitle: "In Racket and Haskell" A filetree can be used to efficiently search/replace in large filesystems. You can think of it as a tree with a variable size of nodes -```{.tight-code} +``` . ├── scripts │   ├── emptydir @@ -138,9 +138,9 @@ For splitting a string into a list of strings you can make use of the function ` ## Haskell -For the Haskell implementation you are provided with a module [`FTree.hs`](../../code/FTree.hs) -which contains a `FTree` type including a `Data.Map` from keys to values. We quote the definition -here: +For the Haskell implementation you are provided with a module +[`FTree.hs`](https://github.com/aicenter/FUP/blob/main/code/FTree.hs) which contains a `FTree` type +including a `Data.Map` from keys to values. We quote the definition here: ```haskell import Data.Map (Map) import qualified Data.Map as Map diff --git a/exams/finite-automata/dfa.tex b/exams/finite-automata/dfa.tex new file mode 100644 index 0000000..e7031c8 --- /dev/null +++ b/exams/finite-automata/dfa.tex @@ -0,0 +1,27 @@ +\documentclass[border=0pt]{standalone} +\usepackage{graphicx} +\usepackage{fancyvrb} +\usepackage{xcolor} +\usepackage{bm} +\usepackage{pgf} +\usepackage{tikz} +\usetikzlibrary{automata, positioning, arrows} + + +\begin{document} + +\begin{tikzpicture}[>=stealth,auto,node distance=20mm,state/.append style={fill=black!10}] + \node[state, initial] (1) {$1$}; + \node[state, accepting, above right of=1] (2) {$2$}; + % \node[state, accepting, right of=2] (3) {$3$}; + \node[state, accepting, below right of=1] (3) {$3$}; + \node[state, above right of=3] (4) {$4$}; + + \draw (1) edge[->,above] node {$a$} (2) + (2) edge[loop above] node {$b$} (2) + (1) edge[->,above] node {$a$} (3) + (3) edge[->, below, bend right] node {$b$} (4) + (4) edge[->, above, bend right] node {$a$} (3) + (2) edge[->, above] node {$a$} (4); +\end{tikzpicture} +\end{document} diff --git a/exams/finite-automata/index.md b/exams/finite-automata/index.md new file mode 100644 index 0000000..3c1dad7 --- /dev/null +++ b/exams/finite-automata/index.md @@ -0,0 +1,303 @@ +--- +outline: deep +title: "Exam Task: Non-deterministic Finite Automata" +subtitle: "From: Exam 2 - 2021" +--- + + +# Non-deterministic Finite Automata + +In the seminars, we have encountered *Deterministic Finite Automata* (DFAs). In this task, we will +work with a generalized version of the DFA that is called *Non-deterministic Finite Automaton +(NFA)*. NFA is the 5-tuple + +* set of states $\mathcal{Q}$, +* a finite set of input symbols $\Sigma$ (called alphabet), +* a set of transitions $\Delta \subseteq \mathcal{Q} \times \Sigma \times \mathcal{Q}$, +* a start state $q_0$, +* a set of final states $\mathcal{F} \subseteq \mathcal{Q}$. + +In other words, NFA is just a directed graph whose vertices are states and transitions are edges +labelled by symbols from $\Sigma$, i.e., if $(s,a,t)\in\Delta$ then there is an edge leading +from the state $s$ to the state $t$ labelled by $a\in\Sigma$. We say that NFA accepts a word +$w=a_1a_2\ldots a_n\in\Sigma^*$ (i.e., a finite sequence of symbols form $\Sigma$) if there +exists a path leading from the start state into a final one labelled consecutively by symbols +$a_1,a_2,\ldots,a_n$. + +An example of an NFA is depicted in the figure below. This NFA accepts e.g. words $abb$ or +$aba$. On the other hand, it accept neither $ba$ nor $abab$. + +Example of NFA where $\mathcal Q=\{1,2,3,4\}$, $\Sigma=\{a,b\}$, $q_0=1$, $\mathcal F=\{2,3\}$ +and $\Delta=\{(1,a,2),(2,b,2),(1,a,3),(3,b,4),(4,a,3),(2,a,4)\}$. + + + + +## Haskell +Your assignment is to implement a function that generates the language accepted by a given NFA. +However, since such a language is potentially infinite, the problem is simplified to listing all +words of given length that are accepted by the automaton. + +For the purpose of this task, the NFA is defined as follows. First, each possible transition is +represented as the triplet + +```haskell +data Transition a b = Tr a b a +``` + +where the type `a` represents the states in $\mathcal{Q}$ and `b` are the symbols $\Sigma$; +hence, the transition from the first member of the triplet to the third member of the triplet is +possible if and only if the next input symbol is equal to the second member of the triplet. +The NFA itself is defined as +```haskell +data Automaton a b = NFA [Transition a b] a [a] +``` +where the first member is the exhaustive list of transitions, the second member is the start state +$q_0$, and the third is the list of final states $\mathcal{F}$. The particular automaton +from the figure is defined as follows: +```haskell +nfa::Automaton Int Char +nfa = NFA [Tr 1 'a' 2, + Tr 2 'b' 2, + Tr 1 'a' 3, + Tr 3 'b' 4, + Tr 4 'a' 3, + Tr 2 'a' 4] + 1 + [2,3] +``` + +### `accepts`-function +The first part of the task is to decide whether a particular word is accepted by a given automaton. +To that end, implement the function +```haskell +accepts :: (Eq a, Eq b) => Automaton a b -> [b] -> Bool +``` +which takes and automaton and a list of symbols that represents the word, and returns \texttt{True} +iff the word is accepted. Notice, `a` and `b` are instances of `Eq`. The function is used as +follows. +```haskell +> accepts nfa "aba" +True + +> accepts nfa "abab" +False +``` + + +_**Hint:**_ One possibility how to implement the function `accepts` is to maintain a list of +accessible states after consuming an initial segment of the input word. + +Consider the NFA from the figure and the word `"aba"`. We start with the list containing the start +state `[1]`. Then we modify this list by the transitions corresponding to the letters as follows: +```haskell +[1] -a-> [2,3] -b-> [2,4] -a-> [3,4] +``` +Finally, we check if the resulting list of states +contains a final state. `3` is a final state, the output is `True`. + +Another example for `"abab"` is +```haskell +[1] -a-> [2,3] -b-> [2,4] -a-> [3,4] -b-> [4] +``` +As `4` is not a final state, the ouput is `False`. + +One more example for `"ba"`: +```haskell +[1] -b-> [] -a-> [] +``` +So the output is `False`. + + +### `lwords`-function + +Next, given the automaton, its alphabet, and a length, generate the list of all words of the given +length that are accepted by the automaton. Implement the function +```haskell +lwords :: (Eq a, Eq b) => [b] -> Automaton a b -> Int -> [[b]] +``` +where the first input is the alphabet as list of unique symbols, the second is the automaton, and +the third is the word length. The function returns a list of the accepted words, i.e., a list of +lists of symbols. In terms of the task evaluation, the ordering of the words is not relevant. The +function operates as follows. + +```haskell +> lwords "ab" nfa 0 +[] +> lwords "ab" nfa 1 +["a"] +> lwords "ab" nfa 3 +["aaa","aba","abb"] +``` + +_**Hint:**_ first, generate all possible words of the given length. Then, filter the words using +the function `accepts`. + +::: details Solution +```haskell +import Control.Monad.State + +data Transition a b = Tr a b a deriving Show +data Automaton a b = NFA [(Transition a b)] a [a] deriving Show + +--nfa::Automaton Int Char +--nfa = NFA [(Tr 1 'a' 1), (Tr 1 'b' 1), (Tr 1 'c' 1), (Tr 1 'b' 2), (Tr 1 'c' 2)] 1 [2] + + +walk :: (Eq a, Eq b) => Automaton a b -> a -> [b] -> Bool +walk aut@(NFA trs start finals) state [] | elem state finals = True + | otherwise = False +walk aut@(NFA trs start finals) state (c:ws) = or [ (walk aut t ws) | tr@(Tr f c' t) <- trs, c' == c, f == state] + +accepts :: (Eq a, Eq b) => Automaton a b -> [b] -> Bool +accepts aut@(NFA trs start finals) word = walk aut start word + +combinations :: [a] -> Int -> State [[a]] [[a]] +combinations cs 0 = do ws <- get + return ws +combinations cs n = do ws <- get + let ws' = [ c:w | c <- cs , w <- ws] + put ws' + combinations cs (n-1) + +lwords :: (Eq a, Eq b) => [b] -> Automaton a b -> Int -> [[b]] +lwords abc aut n = reverse $ [ w | w <- p, accepts aut w] where + p = evalState (combinations abc n) [[]] + +``` +::: + +## Racket + +Implement the functions `accepts` and `lwords`. In this task, you may assume that the input word +will always be a string. Thus you can use functions `string->list` and `list->string` for converting +strings to lists of characters and vice versa. +Of course, we need to adapt the automaton representation for Racket. +First, each possible transition is represented as the triplet: +```scheme +(struct transition (from-state symbol to-state)) +``` +where `from-state` and `to-state` are states in $\mathcal{Q}$ and `symbol` is a symbol in $\Sigma$. +We can construct the NFA itself as +```scheme +(struct automaton (trans init-state final-states)) +``` +where the first member `trans` is the exhaustive list of transitions, the second member `init-state` +is the start state $q_0$, and the third member `final-state` is the list of final states +$\mathcal{F}$. +The particular automaton from the figure is defined as follows. +```scheme +(define nfa + (make-automaton + (list (make-trans 1 #\a 2) + (make-trans 2 #\b 2) + (make-trans 1 #\a 3) + (make-trans 3 #\b 4) + (make-trans 4 #\a 3) + (make-trans 2 #\a 4)) + 1 + (list 2 3))) +``` + +Implement the function +```scheme +(accepts automaton word) +``` +which takes an automaton and a string that represents the word to be parsed by the automaton, and +returns `#t` if the word is accepted, `#f` otherwise. +The function is used as follows: +```scheme +> (accepts nfa "aba") +#t + +> (accepts nfa "abab") +#f +``` +\noindent + +Next, given the automaton, its alphabet, and a length, generate the list of all words of the given +length that are accepted by the automaton. Implement the function +```scheme +(lwords alphabet automaton n) +``` +where the first input `alphabet` is the alphabet passed as a string, the second input +`automaton` is the automaton, and the third input `n` is the word length. +The function returns a list of the accepted words, i.e., a list of lists of characters. +In terms of the task evaluation, the ordering of the words is not relevant. +The function operates as follows: +```scheme +> (lwords "ab" nfa 0) +'() + +> (lwords "ab" nfa 1) +'("a") + +> (lwords "ab" nfa 3) +'("aaa" "aba" "abb") +``` + +For testing purposes your file should be named `task3.rkt` and start with the following lines: +```scheme +#lang racket + +(provide accepts + lwords) +``` + +::: details Solution +```scheme +#lang racket + +(provide accepts + lwords) + +(struct transition (from-state symbol to-state)) +(struct automaton (trans init-state final-states)) + +(define ((s-next fa a) s) + (define tr (automaton-trans fa)) + (map transition-to-state + (filter (lambda (t) (and (equal? s (transition-from-state t)) + (equal? a (transition-symbol t)))) + tr))) + +(define ((next fa) a ss) + (define tr (automaton-trans fa)) + (apply append (map (s-next fa a) ss))) + +(define (accepts fa w) + (define states (foldl (next fa) (list (automaton-init-state fa)) (string->list w))) + (if (eq? #f (ormap (lambda (s) (member s (automaton-final-states fa))) states)) + #f + #t)) + +(define (words alp n) + (cond + ((= n 0) '()) + ((= n 1) (map list alp)) + (else (apply append + (map (lambda (w) + (map (lambda (a) (cons a w)) alp)) + (words alp (- n 1))))))) + +(define (lwords alp nfa n) + (filter (lambda (w) (accepts nfa (list->string w))) + (words (string->list alp) n))) + + +(define nfa + (automaton + (list (transition 1 #\a 2) + (transition 2 #\b 2) + (transition 1 #\a 3) + (transition 3 #\b 4) + (transition 4 #\a 3) + (transition 2 #\a 4)) + 1 + (list 2 3))) + + +(accepts nfa "aba") +(lwords "ab" nfa 3) +``` +::: diff --git a/exams/finite-automata/task.rkt b/exams/finite-automata/task.rkt new file mode 100644 index 0000000..5aef08a --- /dev/null +++ b/exams/finite-automata/task.rkt @@ -0,0 +1,53 @@ +#lang racket + +(provide accepts + lwords) + +(struct transition (from-state symbol to-state)) +(struct automaton (trans init-state final-states)) + +(define ((s-next fa a) s) + (define tr (automaton-trans fa)) + (map transition-to-state + (filter (lambda (t) (and (equal? s (transition-from-state t)) + (equal? a (transition-symbol t)))) + tr))) + +(define ((next fa) a ss) + (define tr (automaton-trans fa)) + (apply append (map (s-next fa a) ss))) + +(define (accepts fa w) + (define states (foldl (next fa) (list (automaton-init-state fa)) (string->list w))) + (if (eq? #f (ormap (lambda (s) (member s (automaton-final-states fa))) states)) + #f + #t)) + +(define (words alp n) + (cond + ((= n 0) '()) + ((= n 1) (map list alp)) + (else (apply append + (map (lambda (w) + (map (lambda (a) (cons a w)) alp)) + (words alp (- n 1))))))) + +(define (lwords alp nfa n) + (filter (lambda (w) (accepts nfa (list->string w))) + (words (string->list alp) n))) + + +(define nfa + (automaton + (list (transition 1 #\a 2) + (transition 2 #\b 2) + (transition 1 #\a 3) + (transition 3 #\b 4) + (transition 4 #\a 3) + (transition 2 #\a 4)) + 1 + (list 2 3))) + + +(accepts nfa "aba") +(lwords "ab" nfa 3) diff --git a/exams/finite-automata/task3_4_en.pdf b/exams/finite-automata/task3_4_en.pdf new file mode 100644 index 0000000..cf782f8 Binary files /dev/null and b/exams/finite-automata/task3_4_en.pdf differ diff --git a/exams/least-common-ancestor/index.md b/exams/least-common-ancestor/index.md new file mode 100644 index 0000000..b5cbc17 --- /dev/null +++ b/exams/least-common-ancestor/index.md @@ -0,0 +1,227 @@ +--- +outline: deep +title: "Exam Task: Least Common Ancestor" +subtitle: "From: Exam 3 - 2023" +--- + +# Least Common Ancestor + +Suppose we have a binary tree $t$. For any two nodes $x,y$ in the tree $t$, the *least common +ancestor* of $x$ and $y$ is defined as the node $z$ satisfying the following two conditions: + +1. $x$ and $y$ are descendants of $z$, +2. if there is a node $z'$ having $x$ and $y$ as descendants, then $z$ is descendant of $z'$. + +To find the least common ancestor of two nodes $x$ and $y$ in a tree $t$, we follow the steps below: + +1. find the path $p_x$ from the root $r$ of $t$ to $x$ (i.e., a list of nodes starting in $r$ and + ending in $x$), +2. find the path $p_y$ from $r$ to $y$, +3. consider the common prefix of $p_x$ and $p_y$, the last node in the common prefix is the least + common ancestor. + +Consider, for example, the binary tree depicted in Figure~\ref{fig:tree}. The least common ancestor +of $3$ and $5$ is $2$. Indeed, the path from the root $1$ to $3$ is $1,2,3$. The path from $1$ +to $5$ is $1,2,4,5$. Their common prefix is $1,2$ whose last element is $2$. + +Similarly, the least common ancestor of $5$ and $8$ is $1$. The least common ancestor of +$7$ and $7$ is $7$. + + + + +## Racket + +In Racket, implement a function `(common-ancestor x y tree)` that takes +two nodes `x`, `y` and a binary tree `tree`, and returns the least common-ancestor +of `x` and `y` in `tree`. If `x` or `y` does not belong to `tree`, +the function returns \Verb|#f|. + +To represent binary trees in Racket, use the following structures: +```scheme +(struct node (val left right) #:transparent) +(struct leaf (val) #:transparent) +``` +Thus the leaves (nodes without children) are represented as, for instance, `(leaf 6)`. +The nodes with children are represented as, for instance, `(node 1 (leaf 2) (leaf 3))`. + +To implement the function `common-ancestor`, implement first a function +`(find-path x tree)` that finds a path from the root of `tree` to `x`. +For example, +```scheme +(define tree (node 1 (node 2 (leaf 5) (leaf 6)) + (node 3 (leaf 4) (leaf 7)))) + +> (find-path 7 tree) +'(1 3 7) +``` + +Your file should be called \texttt{task3.rkt} and should export the `find-path` and +`common-ancestor` functions and the structures `node` and `leaf`. +```scheme +#lang racket + +(provide find-path + common-ancestor + (struct-out node) + (struct-out leaf)) + +(struct node (val kids) #:transparent) +(struct leaf (val) #:transparent) + +(define (find-path x tree) + ; Implement me! + ) + +(define (common-ancestor x y tree) + ; Implement me! + ) +``` + +### Example + +```scheme +(define tree2 (node 1 (node 2 (leaf 3) + (node 4 (leaf 5) + (leaf 6))) + (node 7 (leaf 8) + (leaf 9)))) + +> (common-ancestor 3 5 tree2) +2 + +> (common-ancestor 3 15 tree2) +#f +``` + +### Hints + +To find the common prefix of two lists, use the function `(take-common-prefix lst1 lst2)`. + +::: details Solution +```scheme +#lang racket + +(provide find-path + common-ancestor + (struct-out node) + (struct-out leaf)) + +(struct node (val left right) #:transparent) +(struct leaf (val) #:transparent) + +(define tree (node 1 (node 2 (leaf 5) (leaf 6)) (node 3 (leaf 4) (leaf 7)))) + +(define (find-path x tree) + (match tree + [(leaf y) (if (equal? x y) + (list x) + '())] + [(node y l r) (if (equal? x y) + (list x) + (let* ([pl (find-path x l)] + [pr (find-path x r)] + [both (append pl pr)]) + (if (null? both) + '() + (cons y both))))])) + +(define (common-ancestor x y tree) + (define px (find-path x tree)) + (define py (find-path y tree)) + (define common (take-common-prefix px py)) + (if (null? common) + #f + (last common))) +``` +::: + +## Haskell + +In \textbf{Haskell}, implement a function +`commonAncestor :: Eq a => a -> a -> Tree a -> Maybe a` that takes +two nodes and a binary tree, and returns the least common ancestor of these two nodes. +If it does not exist, the function returns `Nothing`. + +To represent binary trees in Haskell, use the following data type: +```haskell +data Tree a = Leaf a + | Node a (Tree a) (Tree a) deriving (Eq,Show) +``` +Thus the leaves (nodes without children) are represented as, for instance, `Leaf 6`. +The nodes with children are represented as, for instance, +`Node 1 (Leaf 2) (Leaf 3)`. + +To implement the function `commonAncestor`, implement first a function\\ +`findPath :: Eq a => a -> Tree a -> [a]` that finds for a given node and a binary tree +the path from the root to that node. For example, +```haskell +tree = Node 1 (Node 2 (Leaf 5) (Leaf 6)) (Node 3 (Leaf 4) (Leaf 7)) + +> findPath 7 tree +[1,3,7] +``` + +Your file should be called `Task4.hs` and should export the `commonAncestor`, +`findPath` functions and the type `Tree`. + +```haskell +module Task4 (findPath, commonAncestor, Tree(..)) where + +data Tree a = Leaf a + | Node a (Tree a) (Tree a) deriving (Eq,Show) + +findPath :: Eq a => a -> Tree a -> [a] +-- implement me! + +commonAncestor :: Eq a => a -> a -> Tree a -> Maybe a +-- implement me! +``` + +### Example + +```haskell +tree2 = Node 1 (Node 2 (Leaf 3) + (Node 4 (Leaf 5) + (Leaf 6))) + (Node 7 (Leaf 8) + (Leaf 9)) + +> commonAncestor 3 5 tree2 +Just 2 + +> commonAncestor 3 15 tree2 +Nothing +``` + +::: details Solution +```haskell +module Task4 (findPath, commonAncestor, Tree(..)) where + +data Tree a = Leaf a + | Node a (Tree a) (Tree a) deriving (Eq,Show) + +tree = Node 1 (Node 2 (Leaf 5) (Leaf 6)) (Node 3 (Leaf 4) (Leaf 7)) +tree2 = Node 1 (Node 2 (Leaf 3) + (Node 4 (Leaf 5) + (Leaf 6))) + (Node 7 (Leaf 8) + (Leaf 9)) + +findPath :: Eq a => a -> Tree a -> [a] +findPath x (Leaf y) | x == y = [x] + | otherwise = [] +findPath x (Node y l r) | x == y = [x] + | otherwise = let pl = findPath x l + pr = findPath x r + in if null (pl ++ pr) then [] + else y:(pl++pr) + +commonAncestor :: Eq a => a -> a -> Tree a -> Maybe a +commonAncestor x y t | null common = Nothing + | otherwise = Just $ last common + where px = findPath x t + py = findPath y t + common = [x | (x,y) <- zip px py, x==y ] +``` +::: diff --git a/exams/least-common-ancestor/main_en.pdf b/exams/least-common-ancestor/main_en.pdf new file mode 100644 index 0000000..4981c44 Binary files /dev/null and b/exams/least-common-ancestor/main_en.pdf differ diff --git a/exams/least-common-ancestor/task3.reference.rkt b/exams/least-common-ancestor/task3.reference.rkt new file mode 100644 index 0000000..919b04b --- /dev/null +++ b/exams/least-common-ancestor/task3.reference.rkt @@ -0,0 +1,32 @@ +#lang racket +(provide find-path + common-ancestor + (struct-out node) + (struct-out leaf)) + +(struct node (val left right) #:transparent) +(struct leaf (val) #:transparent) + +(define tree (node 1 (node 2 (leaf 5) (leaf 6)) (node 3 (leaf 4) (leaf 7)))) + +(define (find-path x tree) + (match tree + [(leaf y) (if (equal? x y) + (list x) + '())] + [(node y l r) (if (equal? x y) + (list x) + (let* ([pl (find-path x l)] + [pr (find-path x r)] + [both (append pl pr)]) + (if (null? both) + '() + (cons y both))))])) + +(define (common-ancestor x y tree) + (define px (find-path x tree)) + (define py (find-path y tree)) + (define common (take-common-prefix px py)) + (if (null? common) + #f + (last common))) \ No newline at end of file diff --git a/exams/least-common-ancestor/task4.reference.hs b/exams/least-common-ancestor/task4.reference.hs new file mode 100644 index 0000000..882ad04 --- /dev/null +++ b/exams/least-common-ancestor/task4.reference.hs @@ -0,0 +1,28 @@ +module Task4 (findPath, commonAncestor, Tree(..)) where + +data Tree a = Leaf a + | Node a (Tree a) (Tree a) deriving (Eq,Show) + +tree = Node 1 (Node 2 (Leaf 5) (Leaf 6)) (Node 3 (Leaf 4) (Leaf 7)) +tree2 = Node 1 (Node 2 (Leaf 3) + (Node 4 (Leaf 5) + (Leaf 6))) + (Node 7 (Leaf 8) + (Leaf 9)) + +findPath :: Eq a => a -> Tree a -> [a] +findPath x (Leaf y) | x == y = [x] + | otherwise = [] +findPath x (Node y l r) | x == y = [x] + | otherwise = let pl = findPath x l + pr = findPath x r + in if null (pl ++ pr) then [] + else y:(pl++pr) + +commonAncestor :: Eq a => a -> a -> Tree a -> Maybe a +commonAncestor x y t | null common = Nothing + | otherwise = Just $ last common + where px = findPath x t + py = findPath y t + common = [x | (x,y) <- zip px py, x==y ] + \ No newline at end of file diff --git a/exams/least-common-ancestor/tree.tex b/exams/least-common-ancestor/tree.tex new file mode 100644 index 0000000..e697455 --- /dev/null +++ b/exams/least-common-ancestor/tree.tex @@ -0,0 +1,27 @@ +\documentclass[border=0pt]{standalone} +\usepackage{graphicx} +\usepackage{fancyvrb} +\usepackage{xcolor} +\usepackage{bm} +\usepackage{pgf} +\usepackage{tikz} + + +\definecolor{myGray}{rgb}{0.85,0.85,0.85} + +\begin{document} + +\begin{tikzpicture}[level/.style = {level distance = 1.5cm, sibling distance = 1cm}, + level 1/.style={sibling distance=2cm}] + \node {$1$} + child {node {$2$} + child {node {$3$}} + child {node {$4$} + child {node {$5$}} + child {node {$6$}}}} + child {node {$7$} + child {node {$8$}} + child {node {$9$}}}; +\end{tikzpicture} + +\end{document} diff --git a/exams/n2-knights/index.md b/exams/n2-knights/index.md index 256c514..dcf3acd 100644 --- a/exams/n2-knights/index.md +++ b/exams/n2-knights/index.md @@ -17,7 +17,7 @@ looks like the letter "L". The $N^2$-knights puzzle concerns placing $O(N^2)$ knights on an $n \times n$ chessboard so that no two knights can attack each other. Below, you can see a valid configuration for a 8x8 board. - + Determine the validity of $N^2$-knights board configurations. diff --git a/exams/photo-skyscraper/index.md b/exams/photo-skyscraper/index.md new file mode 100644 index 0000000..0a8983c --- /dev/null +++ b/exams/photo-skyscraper/index.md @@ -0,0 +1,146 @@ +--- +outline: deep +title: "Exam Task: Photographing Skyscrapers" +subtitle: "From: Exam 1 - 2023" +--- + +# Photographing Skyscrapers + +You are an avid photographer that is obsessed with regular structures and you want to take pictures +of cities that are built on regular grids. It turns out that you are also really into roof tops so +you want to see as many of them in your pictures as possible. Naturally, you wonder from which side +of a given city (North/South/East/West) you should be taking the picture. Luckily a befriended +architect gave you maps of the cities you want to photograph. The maps are very simplified and can +be represented as lists of lists of integers, so for example in Scheme: + +```scheme +; north +(define city + '((3 0 3 7 3) + (2 5 5 1 2) + (6 5 3 3 2) + (3 3 5 4 9) + (3 5 3 9 0))) +; south +``` +Every number represents the height of a building. + +A roof is *visible* if all other roofs between it and the edge of the grid are *smaller* than it. +Only consider roofs in the same row or column. The first roof at the edge of a grid is always +visible. + + +## Racket + +In Racket, write a function `(best-view city)` that outputs the direction with the most roofs +visible, along with the number of roofs visible from that direction. The direction should be one of +four symbols: `'N`, `'S`, `'E`, and `'N`. The result should be a pair of the format +`'(direction . number)`. + +```scheme +(define city + '((3 3 3) + (1 2 3) + (1 2 3))) + +; 'N has 3 roofs, 'S has 5, 'E has 3, and 'W is the best with 7 +> (best-view city) +'(W . 7) +``` + +Your file should be called `task3.rkt` and should `provide` the `best-view` function. +```scheme +#lang racket + +(provide best-view) + +(define (best-view city) + ; Implement me! + ) +``` + +::: details Solution +```scheme +#lang racket +(provide best-view) + +(define (visible-roofs-row row [height 0] [n 0]) + (if (empty? row) + n + (if (< height (car row)) + (visible-roofs-row (cdr row) (car row) (+ n 1)) + (visible-roofs-row (cdr row) height n)))) + +(define (transpose mat) (apply map list mat)) + + +(define (visible-roofs city dir) + (define m + (match dir + ['W city] + ['E (map reverse city)] + ['N (transpose city)] + ['S ((compose (curry map reverse) transpose) city)])) + (apply + (map visible-roofs-row m))) + + +(define (best-view city) + (define views (list (list 'N (visible-roofs city 'N)) + (list 'S (visible-roofs city 'S)) + (list 'E (visible-roofs city 'E)) + (list 'W (visible-roofs city 'W)))) + (define (inner m v) (if (< (cadr m) (cadr v)) v m)) + (define sol (foldl inner (list 'None 0) views)) + (cons (car sol) (cadr sol))) +``` +::: + + + +## Haskell + +In Haskell, write a function `bestView city` that outputs the direction with the most roofs visible, +along with the number of roofs visible from that direction. The direction should be one of four +characters: `'N'`, `'S'`, `'E'`, or `'W'`. The result should be a pair in the format `(direction, +number)`. + +```haskell +city = [[3, 3, 3], + [1, 2, 3], + [1, 2, 3]] + +-- 'N' has 3 roofs, 'S' has 5, 'E' has 3, and 'W' is the best with 7 +bestView city -- ('W', 7) +``` + +Your file should be called `Task4.hs` and should export the `bestView` function. +```haskell +module Task4 (bestView) where + +bestView :: [[Int]] -> (Char, Int) +bestView city = ... -- Implement me! +``` + +::: details Solution +```haskell + module Task4 (bestView) where + +import Data.List + +roofs xss = sum $ inner <$> xss + where + inner xs = length (group $ scanl1 max xs) + +morph 'N' = transpose +morph 'S' = fmap reverse . transpose +morph 'E' = fmap reverse +morph _ = id + +bestView :: [[Int]] -> (Char, Int) +bestView city = + let dirs = "NSEW" + views = roofs . (`morph` city) <$> dirs + opts = zip dirs views + in last $ sortOn snd opts +``` +::: diff --git a/exams/photo-skyscraper/main_en.pdf b/exams/photo-skyscraper/main_en.pdf new file mode 100644 index 0000000..6820617 Binary files /dev/null and b/exams/photo-skyscraper/main_en.pdf differ diff --git a/exams/photo-skyscraper/task3.reference.rkt b/exams/photo-skyscraper/task3.reference.rkt new file mode 100644 index 0000000..2ead80c --- /dev/null +++ b/exams/photo-skyscraper/task3.reference.rkt @@ -0,0 +1,32 @@ +#lang racket +(provide best-view) + +(define (visible-roofs-row row [height 0] [n 0]) + (if (empty? row) + n + (if (< height (car row)) + (visible-roofs-row (cdr row) (car row) (+ n 1)) + (visible-roofs-row (cdr row) height n)))) + +(define (transpose mat) (apply map list mat)) + + +(define (visible-roofs city dir) + (define m + (match dir + ['W city] + ['E (map reverse city)] + ['N (transpose city)] + ['S ((compose (curry map reverse) transpose) city)])) + (apply + (map visible-roofs-row m))) + + +(define (best-view city) + (define views (list (list 'N (visible-roofs city 'N)) + (list 'S (visible-roofs city 'S)) + (list 'E (visible-roofs city 'E)) + (list 'W (visible-roofs city 'W)))) + (define (inner m v) (if (< (cadr m) (cadr v)) v m)) + (define sol (foldl inner (list 'None 0) views)) + (cons (car sol) (cadr sol))) + diff --git a/exams/photo-skyscraper/task4.reference.hs b/exams/photo-skyscraper/task4.reference.hs new file mode 100644 index 0000000..2a948e2 --- /dev/null +++ b/exams/photo-skyscraper/task4.reference.hs @@ -0,0 +1,19 @@ +module Task4 (bestView) where + +import Data.List + +roofs xss = sum $ inner <$> xss + where + inner xs = length (group $ scanl1 max xs) + +morph 'N' = transpose +morph 'S' = fmap reverse . transpose +morph 'E' = fmap reverse +morph _ = id + +bestView :: [[Int]] -> (Char, Int) +bestView city = + let dirs = "NSEW" + views = roofs . (`morph` city) <$> dirs + opts = zip dirs views + in last $ sortOn snd opts diff --git a/img/building-trees-sequence.svg b/img/building-trees-sequence.svg new file mode 100644 index 0000000..ed58f8e --- /dev/null +++ b/img/building-trees-sequence.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/finite-automata-dfa.svg b/img/finite-automata-dfa.svg new file mode 100644 index 0000000..67bde60 --- /dev/null +++ b/img/finite-automata-dfa.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/least-common-ancestor-tree.svg b/img/least-common-ancestor-tree.svg new file mode 100644 index 0000000..ef993ce --- /dev/null +++ b/img/least-common-ancestor-tree.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +