diff --git a/slides/sources/common/beamer.template b/slides/sources/common/beamer.template index 710cd1304d2245fcdbc073e57485cd14aa95f2d0..9d13111c2089612bbb0de27d0d5acd6ebc7effe4 100644 --- a/slides/sources/common/beamer.template +++ b/slides/sources/common/beamer.template @@ -131,6 +131,7 @@ $endif$ \renewcommand{\example}{\mbox{\boldred{Example}}} \newcommand{\question}{\mbox{\boldred{Question}}} +\newcommand{\answer}{\mbox{\boldred{Answer}}} \renewcommand{\note}{\mbox{\red{Note:}}} % commas and semicolons diff --git a/slides/sources/progfun1-1-7.md b/slides/sources/progfun1-1-7.md index 0476effd36c73077dd4f73b58c4fb8c026b43c6f..abe6e53b09d37f80603b5604de46099372786634 100644 --- a/slides/sources/progfun1-1-7.md +++ b/slides/sources/progfun1-1-7.md @@ -120,6 +120,8 @@ In Scala, only directly recursive calls to the current function are optimized. One can require that a function is tail-recursive using a `@tailrec` annotation: + import scala.annotation.tailrec + @tailrec def gcd(a: Int, b: Int): Int = ... diff --git a/slides/sources/progfun1-2-2.md b/slides/sources/progfun1-2-2.md index deaecf2427ea27b95202cb4379ee7d7b291d642c..bc8c457d6f24ef24d56924bc340d6f2c3290731f 100644 --- a/slides/sources/progfun1-2-2.md +++ b/slides/sources/progfun1-2-2.md @@ -10,7 +10,7 @@ Look again at the summation functions: def sumCubes(a: Int, b: Int) = sum(x => x * x * x, a, b) def sumFactorials(a: Int, b: Int) = sum(fact, a, b) -\question +\red{Q:} Note that `a` and `b` get passed unchanged from `sumInts` and `sumCubes` into `sum`. diff --git a/slides/sources/progfun1-4-1.md b/slides/sources/progfun1-4-1.md index 5d43c24cb6049999ebb2a783a723b70f32788ab5..77cd10af293ab69f87313f7314e6d253736969cf 100644 --- a/slides/sources/progfun1-4-1.md +++ b/slides/sources/progfun1-4-1.md @@ -123,7 +123,7 @@ Here's a formulation of the `eval` method using type tests and casts: else if e.isInstanceOf[Sum] then eval(e.asInstanceOf[Sum].leftOp) + eval(e.asInstanceOf[Sum].rightOp) - else throw new Error("Unknown expression " + e) + else throw Error("Unknown expression " + e) Assessment of this solution: -> diff --git a/slides/sources/progfun1-4-6.md b/slides/sources/progfun1-4-6.md index 94c943c885268ffadd66f68365dcc515f0d92765..ed80394919185538be8be8d2c42ee9d0b93d73db 100644 --- a/slides/sources/progfun1-4-6.md +++ b/slides/sources/progfun1-4-6.md @@ -200,7 +200,7 @@ Lower Bounds But `prepend` is a natural method to have on immutable lists! -\question: How can we make it variance-correct? +\red{Q}: How can we make it variance-correct? \medskip -> diff --git a/slides/sources/progfun1-5-0.md b/slides/sources/progfun1-5-0.md deleted file mode 100644 index cfbae553b31dd3b870ad3393f18865c06812b612..0000000000000000000000000000000000000000 --- a/slides/sources/progfun1-5-0.md +++ /dev/null @@ -1,189 +0,0 @@ -% Lists -% -% -Lists -===== - -The list is a fundamental data structure in functional programming. - -A list having $\btt x_1, ..., x_n$ -as elements is written `List(`$\btt x_1, ..., x_n$`)` - -\example - - val fruit = List("apples", "oranges", "pears") - val nums = List(1, 2, 3, 4) - val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) - val empty = List() - -There are two important differences between lists and arrays. - - - Lists are immutable --- the elements of a list cannot be changed. - - Lists are recursive, while arrays are flat. - -Lists -===== - - val fruit = List("apples", "oranges", "pears") - val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) - -The List Type -============= - -Like arrays, lists are \red{\em homogeneous}: the elements of a -list must all have the same type. - -The type of a list with elements of type `T` is written `scala.List[T]` or shorter just `List[T]` - -\example - - val fruit: List[String] = List("apples", "oranges", "pears") - val nums : List[Int] = List(1, 2, 3, 4) - val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) - val empty: List[Nothing] = List() - -Constructors of Lists -===================== - -All lists are constructed from: - - - the empty list `Nil`, and - - the construction operation `::` (pronounced \emph{cons}): \newline -`x :: xs` gives a new list with the first element `x`, followed by the elements of `xs`. - -For example: - - fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) - nums = 1 :: (2 :: (3 :: (4 :: Nil))) - empty = Nil - - -Right Associativity -=================== - -Convention: Operators ending in "`:`" associate to the right. - -\gap `A :: B :: C` is interpreted as `A :: (B :: C)`. - -We can thus omit the parentheses in the definition above. - -\example - - val nums = 1 :: 2 :: 3 :: 4 :: Nil - -Operators ending in "`:`" are also different in the they are seen as method calls of the _right-hand_ operand. - -So the expression above is equivalent to - - Nil.::(4).::(3).::(2).::(1) - -Operations on Lists -=================== - -All operations on lists can be expressed in terms of the following -three operations: - -\begin{tabular}{ll} -\verb`head` & the first element of the list \\ -\verb`tail` & the list composed of all the elements except the first. \\ -\verb`isEmpty` & `true` if the list is empty, `false` otherwise. -\end{tabular} - -These operations are defined as methods of objects of type list. -For example: - - fruit.head == "apples" - fruit.tail.head == "oranges" - diag3.head == List(1, 0, 0) - empty.head == throw new NoSuchElementException("head of empty list") - -List Patterns -============= - -It is also possible to decompose lists with pattern matching. - -\begin{tabular}{lp{9cm}} - \verb`Nil` & The \verb`Nil` constant \\ - \verb`p :: ps` & A pattern that matches a list with a \verb`head` matching \verb`p` and a - \verb`tail` matching \verb`ps`. \\ - \verb`List(p1, ..., pn)` & same as \verb`p1 :: ... :: pn :: Nil` -\end{tabular} - -\example - -\begin{tabular}{lp{9cm}} - \verb`1 :: 2 :: xs` & Lists of that start with \verb`1` and then \verb`2` -\\ \verb`x :: Nil` & Lists of length 1 -\\ \verb`List(x)` & Same as \verb`x :: Nil` -\\ \verb`List()` & The empty list, same as \verb`Nil` -\\ \verb`List(2 :: xs)` & A list that contains as only element another list - that starts with \verb`2`. -\end{tabular} - -Exercise -======== - -Consider the pattern `x :: y :: List(xs, ys) :: zs`. - -What is the condition that describes most accurately the length `L` -of the lists it matches? - - O L == 3 - O L == 4 - O L == 5 - O L >= 3 - O L >= 4 - O L >= 5 --> -\quiz - -Sorting Lists -============= - -Suppose we want to sort a list of numbers in ascending order: - - - One way to sort the list `List(7, 3, 9, 2)` is to sort the - tail `List(3, 9, 2)` to obtain `List(2, 3, 9)`. - - The next step is to insert the head `7` in the right place - to obtain the result `List(2, 3, 7, 9)`. - -This idea describes \red{Insertion Sort} : - - def isort(xs: List[Int]): List[Int] = xs match - case List() => List() - case y :: ys => insert(y, isort(ys)) - -Exercise -======== - -Complete the definition insertion sort by filling in the `???`s in the definition below: - - def insert(x: Int, xs: List[Int]): List[Int] = xs match - case List() => ??? - case y :: ys => ??? - -What is the worst-case complexity of insertion sort relative to the length of the input list `N`? - - O the sort takes constant time - O proportional to N - O proportional to N log(N) - O proportional to N * N - -\quiz - -Exercise -======== - -Complete the definition insertion sort by filling in the `???`s in the definition below: - - def insert(x: Int, xs: List[Int]): List[Int] = xs match - case List() => - case y :: ys => - -What is the worst-case complexity of insertion sort relative to the length of the input list `N`? - - O the sort takes constant time - O proportional to N - O proportional to N * log(N) - O proportional to N * N - diff --git a/slides/sources/progfun1-5-1.md b/slides/sources/progfun1-5-1.md index 7aa9b807ab6dd93ca08ac02b043481e89a665a3a..acdf3726dfca0950c20a45dad6db5fb9176a3981 100644 --- a/slides/sources/progfun1-5-1.md +++ b/slides/sources/progfun1-5-1.md @@ -1,220 +1,210 @@ -% More Functions on Lists +% Lists % % +Lists +===== -List Methods (1) -================ +The list is a fundamental data structure in functional programming. -\red{Sublists and element access:} +A list having $\btt x_1, ..., x_n$ +as elements is written `List(`$\btt x_1, ..., x_n$`)` -\begin{tabular}{lp{8cm}} - \verb` xs.length ` & The number of elements of \verb`xs`. -\\ \verb` xs.last` & The list's last element, exception if \verb`xs` is empty. -\\ \verb` xs.init` & A list consisting of all elements of \verb`xs` except the last one, - exception if \verb`xs` is empty. -\\ \verb` xs take n` & A list consisting of the first \verb`n` elements of \verb`xs`, or \verb`xs` itself - if it is shorter than \verb`n`. -\\ \verb` xs drop n` & The rest of the collection after taking \verb`n` elements. -\\ \verb` xs(n)` & (or, written out, \verb`xs apply n`). The element of \verb`xs` at index \verb`n`. +\example -\end{tabular} - -List Methods (2) -================ - -\red{Creating new lists:} - -\begin{tabular}{lp{8cm}} - \verb` xs ++ ys` & The list consisting of all elements of \verb`xs` - followed by all elements of \verb`ys`. -\\ \verb` xs.reverse` & The list containing the elements of \verb`xs` in reversed order. -\\ \verb` xs updated (n, x)` & The list containing the same elements as \verb`xs`, except at index - \verb`n` where it contains \verb`x`. -\end{tabular} + val fruit = List("apples", "oranges", "pears") + val nums = List(1, 2, 3, 4) + val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) + val empty = List() -\red{Finding elements:} +There are two important differences between lists and arrays. -\begin{tabular}{lp{8cm}} - \verb` xs indexOf x `& The index of the first element in \verb`xs` equal to \verb`x`, or \verb`-1` if \verb`x` - does not appear in \verb`xs`. -\\ \verb` xs contains x ` & same as \verb`xs indexOf x >= 0` -\end{tabular} - -Implementation of `last` -======================== - -The complexity of `head` is (small) constant time. + - Lists are immutable --- the elements of a list cannot be changed. + - Lists are recursive, while arrays are flat. -What is the complexity of `last`? +Lists +===== -To find out, let's write a possible implementation of `last` as a stand-alone function. - - def last[T](xs: List[T]): T = xs match - case List() => throw new Error("last of empty list") - case List(x) => - case y :: ys => + val fruit = List("apples", "oranges", "pears") + val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) -Implementation of `last` -======================== +\vspace{2cm} +$$ +\mbox{(Drawing on Blackboard)} +$$ -The complexity of `head` is (small) constant time. +The List Type +============= -What is the complexity of `last`? +Like arrays, lists are \red{\em homogeneous}: the elements of a +list must all have the same type. -To find out, let's write a possible implementation of `last` as a stand-alone function. +The type of a list with elements of type `T` is written `scala.List[T]` or shorter just `List[T]` - def last[T](xs: List[T]): T = xs match - case List() => throw new Error("last of empty list") - case List(x) => x - case y :: ys => - -Implementation of `last` -======================== - -The complexity of `head` is (small) constant time. - -What is the complexity of `last`? - -To find out, let's write a possible implementation of `last` as a stand-alone function. - - def last[T](xs: List[T]): T = xs match - case List() => throw new Error("last of empty list") - case List(x) => x - case y :: ys => last(ys) --> -So, `last` takes steps proportional to the length of the list `xs`. - -Exercise -======== +\example -Implement `init` as an external function, analogous to `last`. + val fruit: List[String] = List("apples", "oranges", "pears") + val nums : List[Int] = List(1, 2, 3, 4) + val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) + val empty: List[Nothing] = List() - def init[T](xs: List[T]): List[T] = xs match - case List() => throw new Error("init of empty list") - case List(x) => ??? - case y :: ys => ??? +Constructors of Lists +===================== -\quiz +All lists are constructed from: -Exercise -======== + - the empty list `Nil`, and + - the construction operation `::` (pronounced \emph{cons}): \newline +`x :: xs` gives a new list with the first element `x`, followed by the elements of `xs`. -Implement `init` as an external function, analogous to `last`. +For example: - def init[T](xs: List[T]): List[T] = xs match - case List() => throw new Error("init of empty list") - case List(x) => - case y :: ys => + fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) + nums = 1 :: (2 :: (3 :: (4 :: Nil))) + empty = Nil -\quiz -Implementation of Concatenation -=============================== +Right Associativity +=================== -How can concatenation be implemented? +Convention: Operators ending in "`:`" associate to the right. -Let's try by writing a stand-alone function: +\gap `A :: B :: C` is interpreted as `A :: (B :: C)`. - def concat[T](xs: List[T], ys: List[T]) = +We can thus omit the parentheses in the definition above. -Implementation of Concatenation -=============================== +\example -How can concatenation be implemented? + val nums = 1 :: 2 :: 3 :: 4 :: Nil -Let's try by writing a stand-alone function: +Operators ending in "`:`" are also different in the they are seen as method calls of the _right-hand_ operand. - def concat[T](xs: List[T], ys: List[T]) = xs match - case List() => - case z :: zs => +So the expression above is equivalent to -Implementation of Concatenation -=============================== + Nil.::(4).::(3).::(2).::(1) -How can concatenation be implemented? +Operations on Lists +=================== -Let's try by writing a stand-alone function: +All operations on lists can be expressed in terms of the following +three operations: - def concat[T](xs: List[T], ys: List[T]) = xs match - case List() => ys - case z :: zs => +\begin{tabular}{ll} +\verb`head` & the first element of the list \\ +\verb`tail` & the list composed of all the elements except the first. \\ +\verb`isEmpty` & `true` if the list is empty, `false` otherwise. +\end{tabular} -Implementation of Concatenation -=============================== +These operations are defined as methods of objects of type list. +For example: -How can concatenation be implemented? + fruit.head == "apples" + fruit.tail.head == "oranges" + diag3.head == List(1, 0, 0) + empty.head == throw NoSuchElementException("head of empty list") -Let's try by writing a stand-alone function: +List Patterns +============= - def concat[T](xs: List[T], ys: List[T]) = xs match - case List() => ys - case z :: zs => z :: concat(zs, ys) +It is also possible to decompose lists with pattern matching. --> -What is the complexity of `concat`? +\begin{tabular}{lp{9cm}} + \verb`Nil` & The \verb`Nil` constant \\ + \verb`p :: ps` & A pattern that matches a list with a \verb`head` matching \verb`p` and a + \verb`tail` matching \verb`ps`. \\ + \verb`List(p1, ..., pn)` & same as \verb`p1 :: ... :: pn :: Nil` +\end{tabular} +\example -Implementation of `reverse` -=========================== +\begin{tabular}{lp{9cm}} + \verb`1 :: 2 :: xs` & Lists of that start with \verb`1` and then \verb`2` +\\ \verb`x :: Nil` & Lists of length 1 +\\ \verb`List(x)` & Same as \verb`x :: Nil` +\\ \verb`List()` & The empty list, same as \verb`Nil` +\\ \verb`List(2 :: xs)` & A list that contains as only element another list + that starts with \verb`2`. +\end{tabular} -How can reverse be implemented? +Exercise +======== -Let's try by writing a stand-alone function: +Consider the pattern `x :: y :: List(xs, ys) :: zs`. - def reverse[T](xs: List[T]): List[T] = xs match - case List() => - case y :: ys => +What is the condition that describes most accurately the length `L` +of the lists it matches? -Implementation of `reverse` -=========================== + O L == 3 + O L == 4 + O L == 5 + O L >= 3 + O L >= 4 + O L >= 5 -How can reverse be implemented? +\quiz -Let's try by writing a stand-alone function: +Exercise +======== - def reverse[T](xs: List[T]): List[T] = xs match - case List() => List() - case y :: ys => +Consider the pattern `x :: y :: List(xs, ys) :: zs`. +What is the condition that describes most accurately the length `L` +of the lists it matches? + O L == 3 + O L == 4 + O L == 5 + X L >= 3 + O L >= 4 + O L >= 5 -Implementation of `reverse` -=========================== +Sorting Lists +============= -How can reverse be implemented? +Suppose we want to sort a list of numbers in ascending order: -Let's try by writing a stand-alone function: + - One way to sort the list `List(7, 3, 9, 2)` is to sort the + tail `List(3, 9, 2)` to obtain `List(2, 3, 9)`. + - The next step is to insert the head `7` in the right place + to obtain the result `List(2, 3, 7, 9)`. - def reverse[T](xs: List[T]): List[T] = xs match - case List() => List() - case y :: ys => reverse(ys) ++ List(y) --> -What is the complexity of `reverse`? +This idea describes \red{Insertion Sort} : -\red{Can we do better?} (to be solved later). + def isort(xs: List[Int]): List[Int] = xs match + case List() => List() + case y :: ys => insert(y, isort(ys)) Exercise ======== -Remove the `n`'th element of a list `xs`. If `n` is out of bounds, return `xs` itself. +Complete the definition insertion sort by filling in the `???`s in the definition below: - def removeAt[T](n: Int, xs: List[T]) = ??? + def insert(x: Int, xs: List[Int]): List[Int] = xs match + case List() => ??? + case y :: ys => ??? -Usage example: +What is the worst-case complexity of insertion sort relative to the length of the input list `N`? -\begin{worksheet} - \verb`removeAt(1, List('a', 'b', 'c', 'd'))` \wsf List(a, c, d) -\end{worksheet} + O the sort takes constant time + O proportional to N + O proportional to N log(N) + O proportional to N * N -Exercise (Harder, Optional) -=========================== +\quiz -Flatten a list structure: +Exercise +======== - def flatten(xs: List[Any]): List[Any] = ??? +Complete the definition insertion sort by filling in the `???`s in the definition below: - flatten(List(List(1, 1), 2, List(3, List(5, 8)))) - > res0: List[Any] = List(1, 1, 2, 3, 5, 8) + def insert(x: Int, xs: List[Int]): List[Int] = xs match + case List() => List(x) + case y :: ys => + if x < y then x :: xs else y :: insert(x, ys) +What is the worst-case complexity of insertion sort relative to the length of the input list `N`? + O the sort takes constant time + X proportional to N + O proportional to N * log(N) + O proportional to N * N diff --git a/slides/sources/progfun1-5-2.md b/slides/sources/progfun1-5-2.md index fd3f940ae5ba0a159b6079f6508521c2d25a871d..a4269c6d18b8b24ede1cbae903b39fd4683bd455 100644 --- a/slides/sources/progfun1-5-2.md +++ b/slides/sources/progfun1-5-2.md @@ -1,97 +1,253 @@ -% Pairs and Tuples +% More Functions on Lists % % -Sorting Lists Faster -==================== -As a non-trivial example, let's design a function to sort lists -that is more efficient than insertion sort. +List Methods (1) +================ -A good algorithm for this is \red{merge sort}. The idea is as -follows: +\red{Sublists and element access:} -If the list consists of zero or one elements, it is already sorted. +\begin{tabular}{lp{8cm}} + \verb` xs.length ` & The number of elements of \verb`xs`. +\\ \verb` xs.last` & The list's last element, exception if \verb`xs` is empty. +\\ \verb` xs.init` & A list consisting of all elements of \verb`xs` except the last one, + exception if \verb`xs` is empty. +\\ \verb` xs.take(n)` & A list consisting of the first \verb`n` elements of \verb`xs`, or \verb`xs` itself + if it is shorter than \verb`n`. +\\ \verb` xs.drop(n)` & The rest of the collection after taking \verb`n` elements. +\\ \verb` xs(n)` & (or, written out, \verb`xs.apply(n)`). The element of \verb`xs` at index \verb`n`. -Otherwise, +\end{tabular} - - Separate the list into two sub-lists, each containing around half of -the elements of the original list. - - Sort the two sub-lists. - - Merge the two sorted sub-lists into a single sorted list. +List Methods (2) +================ -First MergeSort Implementation -============================== +\red{Creating new lists:} -Here is the implementation of that algorithm in Scala: +\begin{tabular}{lp{8cm}} + \verb` xs ++ ys` & The list consisting of all elements of \verb`xs` + followed by all elements of \verb`ys`. +\\ \verb` xs.reverse` & The list containing the elements of \verb`xs` in reversed order. +\\ \verb` xs.updated(n, x)`& The list containing the same elements as \verb`xs`, except at index + \verb`n` where it contains \verb`x`. +\end{tabular} - def msort(xs: List[Int]): List[Int] = - val n = xs.length / 2 - if n == 0 then xs - else - def merge(xs: List[Int], ys: List[Int]) = ??? - val (fst, snd) = xs.splitAt(n) - merge(msort(fst), msort(snd)) +\red{Finding elements:} -Definition of Merge -=================== +\begin{tabular}{lp{8cm}} + \verb` xs.indexOf(x) `& The index of the first element in \verb`xs` equal to \verb`x`, or \verb`-1` if \verb`x` + does not appear in \verb`xs`. +\\ \verb` xs.contains(x) `& same as \verb`xs.indexOf(x) >= 0` +\end{tabular} -Here is a definition of the `merge` function: +Implementation of `last` +======================== - def merge(xs: List[Int], ys: List[Int]) = - xs match - case Nil => - ys - case x :: xs1 - ys match - case Nil => - xs - case y :: ys1 => - if x < y then x :: merge(xs1, ys) - else y :: merge(xs, ys1) - end merge +The complexity of `head` is (small) constant time. -The SplitAt Function -==================== +What is the complexity of `last`? -The `splitAt` function on lists returns two sublists +To find out, let's write a possible implementation of `last` as a stand-alone function. - - the elements up the the given index - - the elements from that index + def last[T](xs: List[T]): T = xs match + case List() => throw Error("last of empty list") + case List(x) => + case y :: ys => -The lists are returned in a \red{pair}. +Implementation of `last` +======================== +The complexity of `head` is (small) constant time. -Detour: Pair and Tuples -======================= +What is the complexity of `last`? -The pair consisting of `x` and `y` is written `(x, y)` in Scala. +To find out, let's write a possible implementation of `last` as a stand-alone function. -\example + def last[T](xs: List[T]): T = xs match + case List() => throw Error("last of empty list") + case List(x) => x + case y :: ys => -\begin{worksheet} -\verb` val pair = ("answer", 42)` \wsf pair : (String, Int) = (answer,42) -\end{worksheet} +Implementation of `last` +======================== -The type of `pair` above is `(String, Int)`. +The complexity of `head` is (small) constant time. -Pairs can also be used as patterns: +What is the complexity of `last`? -\begin{worksheet} -\verb` val (label, value) = pair` \wsf label : String = answer \\ - \wsn value : Int = 42 -\end{worksheet} +To find out, let's write a possible implementation of `last` as a stand-alone function. + + def last[T](xs: List[T]): T = xs match + case List() => throw Error("last of empty list") + case List(x) => x + case y :: ys => last(ys) +-> +So, `last` takes steps proportional to the length of the list `xs`. + +Exercise +======== + +Implement `init` as an external function, analogous to `last`. + + def init[T](xs: List[T]): List[T] = xs match + case List() => throw Error("init of empty list") + case List(x) => ??? + case y :: ys => ??? + +\quiz + +Exercise +======== + +Implement `init` as an external function, analogous to `last`. + + def init[T](xs: List[T]): List[T] = xs match + case List() => throw Error("init of empty list") + case List(x) => + case y :: ys => + +\quiz + +Exercise +======== + +Implement `init` as an external function, analogous to `last`. + + def init[T](xs: List[T]): List[T] = xs match + case List() => throw Error("init of empty list") + case List(x) => List() + case y :: ys => -This works analogously for tuples with more than two elements. +\quiz Exercise ======== -The `merge` function as given uses a nested pattern match. +Implement `init` as an external function, analogous to `last`. + + def init[T](xs: List[T]): List[T] = xs match + case List() => throw Error("init of empty list") + case List(x) => List() + case y :: ys => y :: init(ys) + +\quiz + +Implementation of Concatenation +=============================== + +How can concatenation be implemented? + +Let's try by writing a stand-alone function: + + def concat[T](xs: List[T], ys: List[T]) = + +Implementation of Concatenation +=============================== + +How can concatenation be implemented? + +Let's try by writing a stand-alone function: + + def concat[T](xs: List[T], ys: List[T]) = xs match + case List() => + case z :: zs => + +Implementation of Concatenation +=============================== + +How can concatenation be implemented? + +Let's try by writing a stand-alone function: + + def concat[T](xs: List[T], ys: List[T]) = xs match + case List() => ys + case z :: zs => + +Implementation of Concatenation +=============================== + +How can concatenation be implemented? + +Let's try by writing a stand-alone function: + + def concat[T](xs: List[T], ys: List[T]) = xs match + case List() => ys + case z :: zs => z :: concat(zs, ys) + +-> + +What is the complexity of `concat`? + +-> + +Answer: `O(xs.length)` + +Implementation of `reverse` +=========================== + +How can reverse be implemented? + +Let's try by writing a stand-alone function: + + def reverse[T](xs: List[T]): List[T] = xs match + case List() => + case y :: ys => + +Implementation of `reverse` +=========================== + +How can reverse be implemented? + +Let's try by writing a stand-alone function: + + def reverse[T](xs: List[T]): List[T] = xs match + case List() => List() + case y :: ys => + + +Implementation of `reverse` +=========================== + +How can reverse be implemented? + +Let's try by writing a stand-alone function: + + def reverse[T](xs: List[T]): List[T] = xs match + case List() => List() + case y :: ys => reverse(ys) ++ List(y) + +-> + +What is the complexity of `reverse`? + +-> + +Answer: `O(xs.length * xs.length)` + +\red{Can we do better?} (to be solved later). + +Exercise +======== + +Remove the `n`'th element of a list `xs`. If `n` is out of bounds, return `xs` itself. + + def removeAt[T](n: Int, xs: List[T]) = ??? + +Usage example: + +\begin{worksheet} + \verb`removeAt(1, List('a', 'b', 'c', 'd'))` \wsf List(a, c, d) +\end{worksheet} + +Exercise (Harder, Optional) +=========================== + +Flatten a list structure: + + def flatten(xs: List[Any]): List[Any] = ??? + + flatten(List(List(1, 1), 2, List(3, List(5, 8)))) + > res0: List[Any] = List(1, 1, 2, 3, 5, 8) -This does not reflect the inherent symmetry of the merge algorithm. -Rewrite `merge` using a pattern matching over pairs. - def merge(xs: List[Int], ys: List[Int]): List[Int] = - (xs, ys) match - ??? diff --git a/slides/sources/progfun1-5-3.md b/slides/sources/progfun1-5-3.md index cc8a125bba512f3e82b45cbcacebbd262ceb7953..23156bcd155ede968d92752ea14b33ce7d64c528 100644 --- a/slides/sources/progfun1-5-3.md +++ b/slides/sources/progfun1-5-3.md @@ -1,150 +1,168 @@ -% Implicit Parameters +% Tuples and Generic Methods % % +Sorting Lists Faster +==================== -Making Sort more General -======================== - -Problem: How to parameterize `msort` so that it can also be used for -lists with elements other than `Int`? - - def msort[T](xs: List[T]): List[T] = ... - -does not work, because the comparison `<` in `merge` is not defined -for arbitrary types `T`. - -\red{Idea:} Parameterize `merge` with the necessary comparison function. - -Parameterization of Sort -======================== - -The most flexible design is to make the function \verb@sort@ -polymorphic and to pass the comparison operation as an additional -parameter: - - def msort[T](xs: List[T])(lt: (T, T) => Boolean) = - ... - merge(msort(fst)(lt), msort(snd)(lt)) - -Merge then needs to be adapted as follows: - - def merge(xs: List[T], ys: List[T]) = (xs, ys) match - ... - case (x :: xs1, y :: ys1) => - if lt(x, y) then ... - else ... - -Calling Parameterized Sort -========================== - -We can now call `msort` as follows: - - val xs = List(-5, 6, 3, 2, 7) - val fruits = List("apple", "pear", "orange", "pineapple") +As a non-trivial example, let's design a function to sort lists +that is more efficient than insertion sort. - msort(xs)((x: Int, y: Int) => x < y) - msort(fruits)((x: String, y: String) => x.compareTo(y) < 0) +A good algorithm for this is \red{merge sort}. The idea is as +follows: -Or, since parameter types can be inferred from the call `msort(xs)`: +If the list consists of zero or one elements, it is already sorted. - msort(xs)((x, y) => x < y) +Otherwise, + - Separate the list into two sub-lists, each containing around half of +the elements of the original list. + - Sort the two sub-lists. + - Merge the two sorted sub-lists into a single sorted list. -Parametrization with Ordering -============================= +First MergeSort Implementation +============================== -There is already a class in the standard library that represents orderings. +Here is the implementation of that algorithm in Scala: - scala.math.Ordering[T] + def msort(xs: List[Int]): List[Int] = + val n = xs.length / 2 + if n == 0 then xs + else + def merge(xs: List[Int], ys: List[Int]) = ??? + val (fst, snd) = xs.splitAt(n) + merge(msort(fst), msort(snd)) -provides ways to compare elements of type `T`. So instead of -parameterizing with the `lt` operation directly, we could parameterize -with `Ordering` instead: +The SplitAt Function +==================== - def msort[T](xs: List[T])(ord: Ordering[T]) = +The `splitAt` function on lists returns two sublists - def merge(xs: List[T], ys: List[T]) = - ... if ord.lt(x, y) then ... + - the elements up the the given index + - the elements from that index - ... merge(msort(fst)(ord), msort(snd)(ord)) ... +The lists are returned in a \red{pair}. -Ordering Instances: -=================== -Calling the new `msort` can be done like this: +Detour: Pair and Tuples +======================= - import math.Ordering +The pair consisting of `x` and `y` is written `(x, y)` in Scala. - msort(nums)(Ordering.Int) - msort(fruits)(Ordering.String) +\example -This makes use of the values `Int` and `String` defined in the -`scala.math.Ordering` object, which produce the right orderings on -integers and strings. +\begin{worksheet} +\verb` val pair = ("answer", 42)` \wsf pair : (String, Int) = (answer,42) +\end{worksheet} -Aside: Implicit Parameters -========================== +The type of `pair` above is `(String, Int)`. -\red{Problem:} Passing around `lt` or `ord` values is cumbersome. +Pairs can also be used as patterns: -We can avoid this by making `ord` an implicit parameter. +\begin{worksheet} +\verb` val (label, value) = pair` \wsf label : String = answer \\ + \wsn value : Int = 42 +\end{worksheet} - def msort[T](xs: List[T])(implicit ord: Ordering[T]) = +This works analogously for tuples with more than two elements. - def merge(xs: List[T], ys: List[T]) = - ... if ord.lt(x, y) then ... +Translation of Tuples +===================== - ... merge(msort(fst), msort(snd)) ... +For small `(*)` $n$, the tuple type $\btt (T_1, ..., T_n)$ is an abbreviation of the +parameterized type +$$\btt + scala.Tuple{\it n}[T_1, ..., T_n] +$$ +A tuple expression $\btt (e_1, ..., e_n)$ +is equivalent to the function application +$$\btt + scala.Tuple{\it n}(e_1, ..., e_n) +$$ +A tuple pattern $\btt (p_1, ..., p_n)$ +is equivalent to the constructor pattern +$$\btt + scala.Tuple{\it n}(p_1, ..., p_n) +$$ +`(*)` Currently, "small" = up to 22. There's also a TupleXXL class that handles Tuples larger than that limit. -Then calls to `msort` can avoid the ordering parameters: +The Tuple class +=============== - msort(nums) - msort(fruits) +Here, all `Tuple`\mbox{\it n} classes are modeled after the following pattern: -The compiler will figure out the right implicit to pass based on the -demanded type. + case class Tuple2[T1, T2](_1: +T1, _2: +T2) { + override def toString = "(" + _1 + "," + _2 +")" + } -Rules for Implicit Parameters -============================= +The fields of a tuple can be accessed with names `_1`, `_2`, ... -Say, a function takes an implicit parameter of type `T`. +So instead of the pattern binding -The compiler will search an implicit definition that + val (label, value) = pair - - is marked `implicit` - - has a type compatible with `T` - - is visible at the point of the function call, or is defined - in a companion object associated with `T`. +one could also have written: -If there is a single (most specific) definition, it will be taken as -actual argument for the implicit parameter. + val label = pair._1 + val value = pair._2 -Otherwise it's an error. +But the pattern matching form is generally preferred. -Exercise: Implicit Parameters -============================= +Definition of Merge +=================== -Consider the following line of the definition of `msort`: +Here is a definition of the `merge` function: - ... merge(msort(fst), msort(snd)) ... + def merge(xs: List[Int], ys: List[Int]) = (xs, ys) match + case (Nil, ys) => ys + case (xs, Nil) => xs + case (x :: xs1, y :: ys1) => + if x < y then x :: merge(xs1, ys) + else y :: merge(xs, ys1) -Which implicit argument is inserted? +Making Sort more General +======================== - O Ordering.Int - O Ordering.String - O the "ord" parameter of "msort" +Problem: How to parameterize `msort` so that it can also be used for +lists with elements other than `Int`? + def msort[T](xs: List[T]): List[T] = ??? +does not work, because the comparison `<` in `merge` is not defined +for arbitrary types `T`. +\red{Idea:} Parameterize `merge` with the necessary comparison function. +Parameterization of Sort +======================== +The most flexible design is to make the function \verb@sort@ +polymorphic and to pass the comparison operation as an additional +parameter: + def msort[T](xs: List[T])(lt: (T, T) => Boolean) = + ... + merge(msort(fst)(lt), msort(snd)(lt)) +Merge then needs to be adapted as follows: + def merge(xs: List[T], ys: List[T]) = (xs, ys) match + ... + case (x :: xs1, y :: ys1) => + if lt(x, y) then ... + else ... +Calling Parameterized Sort +========================== +We can now call `msort` as follows: + val xs = List(-5, 6, 3, 2, 7) + val fruits = List("apple", "pear", "orange", "pineapple") + msort(xs)((x: Int, y: Int) => x < y) + msort(fruits)((x: String, y: String) => x.compareTo(y) < 0) +Or, since parameter types can be inferred from the call `msort(xs)`: + msort(xs)((x, y) => x < y) diff --git a/slides/sources/progfun1-5-4.md b/slides/sources/progfun1-5-4.md index 66b09f59680c471497fe9028f972bae112217a44..b910554c803dacd39ca86de51b760845e38fe261 100644 --- a/slides/sources/progfun1-5-4.md +++ b/slides/sources/progfun1-5-4.md @@ -30,15 +30,15 @@ For example, to multiply each element of a list by the same factor, you could wr case Nil => xs case y :: ys => y * factor :: scaleList(ys, factor) - -Map -=== +Mapping +======= This scheme can be generalized to the method `map` of the `List` class. A simple way to define `map` is as follows: - abstract class List[T] { ... + abstract class List[T] + def map[U](f: T => U): List[U] = this match case Nil => this case x :: xs => f(x) :: xs.map(f) @@ -50,7 +50,7 @@ collections, not just lists). Using `map`, `scaleList` can be written more concisely. def scaleList(xs: List[Double], factor: Double) = - xs map (x => x * factor) + xs.map(x => x * factor) Exercise ======== @@ -62,6 +62,7 @@ return the result. Complete the two following equivalent definitions of def squareList(xs: List[Int]): List[Int] = xs match case Nil => ??? + case y :: ys => ??? def squareList(xs: List[Int]): List[Int] = xs.map(???) @@ -75,11 +76,11 @@ return the result. Complete the two following equivalent definitions of def squareList(xs: List[Int]): List[Int] = xs match - case Nil => - case y :: ys => + case Nil => Nil + case y :: ys => y * y :: squareList(ys) def squareList(xs: List[Int]): List[Int] = - xs.map + xs.map(x => x * x) Filtering ========= @@ -97,7 +98,7 @@ Filter This pattern is generalized by the method `filter` of the `List` class: abstract class List[T] - ... + def filter(p: T => Boolean): List[T] = this match case Nil => this case x :: xs => if p(x) then x :: xs.filter(p) else xs.filter(p) @@ -106,7 +107,7 @@ This pattern is generalized by the method `filter` of the `List` class: Using `filter`, `posElems` can be written more concisely. def posElems(xs: List[Int]): List[Int] = - xs filter (x => x > 0) + xs.filter(x => x > 0) Variations of Filter ==================== @@ -115,11 +116,11 @@ Besides filter, there are also the following methods that extract sublists based on a predicate: \begin{tabular}{lp{8cm}} - \verb` xs.filterNot(p)` & Same as \verb`xs filter (x => !p(x))`; The list consisting of those elements of \verb`xs` that do not satisfy the predicate \verb`p`. -\\ \verb` xs.partition(p)` & Same as \verb`(xs filter p, xs filterNot p)`, but computed in a single traversal of the list \verb`xs`. + \verb` xs.filterNot(p)` & Same as \verb`xs.filter(x => !p(x))`; The list consisting of those elements of \verb`xs` that do not satisfy the predicate \verb`p`. +\\ \verb` xs.partition(p)` & Same as \verb`(xs.filter(p), xs.filterNot(p))`, but computed in a single traversal of the list \verb`xs`. \\ \verb` xs.takeWhile(p)` & The longest prefix of list \verb`xs` consisting of elements that all satisfy the predicate \verb`p`. \\ \verb` xs.dropWhile(p)` & The remainder of the list \verb`xs` after any leading elements satisfying \verb`p` have been removed. -\\ \verb` xs.span(p)` & Same as \verb`(xs takeWhile p, xs dropWhile p)` but computed in a single traversal of the list \verb`xs`. +\\ \verb` xs.span(p)` & Same as \verb`(xs.takeWhile(p), xs.dropWhile(p))` but computed in a single traversal of the list \verb`xs`. \end{tabular} Exercise @@ -142,6 +143,25 @@ You can use the following template: Exercise ======== +Write a function `pack` that packs consecutive duplicates of list elements into sublists. For instance, + + pack(List("a", "a", "a", "b", "c", "c", "a")) + +should give + + List(List("a", "a", "a"), List("b"), List("c", "c"), List("a")). + +You can use the following template: + + def pack[T](xs: List[T]): List[List[T]] = xs match + case Nil => Nil + case x :: xs1 => + val (more, rest) = xs1.partition(y => y == x) + (x :: more, pack(rest)) + +Exercise +======== + Using `pack`, write a function `encode` that produces the run-length encoding of a list. @@ -153,3 +173,12 @@ should give List(("a", 3), ("b", 1), ("c", 2), ("a", 1)). +Exercise +======== + +Using `pack`, write a function `encode` that produces the run-length +encoding of a list. + + def encode[T](xs: List[T]): List[(T, Int)] = + pack(xs).map(ys => (ys.head, ys.length)) + diff --git a/slides/sources/progfun1-5-5.md b/slides/sources/progfun1-5-5.md index 0eb95a95bb203dff416989e98e5f19e8b1fcfb55..8c802832d1a3f4d499cf504509a89773d7ff200f 100644 --- a/slides/sources/progfun1-5-5.md +++ b/slides/sources/progfun1-5-5.md @@ -17,6 +17,7 @@ We can implement this with the usual recursive schema: case Nil => 0 case y :: ys => y + sum(ys) + ReduceLeft ========== @@ -25,13 +26,12 @@ This pattern can be abstracted out using the generic method `reduceLeft`: `reduceLeft` inserts a given binary operator between adjacent elements of a list: - List(x1, ..., xn) reduceLeft op = (...(x1 op x2) op ... ) op xn + List(x1, ..., xn).reduceLeft(op) = x1.op(x2). ... .op(xn) -\vspace{2cm} Using `reduceLeft`, we can simplify: - def sum(xs: List[Int]) = (0 :: xs) reduceLeft ((x, y) => x + y) - def product(xs: List[Int]) = (1 :: xs) reduceLeft ((x, y) => x * y) + def sum(xs: List[Int]) = (0 :: xs).reduceLeft((x, y) => x + y) + def product(xs: List[Int]) = (1 :: xs).reduceLeft((x, y) => x * y) A Shorter Way to Write Functions ================================ @@ -46,8 +46,8 @@ The parameters are defined at the next outer pair of parentheses (or the whole e So, `sum` and `product` can also be expressed like this: - def sum(xs: List[Int]) = (0 :: xs) reduceLeft (_ + _) - def product(xs: List[Int]) = (1 :: xs) reduceLeft (_ * _) + def sum(xs: List[Int]) = (0 :: xs).reduceLeft(_ + _) + def product(xs: List[Int]) = (1 :: xs).reduceLeft(_ * _) FoldLeft ======== @@ -56,14 +56,17 @@ The function `reduceLeft` is defined in terms of a more general function, `foldL `foldLeft` is like `reduceLeft` but takes an \red{accumulator}, `z`, as an additional parameter, which is returned when `foldLeft` is called on an empty list. - (List(x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ... ) op xn + List(x1, ..., xn).foldLeft(z)(op) = z.op(x1).op ... .op(xn) -\vspace{1.7cm} +\vspace{1cm} +$$ +\mbox{(Drawing on Blackboard)} +$$ So, `sum` and `product` can also be defined as follows: - def sum(xs: List[Int]) = (xs foldLeft 0) (_ + _) - def product(xs: List[Int]) = (xs foldLeft 1) (_ * _) + def sum(xs: List[Int]) = xs.foldLeft(0)(_ + _) + def product(xs: List[Int]) = xs.foldLeft(1)(_ * _) Implementations of ReduceLeft and FoldLeft ========================================== @@ -73,13 +76,12 @@ Implementations of ReduceLeft and FoldLeft abstract class List[T] def reduceLeft(op: (T, T) => T): T = this match - case Nil => throw new Error("Nil.reduceLeft") - case x :: xs => (xs foldLeft x)(op) + case Nil => throw Error("Nil.reduceLeft") + case x :: xs => xs.foldLeft(x)(op) def foldLeft[U](z: U)(op: (U, T) => U): U = this match case Nil => z - case x :: xs => (xs foldLeft op(z, x))(op) - + case x :: xs => xs.foldLeft(op(z, x))(op) FoldRight and ReduceRight ========================= @@ -89,8 +91,13 @@ Applications of `foldLeft` and `reduceLeft` unfold on trees that lean to the lef They have two dual functions, `foldRight` and `reduceRight`, which produce trees which lean to the right, i.e., - List(x1, ..., x{n-1}, xn) reduceRight op = x1 op ( ... (x{n-1} op xn) ... ) - (List(x1, ..., xn) foldRight acc)(op) = x1 op ( ... (xn op acc) ... ) + List(x1, ..., x{n-1}, xn).reduceRight(op) = x1.op(x2.op( ... x{n-1}.op(xn) ... )) + (List(x1, ..., xn).foldRight(z)(op) = x1.op(x2.op( ... xn.op(z) ...)) + +\vspace{1.5cm} +$$ +\mbox{(Drawing on Blackboard)} +$$ Implementation of FoldRight and ReduceRight =========================================== @@ -98,12 +105,12 @@ Implementation of FoldRight and ReduceRight They are defined as follows def reduceRight(op: (T, T) => T): T = this match - case Nil => throw new Error("Nil.reduceRight") + case Nil => throw Error("Nil.reduceRight") case x :: Nil => x - case x :: xs => op(x, xs.reduceRight(op)) + case x :: xs => op(x, xs.reduceRight(op)) def foldRight[](z: U)(op: (T, U) => U): U = this match - case Nil => z + case Nil => z case x :: xs => op(x, xs.foldRight(z)(op)) Difference between FoldLeft and FoldRight @@ -119,7 +126,7 @@ Exercise Here is another formulation of `concat`: def concat[T](xs: List[T], ys: List[T]): List[T] = - xs.foldRight(ys) (_ :: _) + xs.foldRight(ys)(_ :: _) Here, it isn't possible to replace `foldRight` by `foldLeft`. Why? @@ -127,6 +134,20 @@ Here, it isn't possible to replace `foldRight` by `foldLeft`. Why? O The resulting function would not terminate O The result would be reversed +Exercise +======== + +Here is another formulation of `concat`: + + def concat[T](xs: List[T], ys: List[T]): List[T] = + xs.foldRight(ys)(_ :: _) + +Here, it isn't possible to replace `foldRight` by `foldLeft`. Why? + + X The types would not work out + O The resulting function would not terminate + O The result would be reversed + Back to Reversing Lists ======================= @@ -151,11 +172,11 @@ We know `reverse(Nil) == Nil`, so we can compute as follows: -> = reverse(Nil) -> - = (Nil foldLeft z?)(op) + = Nil.foldLeft(z?)(op) -> = z? -Consequently, `z? = List()` +Consequently, `z? = Nil` Deduction of Reverse (2) ======================== @@ -167,11 +188,11 @@ list after `Nil` into our equation for `reverse`: -> = reverse(List(x)) -> - = (List(x) foldLeft Nil)(op?) + = List(x).foldLeft(Nil)(op?) -> = op?(Nil, x) -Consequently, `op?(Nil, x) = List(x) = x :: List()`. +Consequently, `op?(Nil, x) = List(x) = x :: Nil`. This suggests to take for `op?` the operator `::` but with its operands swapped. @@ -181,12 +202,26 @@ Deduction of Reverse(3) We thus arrive at the following implementation of `reverse`. def reverse[a](xs: List[T]): List[T] = - (xs foldLeft List[T]())((xs, x) => x :: xs) + xs.foldLeft(List[T]())((xs, x) => x :: xs) Remark: the type parameter in `List[T]()` is necessary for type inference. -\question: What is the complexity of this implementation of `reverse` ? +\red{Q}: What is the complexity of this implementation of `reverse` ? + +-> +\red{A}: Linear in `xs` + +Exercise +======== + +Complete the following definitions of the basic functions `map` and `length` on lists, such that their implementation uses `foldRight`: + + def mapFun[T, U](xs: List[T], f: T => U): List[U] = + xs.foldRight(List[U]())( ??? ) + + def lengthFun[T](xs: List[T]): Int = + xs.foldRight(0)( ??? ) Exercise ======== @@ -194,8 +229,8 @@ Exercise Complete the following definitions of the basic functions `map` and `length` on lists, such that their implementation uses `foldRight`: def mapFun[T, U](xs: List[T], f: T => U): List[U] = - (xs foldRight List[U]())( ??? ) + xs.foldRight(List[U]())((y, ys) => f(y) :: ys) def lengthFun[T](xs: List[T]): Int = - (xs foldRight 0)( ??? ) + xs.foldRight(0)((y, n) => n + 1) diff --git a/slides/sources/progfun1-5-7.md b/slides/sources/progfun1-5-7.md index 68687b4d4cd1d13c89e9acbe2d74345c662cc7a7..7d7071fcc938999297b3fd978b8b8ff7b2e28d10 100644 --- a/slides/sources/progfun1-5-7.md +++ b/slides/sources/progfun1-5-7.md @@ -94,9 +94,9 @@ Prove the following distribution law for `map` over concatenation. For any lists `xs`, `ys`, function `f`: - (xs ++ ys) map f = (xs map f) ++ (ys map f) + (xs ++ ys).map(f) = xs.map(f) ++ ys.map(f) You will need the clauses of ++ as well as the following clauses for `map`: - Nil map f = Nil - (x :: xs) map f = f(x) :: (xs map f) + Nil.map(f) = Nil + (x :: xs).map(f) = f(x) :: xs.map(f) diff --git a/slides/sources/progfun1-5-implicits.md b/slides/sources/progfun1-5-implicits.md new file mode 100644 index 0000000000000000000000000000000000000000..75ef2532dfb3bea6e8d35ad1f5101370936208c8 --- /dev/null +++ b/slides/sources/progfun1-5-implicits.md @@ -0,0 +1,125 @@ +% Implicit Parameters +% +% +<!-- to be integrated with the implicits chapter --> +Parametrization with Ordering +============================= + +There is already a class in the standard library that represents orderings. + + scala.math.Ordering[T] + +provides ways to compare elements of type `T`. So instead of +parameterizing with the `lt` operation directly, we could parameterize +with `Ordering` instead: + + def msort[T](xs: List[T])(ord: Ordering[T]) = + + def merge(xs: List[T], ys: List[T]) = + ... if ord.lt(x, y) then ... + + ... merge(msort(fst)(ord), msort(snd)(ord)) ... + +Ordering Instances: +=================== + +Calling the new `msort` can be done like this: + + import math.Ordering + + msort(nums)(Ordering.Int) + msort(fruits)(Ordering.String) + +This makes use of the values `Int` and `String` defined in the +`scala.math.Ordering` object, which produce the right orderings on +integers and strings. + +Aside: Implicit Parameters +========================== + +Calling `msort` can be done like this: + + import math.Ordering + + msort(nums)(Ordering.Int) + msort(fruits)(Ordering.String) + +\red{Problem:} Passing around `lt` or `ord` values is cumbersome. + +Aside: Implicit Parameters +========================== + +We can avoid this by making `ord` an _implicit parameter_. + + def msort[T](xs: List[T])(given ord: Ordering[T]) = + + def merge(xs: List[T], ys: List[T]) = + ... if ord.lt(x, y) then ... + + ... merge(msort(fst), msort(snd)) ... + +Then calls to `msort` can avoid the ordering parameters: + + msort(nums) + msort(fruits) + +The compiler will figure out the right `Ordering` instance to pass based on the +demanded type. + +Rules for Implicit Parameters +============================= + +Say, a function takes an implicit parameter of type `T`. + +The compiler will search a definition or parameter that + + - is marked `given` + - has a type compatible with `T` + - is visible at the point of the function call, or is defined + in a companion object associated with `T`. + +If there is a single (most specific) definition, it will be taken as +actual argument for the implicit parameter. + +Otherwise it's an error. + +Exercise: Implicit Parameters +============================= + +Consider the following line of the definition of `msort`: + + ... merge(msort(fst), msort(snd)) ... + +Which implicit argument is inserted? + + O Ordering.Int + O Ordering.String + O the "ord" parameter of "msort" + + +Exercise: Implicit Parameters +============================= + +Consider the following line of the definition of `msort`: + + ... merge(msort(fst), msort(snd)) ... + +Which implicit argument is inserted? + + O Ordering.Int + O Ordering.String + X the "ord" parameter of "msort" + + + + + + + + + + + + + + diff --git a/slides/sources/progfun1-6-1.md b/slides/sources/progfun1-6-1.md index 5e2eb4ab24bcb7d7b90f2ceea00f8fbd133e91c8..f0ce0d394c54ead60f772b55e7f61e1bdacfaf52 100644 --- a/slides/sources/progfun1-6-1.md +++ b/slides/sources/progfun1-6-1.md @@ -35,13 +35,19 @@ Instead of `x :: xs`, there is Collection Hierarchy ==================== -A common base class of `List` and `Vector` is `Seq`, the class of all _sequences_. - -`Seq` itself is a subclass of `Iterable`. - - +A common base class of `List` and `Vector` is `Seq`, the class of all _sequences_. +`Seq` itself is a subclass of `Iterable`. +~~~ + Iterable + / | \ + / | \ + ............Seq Set Map + . / \ + . / \ + Array List Vector +~~~ Arrays and Strings ================== @@ -52,10 +58,10 @@ implicitly be converted to sequences where needed. (They cannot be subclasses of `Seq` because they come from Java) val xs: Array[Int] = Array(1, 2, 3) - xs map (x => 2 * x) + xs.map(x => 2 * x) val ys: String = "Hello world!" - ys filter (_.isUpper) + ys.filter(_.isUpper) Ranges ====== @@ -74,22 +80,22 @@ determine step value): 1 to 10 by 3 6 to 1 by -2 -Ranges a represented as single objects with three fields: +A `Range` is represented as a single object with three fields: lower bound, upper bound, step value. Some more Sequence Operations: ============================== \begin{tabular}{lp{8.7cm}} - \verb` xs exists p ` & \verb`true` if there is an element \verb`x` of \verb`xs` such that \verb`p(x)` holds, \verb`false` otherwise. -\\ \verb` xs forall p ` & \verb`true` if \verb`p(x)` holds for all elements \verb`x` of \verb`xs`, \verb`false` otherwise. -\\ \verb` xs zip ys ` & A sequence of pairs drawn from corresponding elements of sequences \verb`xs` and \verb`ys`. -\\ \verb` xs.unzip ` & Splits a sequence of pairs \verb`xs` into two sequences consisting of the first, respectively second halves of all pairs. -\\ \verb` xs.flatMap f ` & Applies collection-valued function \verb`f` to all elements of \verb`xs` and concatenates the results -\\ \verb` xs.sum ` & The sum of all elements of this numeric collection. -\\ \verb` xs.product ` & The product of all elements of this numeric collection -\\ \verb` xs.max ` & The maximum of all elements of this collection (an \verb`Ordering` must exist) -\\ \verb` xs.min ` & The minimum of all elements of this collection + \verb` xs.exists(p) ` & \verb`true` if there is an element \verb`x` of \verb`xs` such that \verb`p(x)` holds, \verb`false` otherwise. +\\ \verb` xs.forall(p) ` & \verb`true` if \verb`p(x)` holds for all elements \verb`x` of \verb`xs`, \verb`false` otherwise. +\\ \verb` xs.zip(ys) ` & A sequence of pairs drawn from corresponding elements of sequences \verb`xs` and \verb`ys`. +\\ \verb` xs.unzip ` & Splits a sequence of pairs \verb`xs` into two sequences consisting of the first, respectively second halves of all pairs. +\\ \verb` xs.flatMap(f) ` & Applies collection-valued function \verb`f` to all elements of \verb`xs` and concatenates the results +\\ \verb` xs.sum ` & The sum of all elements of this numeric collection. +\\ \verb` xs.product ` & The product of all elements of this numeric collection +\\ \verb` xs.max ` & The maximum of all elements of this collection (an \verb`Ordering` must exist) +\\ \verb` xs.min ` & The minimum of all elements of this collection \end{tabular} Example: Combinations @@ -97,14 +103,14 @@ Example: Combinations To list all combinations of numbers `x` and `y` where `x` is drawn from `1..M` and `y` is drawn from `1..N`: - (1 to M) flatMap (x => + (1 to M).flatMap(x => Example: Combinations ===================== To list all combinations of numbers `x` and `y` where `x` is drawn from `1..M` and `y` is drawn from `1..N`: - (1 to M) flatMap (x => (1 to N) map (y => (x, y))) + (1 to M).flatMap(x => (1 to N).map(y => (x, y))) Example: Scalar Product ======================= @@ -112,21 +118,36 @@ Example: Scalar Product To compute the scalar product of two vectors: def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = - (xs zip ys).map(xy => xy._1 * xy._2).sum + xs.zip(ys).map(xy => xy._1 * xy._2).sum -> An alternative way to write this is with a \red{pattern matching function value}. def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = - (xs zip ys).map{ case (x, y) => x * y }.sum + xs.zip(ys).map { case (x, y) => x * y }.sum Generally, the function value { case p1 => e1 ... case pn => en } -is equivalent to +is equivalent to x => x match { case p1 => e1 ... case pn => en } +Example: Scalar Product +======================= + +For simple tuple decomposition, the `case` prefix in the pattern can be omitted. + +So, the previous code can be simplified to: + + def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = + xs.zip(ys).map((x, y) => x * y).sum + +Or, even simpler + + def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = + xs.zip(ys).map(_ * _).sum + Exercise: ========= @@ -146,7 +167,8 @@ A number `n` is \red{prime} if the only divisors of `n` are `1` and `n` itself. What is a high-level way to write a test for primality of numbers? For once, value conciseness over efficiency. - def isPrime(n: Int): Boolean = + def isPrime(n: Int): Boolean = + (2 to srqt(n)).forall(d => n % d == 0) \quiz diff --git a/slides/sources/progfun1-6-2.md b/slides/sources/progfun1-6-2.md index 1c6c5482e92f00182ba90dd7fb41272dcbdc4cdf..ddc5e381d48ad86f182cba0e8ecc5aa4530016a9 100644 --- a/slides/sources/progfun1-6-2.md +++ b/slides/sources/progfun1-6-2.md @@ -32,8 +32,8 @@ One natural way to generate the sequence of pairs is to: This can be achieved by combining `until` and `map`: - (1 until n) map (i => - (1 until i) map (j => (i, j))) + (1 until n).map(i => + (1 until i).map(j => (i, j))) Generate Pairs @@ -43,7 +43,7 @@ The previous step gave a sequence of sequences, let's call it `xss`. We can combine all the sub-sequences using `foldRight` with `++`: - (xss foldRight Seq[Int]())(_ ++ _) + xss.foldRight(Seq[Int]())(_ ++ _) Or, equivalently, we use the built-in method `flatten` @@ -51,20 +51,20 @@ Or, equivalently, we use the built-in method `flatten` This gives: - ((1 until n) map (i => - (1 until i) map (j => (i, j)))).flatten + ((1 until n).map(i => + (1 until i)map(j => (i, j)))).flatten Generate Pairs (2) ================== Here's a useful law: - xs flatMap f = (xs map f).flatten + xs.flatMap(f) = xs.map(f).flatten Hence, the above expression can be simplified to - (1 until n) flatMap (i => - (1 until i) map (j => (i, j))) + (1 until n).flatMap(i => + (1 until i).map(j => (i, j))) Assembling the pieces @@ -72,9 +72,9 @@ Assembling the pieces By reassembling the pieces, we obtain the following expression: - (1 until n) flatMap (i => - (1 until i) map (j => (i, j))) filter ( pair => - isPrime(pair._1 + pair._2)) + (1 until n) + .flatMap(i => (1 until i).map(j => (i, j))) + .filter((x, y) => isPrime(x + y)) This works, but makes most people's head hurt. @@ -99,11 +99,14 @@ Let `persons` be a list of elements of class `Person`, with fields `name` and `a To obtain the names of persons over 20 years old, you can write: - for ( p <- persons if p.age > 20 ) yield p.name + for p <- persons if p.age > 20 + yield p.name which is equivalent to: - persons filter (p => p.age > 20) map (p => p.name) + persons + .filter(p => p.age > 20) + .map(p => p.name) The for-expression is similar to loops in imperative languages, except that it builds a list of the results of all iterations. @@ -113,7 +116,7 @@ Syntax of For A for-expression is of the form - for ( s ) yield e + for s yield e where `s` is a sequence of \red{generators} and \red{filters}, and `e` is an expression whose value is returned by an iteration. @@ -124,10 +127,6 @@ and `e` is an expression whose value is returned by an iteration. - The sequence must start with a generator. - If there are several generators in the sequence, the last generators vary faster than the first. -Instead of `( s )`, braces `{ s }` can also be used, and then the -sequence of generators and filters can be written on multiple lines -without requiring semicolons. - Use of For ========== @@ -151,5 +150,5 @@ a `for`: def scalarProduct(xs: List[Double], ys: List[Double]) : Double = -> - (for ((x, y) <- xs zip ys) yield x * y).sum + (for ((x, y) <- xs.zip(ys)) yield x * y).sum \quiz diff --git a/slides/sources/progfun1-6-3.md b/slides/sources/progfun1-6-3.md index 826285276a86787614606223ab895a06f56db69d..d2352261df3ce2743d94c63834d5bb65521628ff 100644 --- a/slides/sources/progfun1-6-3.md +++ b/slides/sources/progfun1-6-3.md @@ -63,14 +63,13 @@ Implementation def queens(n: Int) = def placeQueens(k: Int): Set[List[Int]] = - if k == 0 Set(List()) + if k == 0 then Set(List()) else for queens <- placeQueens(k - 1) col <- 0 until n if isSafe(col, queens) - yield - col :: queens + yield col :: queens placeQueens(n) Exercise @@ -85,3 +84,4 @@ which tests if a queen placed in an indicated column `col` is secure amongst the It is assumed that the new queen is placed in the next available row after the other placed queens (in other words: in row `queens.length`). \quiz + diff --git a/slides/sources/progfun1-6-4.md b/slides/sources/progfun1-6-4.md index 2ee574be2a30d48ff4b64cb88aceecab751005c6..23759337472f0b22d370c6a5005413bd616fbcf2 100644 --- a/slides/sources/progfun1-6-4.md +++ b/slides/sources/progfun1-6-4.md @@ -23,7 +23,7 @@ Class `Map[Key, Value]` extends the collection type \newline Therefore, maps support the same collection operations as other iterables do. Example: val countryOfCapital = capitalOfCountry.map((x, y) => (y, x)) - // Map("Washington" -> "US", "Bern" -> "Switzerland") + // Map("Washington" -> "US", "Bern" -> "Switzerland") Note that maps extend iterables of key/value _pairs_. @@ -50,8 +50,8 @@ Applying a map to a non-existing key gives an error: To query a map without knowing beforehand whether it contains a given key, you can use the `get` operation: - capitalOfCountry get "US" // Some("Washington") - capitalOfCountry get "Andorra" // None + capitalOfCountry.get("US") // Some("Washington") + capitalOfCountry.get("Andorra") // None The result of a `get` operation is an `Option` value. @@ -64,7 +64,7 @@ The `Option` type is defined as: case class Some[+A](value: A) extends Option[A] object None extends Option[Nothing] -The expression `map get key` returns +The expression `map.get(key)` returns - `None` \gap if `map` does not contain the given `key`, - `Some(x)` \ \ if `map` associates the given `key` with the value `x`. @@ -86,6 +86,26 @@ Options also support quite a few operations of the other collections. I invite you to try them out! +Updating Maps +============= + +Functional updates of a map are done with the `+` and `++` operations: + +\begin{tabular}{lp{8cm}} + \verb` m + (k -> v)` & The map that takes key `k` to value `v` \\ + \verb` ` & and is otherwise equal to `m` \\ + \verb` m ++ kvs` & The map `m` updated via `+` with all key/value pairs in `kvs` +\end{tabular} + +These operations are purely functional. For instance, + +\begin{worksheet} +val m1 = Map("red" -> 1, "blue" -> 2) \wsf m1 = Map(red -> 1, blue -> 2) \\ +val m2 = m1 + ("blue" -> 3) \wsf m2 = Map(red -> 1, blue -> 3) \\ +m1 \wsf Map(red -> 1, blue -> 2) +\end{worksheet} + + Sorted and GroupBy ================== @@ -95,17 +115,19 @@ Two useful operation of SQL queries in addition to for-expressions are `orderBy` on a collection can be expressed by `sortWith` and `sorted`. val fruit = List("apple", "pear", "orange", "pineapple") - fruit sortWith (_.length < _.length) // List("pear", "apple", "orange", "pineapple") - fruit.sorted // List("apple", "orange", "pear", "pineapple") + fruit.sortWith(_.length < _.length) // List("pear", "apple", "orange", "pineapple") + fruit.sorted // List("apple", "orange", "pear", "pineapple") `groupBy` is available on Scala collections. It partitions a collection into a map of collections according to a _discriminator function_ `f`. \example: - fruit groupBy (_.head) //> Map(p -> List(pear, pineapple), + fruit.groupBy(_.head) //> Map(p -> List(pear, pineapple), //| a -> List(apple), //| o -> List(orange)) +<!--- + Map Example =========== @@ -127,7 +149,7 @@ in the map. There is an operation `withDefaultValue` that turns a map into a total function: - val cap1 = capitalOfCountry withDefaultValue "<unknown>" + val cap1 = capitalOfCountry.withDefaultValue("<unknown>") cap1("Andorra") // "<unknown>" Variable Length Argument Lists @@ -144,7 +166,7 @@ Problem: The number of `key -> value` pairs passed to `Map` can vary. We can accommodate this pattern using a \red{repeated parameter}: def Polynom(bindings: (Int, Double)*) = - new Polynom(bindings.toMap withDefaultValue 0) + Polynom(bindings.toMap.withDefaultValue(0)) Polynom(1 -> 2.0, 3 -> 4.0, 5 -> 6.2) @@ -154,31 +176,30 @@ Inside the `Polynom` function, `bindings` is seen as a Final Implementation of Polynom =============================== - class Poly(terms0: Map[Int, Double]) + class Polynom(terms0: Map[Int, Double]) def this(bindings: (Int, Double)*) = this(bindings.toMap) - val terms = terms0 withDefaultValue 0.0 - - def + (other: Poly) = new Poly(terms ++ (other.terms map adjust)) - - def adjust(term: (Int, Double)): (Int, Double) = + val terms = terms0.withDefaultValue(0.0) + def + (other: Polynom) = Polynom(terms ++ other.terms.map(adjust)) + def adjust(term: (Int, Double)): (Int, Double) = { val (exp, coeff) = term exp -> (coeff + terms(exp)) + } - override def toString = - val termStrings = - for (exp, coeff) <- terms.toList.sorted.reverse - yield s"${coeff}x^$exp" - termStrings.mkString(" + ") + override def toString = + val termStrings = + for (exp, coeff) <- terms.toList.sorted.reverse + yield s"${coeff}x^$exp" + termStrings.mkString(" + ") Exercise ======== -The `+` operation on `Poly` used map concatenation with `++`. +The `+` operation on `Polynom` used map concatenation with `++`. Design another version of `+` in terms of `foldLeft`: - def + (other: Poly) = - new Poly((other.terms foldLeft ???)(addTerm) + def + (other: Polynom) = + Polynom(other.terms.foldLeft(???)(addTerm)) def addTerm(terms: Map[Int, Double], term: (Int, Double)) = ??? @@ -192,3 +213,23 @@ Which of the two versions do you believe is more efficient? \quiz +Exercise +======== + +The `+` operation on `Polynom` used map concatenation with `++`. +Design another version of `+` in terms of `foldLeft`: + + def + (other: Polynom) = + Polynom(other.terms.foldLeft(terms)(addTerm)) + + def addTerm(terms: Map[Int, Double], term: (Int, Double)) = + val (exp, coeff) = term + terms + (exp, coeff + terms(exp)) + +Which of the two versions do you believe is more efficient? + + O The version using ++ + X The version using foldLeft + +-> +--> diff --git a/slides/sources/progfun1-6-5.md b/slides/sources/progfun1-6-5.md index 1b3d92a4eaba7af58d146cbc5b9153890151d54c..c8ba60f68b1f649eeb33a21053639fa2830957c6 100644 --- a/slides/sources/progfun1-6-5.md +++ b/slides/sources/progfun1-6-5.md @@ -7,38 +7,244 @@ Task Phone keys have mnemonics assigned to them. val mnemonics = Map( - '2' -> "ABC", '3' -> "DEF", '4' -> "GHI", '5' -> "JKL", + '2' -> "ABC", '3' -> "DEF", '4' -> "GHI", '5' -> "JKL", '6' -> "MNO", '7' -> "PQRS", '8' -> "TUV", '9' -> "WXYZ") -Assume you are given a dictionary `words` as a list of words. +Assume you are given a dictionary `words` as a list of words. -Design a method `translate` such that +Design a method `encode` such that - translate(phoneNumber) + encode(phoneNumber) -produces all phrases of words +produces all phrases of words that can serve as mnemonics for the phone number. \example: The phone number "7225247386" should have the mnemonic `Scala is fun` as one element of the set of solution phrases. +Outline +======= + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = ??? + + /** Maps a word to the digit string it can represent */ + private def wordCode(word: String): String = ??? + + /** Maps a digit string to all words in the dictionary that represent it */ + private val wordsForNum: Map[String, List[String]] = ??? + + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = ??? +} +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = + for (digit, str) <- mnemonics; ltr <- str yield ltr -> digit +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = + for (digit, str) <- mnemonics; ltr <- str yield ltr -> digit + + /** Maps a word to the digit string it can represent */ + private def wordCode(word: String): String = +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = + for (digit, str) <- mnemonics; ltr <- str yield ltr -> digit + + /** Maps a word to the digit string it can represent */ + private def wordCode(word: String): String = word.toUpperCase.map(charCode) +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = + for (digit, str) <- mnemonics; ltr <- str yield ltr -> digit + + /** Maps a word to the digit string it can represent */ + private def wordCode(word: String): String = word.toUpperCase.map(charCode) + + /** Maps a digit string to all words in the dictionary that represent it */ + private val wordsForNum: Map[String, List[String]] = +~~~ + +Implementation (1) +================== + +~~~ +class Coder(words: List[String]) { + val mnemonics = Map(...) + + /** Maps a letter to the digit it represents */ + val charCode: Map[Char, Char] = + for (digit, str) <- mnemonics; ltr <- str yield ltr -> digit + + /** Maps a word to the digit string it can represent */ + private def wordCode(word: String): String = word.toUpperCase.map(charCode) + + /** Maps a digit string to all words in the dictionary that represent it */ + private val wordsForNum: Map[String, List[String]] = + words.groupBy(wordCode).withDefaultValue(Nil) +~~~ + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = +~~~ + +Idea: use divide and conquer + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = + if number.isEmpty then ??? + else ??? +~~~ + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = + if number.isEmpty then Set(Nil) + else ??? +~~~ + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = + if number.isEmpty then Set(Nil) + else + for + splitPoint <- (1 to number.length).toSet + word <- ??? + rest <- ??? + yield word :: rest +~~~ + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = + if number.isEmpty then Set(Nil) + else + for + splitPoint <- (1 to number.length).toSet + word <- wordsForNum(number.take(splitPoint)) + rest <- ??? + yield word :: rest +~~~ + +Implementation (2) +================== + +~~~ + /** All ways to encode a number as a list of words */ + def encode(number: String): Set[List[String]] = + if number.isEmpty then Set(Nil) + else + for + splitPoint <- (1 to number.length).toSet + word <- wordsForNum(number.take(splitPoint)) + rest <- encode(number.drop(splitPoint)) + yield word :: rest +~~~ + +Testing It +========== + +A test program: + +~~~ + @main def code(number: String) = + val coder = Coder(List( + "Scala", "Python", "Ruby", "C", + "rocks", "socks", "sucks", "works", "pack")) + coder.encode(number).map(_.mkString(" ")) +~~~ + +A sample run: + +~~~ +> scala code "7225276257" +HashSet(Scala rocks, pack C rocks, pack C socks, Scala socks) +~~~ + Background ========== This example was taken from: \begin{quote} -Lutz Prechelt: An Empirical Comparison of Seven Programming Languages. IEEE Computer 33(10): 23-29 (2000) +Lutz Prechelt: An Empirical Comparison of Seven Programming Languages. IEEE Computer 33(10): 23-29 (2000) \end{quote} Tested with Tcl, Python, Perl, Rexx, Java, C++, C. -Code size medians: +Code size medians: - 100 loc for scripting languages - 200-300 loc for the others -The Future? -=========== +Benefits +======== Scala's immutable collections are: @@ -48,4 +254,4 @@ Scala's immutable collections are: - _fast_: collection ops are tuned, can be parallelized. - _universal_: one vocabulary to work on all kinds of collections. -This makes them a very attractive tool for software development +This makes them an attractive tool for software development diff --git a/slides/sources/src/ListSpecification.scala b/slides/sources/src/ListSpecification.scala index d2326987ddd62de2f192850dbf667f94604b155a..668a0d414adc49bc1c2207902ff260d1df93d852 100644 --- a/slides/sources/src/ListSpecification.scala +++ b/slides/sources/src/ListSpecification.scala @@ -8,15 +8,15 @@ object ListSpecification extends Properties("List") { property("double reverse") = forAll { (lst: List[Int]) => lst.reverse.reverse == lst } - + // property("zip reverse") = forAll { (a: List[Int], b: List[Int]) => -// (a.reverse zip b.reverse) == (a zip b).reverse +// a.reverse.zip(b.reverse) == a.zip(b).reverse // } - + // property("zip reverse") = forAll { (a: List[Int], b: List[Int]) => // val a1 = a.take(b.length) // val b1 = b.take(a.length) -// (a1.reverse zip b1.reverse) == (a1 zip b1).reverse +// a1.reverse.zip(b1.reverse) == a1.zip(b1).reverse // } } \ No newline at end of file diff --git a/slides/sources/src/maps.sc b/slides/sources/src/maps.sc index 40796e1a2588fbb44c0c4a58ac0b080d0befd4e8..e3de773f85d4d09a76b0e2bb58b96768f81bd711 100644 --- a/slides/sources/src/maps.sc +++ b/slides/sources/src/maps.sc @@ -2,7 +2,7 @@ package week6 object maps { val romanNumerals = Map('I' -> 1, 'V' -> 5, 'X' -> 10) - //> romanNumerals : scala.collection.immutable.Map[Char,Int] = Map(I -> 1, V -> + //> romanNumerals : scala.collection.immutable.Map[Char,Int] = Map(I -> 1, V -> //| 5, X -> 10) val capitalOfCountry = Map("US" -> "Washington", "Switzerland" -> "Bern") //> capitalOfCountry : scala.collection.immutable.Map[String,String] = Map(US - diff --git a/slides/sources/src/pairs.sc b/slides/sources/src/pairs.sc index c6a9ef92bdea3cc1de52e33ab2793342dc84d9be..dce366c69866a1241c6d5ed1211f3c272a753210 100644 --- a/slides/sources/src/pairs.sc +++ b/slides/sources/src/pairs.sc @@ -3,12 +3,12 @@ package week6 object pairs { val s = (1 to 6).toSet //> s : scala.collection.immutable.Set[Int] = Set(5, 1, 6, 2, 3, 4) - + s map (_ / 2) //> res0: scala.collection.immutable.Set[Int] = Set(2, 0, 3, 1) - + s contains 5 //> res1: Boolean = true - - def isPrime(n: Int) = (2 until n) forall (n % _ != 0) + + def isPrime(n: Int) = (2 until n).forall(n % _ != 0) //> isPrime: (n: Int)Boolean val n = 8 //> n : Int = 8 val sublists = @@ -28,125 +28,120 @@ object pairs { //| ), (4,1), (4,3), (5,2), (6,1), (6,5), (7,4), (7,6)) def queens(n: Int) = { - def placeQueens(k: Int): Set[List[Int]] = { + def placeQueens(k: Int): Set[List[Int]] = if (k == 0) Set(List()) - else { - def isSafe(col: Int, queens: List[Int]) = { + else + def isSafe(col: Int, queens: List[Int]) = val row = queens.length - queens zip (row - 1 to 0 by -1) forall { - case (c, r) => col != c && (row - r) != math.abs(col - c) - } - } - for { + queens.zip(row - 1 to 0 by -1).forall( + (c, r) => col != c && (row - r) != math.abs(col - c)) + for queens <- placeQueens(k - 1) col <- 0 until n if isSafe(col, queens) - } yield col :: queens - } - } - placeQueens(n) - } //> queens: (n: Int)Set[List[Int]] - + yield col :: queens + placeQueens(n) //> queens: (n: Int)Set[List[Int]] + def show(queens: List[Int]) = { val lines = for (col <- queens.reverse) yield Vector.fill(queens.length)("* ").updated(col, "X ").mkString "\n" + (lines mkString "\n") } //> show: (queens: List[Int])String - + (queens(8) take 10 map show) mkString "\n" //> res4: String = " - //| * * * X * * * * - //| * * * * * * * X - //| X * * * * * * * - //| * * * * X * * * - //| * * * * * * X * - //| * X * * * * * * - //| * * * * * X * * - //| * * X * * * * * - //| - //| * * * * X * * * - //| * * X * * * * * - //| X * * * * * * * - //| * * * * * * X * - //| * X * * * * * * - //| * * * * * * * X - //| * * * * * X * * - //| * * * X * * * * - //| - //| * X * * * * * * - //| * * * * X * * * - //| * * * * * * X * - //| X * * * * * * * - //| * * X * * * * * - //| * * * * * * * X - //| * * * * * X * * - //| * * * X * * * * - //| - //| * * * * * * X * - //| * * * X * * * * - //| * X * * * * * * - //| * * * * * * * X - //| * * * * * X * * - //| X * * * * * * * - //| * * X * * * * * - //| * * * * X * * * - //| - //| * * * * * X * * - //| * * X * * * * * - //| * * * * * * X * - //| * X * * * * * * - //| * * * X * * * * - //| * * * * * * * X - //| X * * * * * * * - //| * * * * X * * * - //| - //| * * * * * X * * - //| * * X * * * * * - //| * * * * * * X * - //| * * * X * * * * - //| X * * * * * * * - //| * * * * * * * X - //| * X * * * * * * - //| * * * * X * * * - //| - //| * * * * * X * * - //| * * * X * * * * - //| X * * * * * * * - //| * * * * X * * * - //| * * * * * * * X - //| * X * * * * * * - //| * * * * * * X * - //| * * X * * * * * - //| - //| * * * * X * * * - //| * * * * * * X * - //| * X * * * * * * - //| * * * X * * * * - //| * * * * * * * X - //| X * * * * * * * - //| * * X * * * * * - //| * * * * * X * * - //| - //| * X * * * * * * - //| * * * * * X * * - //| X * * * * * * * - //| * * * * * * X * - //| * * * X * * * * - //| * * * * * * * X - //| * * X * * * * * - //| * * * * X * * * - //| - //| * * * * * X * * - //| * * * X * * * * - //| * X * * * * * * - //| * * * * * * * X - //| * * * * X * * * - //| * * * * * * X * - //| X * * * * * * * + //| * * * X * * * * + //| * * * * * * * X + //| X * * * * * * * + //| * * * * X * * * + //| * * * * * * X * + //| * X * * * * * * + //| * * * * * X * * + //| * * X * * * * * + //| + //| * * * * X * * * + //| * * X * * * * * + //| X * * * * * * * + //| * * * * * * X * + //| * X * * * * * * + //| * * * * * * * X + //| * * * * * X * * + //| * * * X * * * * + //| + //| * X * * * * * * + //| * * * * X * * * + //| * * * * * * X * + //| X * * * * * * * + //| * * X * * * * * + //| * * * * * * * X + //| * * * * * X * * + //| * * * X * * * * + //| + //| * * * * * * X * + //| * * * X * * * * + //| * X * * * * * * + //| * * * * * * * X + //| * * * * * X * * + //| X * * * * * * * + //| * * X * * * * * + //| * * * * X * * * + //| + //| * * * * * X * * + //| * * X * * * * * + //| * * * * * * X * + //| * X * * * * * * + //| * * * X * * * * + //| * * * * * * * X + //| X * * * * * * * + //| * * * * X * * * + //| + //| * * * * * X * * + //| * * X * * * * * + //| * * * * * * X * + //| * * * X * * * * + //| X * * * * * * * + //| * * * * * * * X + //| * X * * * * * * + //| * * * * X * * * + //| + //| * * * * * X * * + //| * * * X * * * * + //| X * * * * * * * + //| * * * * X * * * + //| * * * * * * * X + //| * X * * * * * * + //| * * * * * * X * + //| * * X * * * * * + //| + //| * * * * X * * * + //| * * * * * * X * + //| * X * * * * * * + //| * * * X * * * * + //| * * * * * * * X + //| X * * * * * * * + //| * * X * * * * * + //| * * * * * X * * + //| + //| * X * * * * * * + //| * * * * * X * * + //| X * * * * * * * + //| * * * * * * X * + //| * * * X * * * * + //| * * * * * * * X + //| * * X * * * * * + //| * * * * X * * * + //| + //| * * * * * X * * + //| * * * X * * * * + //| * X * * * * * * + //| * * * * * * * X + //| * * * * X * * * + //| * * * * * * X * + //| X * * * * * * * //| * * X * * * * * " - + def isSafe(row: Int, col: Int, queens: List[Int]) = - queens zip (row - 1 to 0 by -1) forall { - case (r, c) => col != c && (row - r) != math.abs(col - c) - } //> isSafe: (row: Int, col: Int, queens: List[Int])Boolean + queens.zip(row - 1 to 0 by -1).forall( + (r, c) => col != c && (row - r) != math.abs(col - c) + ) //> isSafe: (row: Int, col: Int, queens: List[Int])Boolean } \ No newline at end of file