Skip to content
Snippets Groups Projects
Commit 36a8714a authored by Martin Odersky's avatar Martin Odersky
Browse files

Updates to week 5 and 6

parent e7c6e8e5
No related branches found
No related tags found
No related merge requests found
Showing
with 1065 additions and 630 deletions
...@@ -131,6 +131,7 @@ $endif$ ...@@ -131,6 +131,7 @@ $endif$
\renewcommand{\example}{\mbox{\boldred{Example}}} \renewcommand{\example}{\mbox{\boldred{Example}}}
\newcommand{\question}{\mbox{\boldred{Question}}} \newcommand{\question}{\mbox{\boldred{Question}}}
\newcommand{\answer}{\mbox{\boldred{Answer}}}
\renewcommand{\note}{\mbox{\red{Note:}}} \renewcommand{\note}{\mbox{\red{Note:}}}
% commas and semicolons % commas and semicolons
......
...@@ -120,6 +120,8 @@ In Scala, only directly recursive calls to the current function are optimized. ...@@ -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: One can require that a function is tail-recursive using a `@tailrec` annotation:
import scala.annotation.tailrec
@tailrec @tailrec
def gcd(a: Int, b: Int): Int = ... def gcd(a: Int, b: Int): Int = ...
......
...@@ -10,7 +10,7 @@ Look again at the summation functions: ...@@ -10,7 +10,7 @@ Look again at the summation functions:
def sumCubes(a: Int, b: Int) = sum(x => x * x * x, a, b) def sumCubes(a: Int, b: Int) = sum(x => x * x * x, a, b)
def sumFactorials(a: Int, b: Int) = sum(fact, 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`. Note that `a` and `b` get passed unchanged from `sumInts` and `sumCubes` into `sum`.
......
...@@ -123,7 +123,7 @@ Here's a formulation of the `eval` method using type tests and casts: ...@@ -123,7 +123,7 @@ Here's a formulation of the `eval` method using type tests and casts:
else if e.isInstanceOf[Sum] then else if e.isInstanceOf[Sum] then
eval(e.asInstanceOf[Sum].leftOp) eval(e.asInstanceOf[Sum].leftOp)
+ eval(e.asInstanceOf[Sum].rightOp) + eval(e.asInstanceOf[Sum].rightOp)
else throw new Error("Unknown expression " + e) else throw Error("Unknown expression " + e)
Assessment of this solution: Assessment of this solution:
-> ->
......
...@@ -200,7 +200,7 @@ Lower Bounds ...@@ -200,7 +200,7 @@ Lower Bounds
But `prepend` is a natural method to have on immutable lists! 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 \medskip
-> ->
......
% 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
% 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}} \example
\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`.
\end{tabular} val fruit = List("apples", "oranges", "pears")
val nums = List(1, 2, 3, 4)
List Methods (2) val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
================ val empty = List()
\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}
\red{Finding elements:} There are two important differences between lists and arrays.
\begin{tabular}{lp{8cm}} - Lists are immutable --- the elements of a list cannot be changed.
\verb` xs indexOf x `& The index of the first element in \verb`xs` equal to \verb`x`, or \verb`-1` if \verb`x` - Lists are recursive, while arrays are flat.
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.
What is the complexity of `last`? Lists
=====
To find out, let's write a possible implementation of `last` as a stand-alone function. val fruit = List("apples", "oranges", "pears")
val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
def last[T](xs: List[T]): T = xs match
case List() => throw new Error("last of empty list")
case List(x) =>
case y :: ys =>
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 \example
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
========
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 Constructors of Lists
case List() => throw new Error("init of empty list") =====================
case List(x) => ???
case y :: ys => ???
\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 fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
case List() => throw new Error("init of empty list") nums = 1 :: (2 :: (3 :: (4 :: Nil)))
case List(x) => empty = Nil
case y :: ys =>
\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 So the expression above is equivalent to
case List() =>
case z :: zs =>
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 \begin{tabular}{ll}
case List() => ys \verb`head` & the first element of the list \\
case z :: zs => \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 It is also possible to decompose lists with pattern matching.
case List() => ys
case z :: zs => z :: concat(zs, ys)
-> \begin{tabular}{lp{9cm}}
What is the complexity of `concat`? \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 What is the condition that describes most accurately the length `L`
case List() => of the lists it matches?
case y :: ys =>
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 Consider the pattern `x :: y :: List(xs, ys) :: zs`.
case List() => List()
case y :: ys =>
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 This idea describes \red{Insertion Sort} :
case List() => List()
case y :: ys => reverse(ys) ++ List(y)
->
What is the complexity of `reverse`?
\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 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} O the sort takes constant time
\verb`removeAt(1, List('a', 'b', 'c', 'd'))` \wsf List(a, c, d) O proportional to N
\end{worksheet} 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)))) def insert(x: Int, xs: List[Int]): List[Int] = xs match
> res0: List[Any] = List(1, 1, 2, 3, 5, 8) 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
% Pairs and Tuples % More Functions on Lists
% %
% %
Sorting Lists Faster
====================
As a non-trivial example, let's design a function to sort lists List Methods (1)
that is more efficient than insertion sort. ================
A good algorithm for this is \red{merge sort}. The idea is as \red{Sublists and element access:}
follows:
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 List Methods (2)
the elements of the original list. ================
- Sort the two sub-lists.
- Merge the two sorted sub-lists into a single sorted list.
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] = \red{Finding elements:}
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))
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]) = The complexity of `head` is (small) constant time.
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 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 def last[T](xs: List[T]): T = xs match
- the elements from that index 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} Implementation of `last`
\verb` val pair = ("answer", 42)` \wsf pair : (String, Int) = (answer,42) ========================
\end{worksheet}
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} To find out, let's write a possible implementation of `last` as a stand-alone function.
\verb` val (label, value) = pair` \wsf label : String = answer \\
\wsn value : Int = 42 def last[T](xs: List[T]): T = xs match
\end{worksheet} 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 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
???
% Implicit Parameters % Tuples and Generic Methods
% %
% %
Sorting Lists Faster
====================
Making Sort more General As a non-trivial example, let's design a function to sort lists
======================== that is more efficient than insertion sort.
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) A good algorithm for this is \red{merge sort}. The idea is as
msort(fruits)((x: String, y: String) => x.compareTo(y) < 0) 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 The SplitAt Function
parameterizing with the `lt` operation directly, we could parameterize ====================
with `Ordering` instead:
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]) = - the elements up the the given index
... if ord.lt(x, y) then ... - 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) \example
msort(fruits)(Ordering.String)
This makes use of the values `Int` and `String` defined in the \begin{worksheet}
`scala.math.Ordering` object, which produce the right orderings on \verb` val pair = ("answer", 42)` \wsf pair : (String, Int) = (answer,42)
integers and strings. \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]) = Translation of Tuples
... if ord.lt(x, y) then ... =====================
... 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) Here, all `Tuple`\mbox{\it n} classes are modeled after the following pattern:
msort(fruits)
The compiler will figure out the right implicit to pass based on the case class Tuple2[T1, T2](_1: +T1, _2: +T2) {
demanded type. 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` one could also have written:
- 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 val label = pair._1
actual argument for the implicit parameter. 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 Problem: How to parameterize `msort` so that it can also be used for
O Ordering.String lists with elements other than `Int`?
O the "ord" parameter of "msort"
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)
...@@ -30,15 +30,15 @@ For example, to multiply each element of a list by the same factor, you could wr ...@@ -30,15 +30,15 @@ For example, to multiply each element of a list by the same factor, you could wr
case Nil => xs case Nil => xs
case y :: ys => y * factor :: scaleList(ys, factor) case y :: ys => y * factor :: scaleList(ys, factor)
Mapping
Map =======
===
This scheme can be generalized to the method `map` of the This scheme can be generalized to the method `map` of the
`List` class. A simple way to define `map` is as follows: `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 def map[U](f: T => U): List[U] = this match
case Nil => this case Nil => this
case x :: xs => f(x) :: xs.map(f) case x :: xs => f(x) :: xs.map(f)
...@@ -50,7 +50,7 @@ collections, not just lists). ...@@ -50,7 +50,7 @@ collections, not just lists).
Using `map`, `scaleList` can be written more concisely. Using `map`, `scaleList` can be written more concisely.
def scaleList(xs: List[Double], factor: Double) = def scaleList(xs: List[Double], factor: Double) =
xs map (x => x * factor) xs.map(x => x * factor)
Exercise Exercise
======== ========
...@@ -62,6 +62,7 @@ return the result. Complete the two following equivalent definitions of ...@@ -62,6 +62,7 @@ return the result. Complete the two following equivalent definitions of
def squareList(xs: List[Int]): List[Int] = xs match def squareList(xs: List[Int]): List[Int] = xs match
case Nil => ??? case Nil => ???
case y :: ys => ???
def squareList(xs: List[Int]): List[Int] = def squareList(xs: List[Int]): List[Int] =
xs.map(???) xs.map(???)
...@@ -75,11 +76,11 @@ return the result. Complete the two following equivalent definitions of ...@@ -75,11 +76,11 @@ return the result. Complete the two following equivalent definitions of
def squareList(xs: List[Int]): List[Int] = xs match def squareList(xs: List[Int]): List[Int] = xs match
case Nil => case Nil => Nil
case y :: ys => case y :: ys => y * y :: squareList(ys)
def squareList(xs: List[Int]): List[Int] = def squareList(xs: List[Int]): List[Int] =
xs.map xs.map(x => x * x)
Filtering Filtering
========= =========
...@@ -97,7 +98,7 @@ Filter ...@@ -97,7 +98,7 @@ Filter
This pattern is generalized by the method `filter` of the `List` class: This pattern is generalized by the method `filter` of the `List` class:
abstract class List[T] abstract class List[T]
...
def filter(p: T => Boolean): List[T] = this match def filter(p: T => Boolean): List[T] = this match
case Nil => this case Nil => this
case x :: xs => if p(x) then x :: xs.filter(p) else xs.filter(p) 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: ...@@ -106,7 +107,7 @@ This pattern is generalized by the method `filter` of the `List` class:
Using `filter`, `posElems` can be written more concisely. Using `filter`, `posElems` can be written more concisely.
def posElems(xs: List[Int]): List[Int] = def posElems(xs: List[Int]): List[Int] =
xs filter (x => x > 0) xs.filter(x => x > 0)
Variations of Filter Variations of Filter
==================== ====================
...@@ -115,11 +116,11 @@ Besides filter, there are also the following methods that extract ...@@ -115,11 +116,11 @@ Besides filter, there are also the following methods that extract
sublists based on a predicate: sublists based on a predicate:
\begin{tabular}{lp{8cm}} \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.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.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.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.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} \end{tabular}
Exercise Exercise
...@@ -142,6 +143,25 @@ You can use the following template: ...@@ -142,6 +143,25 @@ You can use the following template:
Exercise 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 Using `pack`, write a function `encode` that produces the run-length
encoding of a list. encoding of a list.
...@@ -153,3 +173,12 @@ should give ...@@ -153,3 +173,12 @@ should give
List(("a", 3), ("b", 1), ("c", 2), ("a", 1)). 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))
...@@ -17,6 +17,7 @@ We can implement this with the usual recursive schema: ...@@ -17,6 +17,7 @@ We can implement this with the usual recursive schema:
case Nil => 0 case Nil => 0
case y :: ys => y + sum(ys) case y :: ys => y + sum(ys)
ReduceLeft ReduceLeft
========== ==========
...@@ -25,13 +26,12 @@ This pattern can be abstracted out using the generic method `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 `reduceLeft` inserts a given binary operator between adjacent elements
of a list: 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: Using `reduceLeft`, we can simplify:
def sum(xs: List[Int]) = (0 :: 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) def product(xs: List[Int]) = (1 :: xs).reduceLeft((x, y) => x * y)
A Shorter Way to Write Functions 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 ...@@ -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: So, `sum` and `product` can also be expressed like this:
def sum(xs: List[Int]) = (0 :: xs) reduceLeft (_ + _) def sum(xs: List[Int]) = (0 :: xs).reduceLeft(_ + _)
def product(xs: List[Int]) = (1 :: xs) reduceLeft (_ * _) def product(xs: List[Int]) = (1 :: xs).reduceLeft(_ * _)
FoldLeft FoldLeft
======== ========
...@@ -56,14 +56,17 @@ The function `reduceLeft` is defined in terms of a more general function, `foldL ...@@ -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. `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: So, `sum` and `product` can also be defined as follows:
def sum(xs: List[Int]) = (xs foldLeft 0) (_ + _) def sum(xs: List[Int]) = xs.foldLeft(0)(_ + _)
def product(xs: List[Int]) = (xs foldLeft 1) (_ * _) def product(xs: List[Int]) = xs.foldLeft(1)(_ * _)
Implementations of ReduceLeft and FoldLeft Implementations of ReduceLeft and FoldLeft
========================================== ==========================================
...@@ -73,13 +76,12 @@ Implementations of ReduceLeft and FoldLeft ...@@ -73,13 +76,12 @@ Implementations of ReduceLeft and FoldLeft
abstract class List[T] abstract class List[T]
def reduceLeft(op: (T, T) => T): T = this match def reduceLeft(op: (T, T) => T): T = this match
case Nil => throw new Error("Nil.reduceLeft") case Nil => throw Error("Nil.reduceLeft")
case x :: xs => (xs foldLeft x)(op) case x :: xs => xs.foldLeft(x)(op)
def foldLeft[U](z: U)(op: (U, T) => U): U = this match def foldLeft[U](z: U)(op: (U, T) => U): U = this match
case Nil => z case Nil => z
case x :: xs => (xs foldLeft op(z, x))(op) case x :: xs => xs.foldLeft(op(z, x))(op)
FoldRight and ReduceRight FoldRight and ReduceRight
========================= =========================
...@@ -89,8 +91,13 @@ Applications of `foldLeft` and `reduceLeft` unfold on trees that lean to the lef ...@@ -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 They have two dual functions, `foldRight` and `reduceRight`, which
produce trees which lean to the right, i.e., 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, ..., x{n-1}, xn).reduceRight(op) = x1.op(x2.op( ... x{n-1}.op(xn) ... ))
(List(x1, ..., xn) foldRight acc)(op) = x1 op ( ... (xn op acc) ... ) (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 Implementation of FoldRight and ReduceRight
=========================================== ===========================================
...@@ -98,12 +105,12 @@ Implementation of FoldRight and ReduceRight ...@@ -98,12 +105,12 @@ Implementation of FoldRight and ReduceRight
They are defined as follows They are defined as follows
def reduceRight(op: (T, T) => T): T = this match 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 :: 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 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)) case x :: xs => op(x, xs.foldRight(z)(op))
Difference between FoldLeft and FoldRight Difference between FoldLeft and FoldRight
...@@ -119,7 +126,7 @@ Exercise ...@@ -119,7 +126,7 @@ Exercise
Here is another formulation of `concat`: Here is another formulation of `concat`:
def concat[T](xs: List[T], ys: List[T]): List[T] = 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? 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? ...@@ -127,6 +134,20 @@ Here, it isn't possible to replace `foldRight` by `foldLeft`. Why?
O The resulting function would not terminate O The resulting function would not terminate
O The result would be reversed 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 Back to Reversing Lists
======================= =======================
...@@ -151,11 +172,11 @@ We know `reverse(Nil) == Nil`, so we can compute as follows: ...@@ -151,11 +172,11 @@ We know `reverse(Nil) == Nil`, so we can compute as follows:
-> ->
= reverse(Nil) = reverse(Nil)
-> ->
= (Nil foldLeft z?)(op) = Nil.foldLeft(z?)(op)
-> ->
= z? = z?
Consequently, `z? = List()` Consequently, `z? = Nil`
Deduction of Reverse (2) Deduction of Reverse (2)
======================== ========================
...@@ -167,11 +188,11 @@ list after `Nil` into our equation for `reverse`: ...@@ -167,11 +188,11 @@ list after `Nil` into our equation for `reverse`:
-> ->
= reverse(List(x)) = reverse(List(x))
-> ->
= (List(x) foldLeft Nil)(op?) = List(x).foldLeft(Nil)(op?)
-> ->
= op?(Nil, x) = 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. This suggests to take for `op?` the operator `::` but with its operands swapped.
...@@ -181,12 +202,26 @@ Deduction of Reverse(3) ...@@ -181,12 +202,26 @@ Deduction of Reverse(3)
We thus arrive at the following implementation of `reverse`. We thus arrive at the following implementation of `reverse`.
def reverse[a](xs: List[T]): List[T] = 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. 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 Exercise
======== ========
...@@ -194,8 +229,8 @@ 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`: 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] = 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 = def lengthFun[T](xs: List[T]): Int =
(xs foldRight 0)( ??? ) xs.foldRight(0)((y, n) => n + 1)
...@@ -94,9 +94,9 @@ Prove the following distribution law for `map` over concatenation. ...@@ -94,9 +94,9 @@ Prove the following distribution law for `map` over concatenation.
For any lists `xs`, `ys`, function `f`: 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`: You will need the clauses of ++ as well as the following clauses for `map`:
Nil map f = Nil Nil.map(f) = Nil
(x :: xs) map f = f(x) :: (xs map f) (x :: xs).map(f) = f(x) :: xs.map(f)
% 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"
...@@ -35,13 +35,19 @@ Instead of `x :: xs`, there is ...@@ -35,13 +35,19 @@ Instead of `x :: xs`, there is
Collection Hierarchy Collection Hierarchy
==================== ====================
A common base class of `List` and `Vector` is `Seq`, the class of all _sequences_. A common base class of `List` and `Vector` is `Seq`, the class of all _sequences_.
`Seq` itself is a subclass of `Iterable`.
`Seq` itself is a subclass of `Iterable`.
~~~
Iterable
/ | \
/ | \
............Seq Set Map
. / \
. / \
Array List Vector
~~~
Arrays and Strings Arrays and Strings
================== ==================
...@@ -52,10 +58,10 @@ implicitly be converted to sequences where needed. ...@@ -52,10 +58,10 @@ implicitly be converted to sequences where needed.
(They cannot be subclasses of `Seq` because they come from Java) (They cannot be subclasses of `Seq` because they come from Java)
val xs: Array[Int] = Array(1, 2, 3) val xs: Array[Int] = Array(1, 2, 3)
xs map (x => 2 * x) xs.map(x => 2 * x)
val ys: String = "Hello world!" val ys: String = "Hello world!"
ys filter (_.isUpper) ys.filter(_.isUpper)
Ranges Ranges
====== ======
...@@ -74,22 +80,22 @@ determine step value): ...@@ -74,22 +80,22 @@ determine step value):
1 to 10 by 3 1 to 10 by 3
6 to 1 by -2 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. lower bound, upper bound, step value.
Some more Sequence Operations: Some more Sequence Operations:
============================== ==============================
\begin{tabular}{lp{8.7cm}} \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.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.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.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.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.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.sum ` & The sum of all elements of this numeric collection.
\\ \verb` xs.product ` & The product 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.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.min ` & The minimum of all elements of this collection
\end{tabular} \end{tabular}
Example: Combinations Example: Combinations
...@@ -97,14 +103,14 @@ 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`: 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 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`: 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 Example: Scalar Product
======================= =======================
...@@ -112,21 +118,36 @@ Example: Scalar Product ...@@ -112,21 +118,36 @@ Example: Scalar Product
To compute the scalar product of two vectors: To compute the scalar product of two vectors:
def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = 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}. An alternative way to write this is with a \red{pattern matching function value}.
def scalarProduct(xs: Vector[Double], ys: Vector[Double]): Double = 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 Generally, the function value
{ case p1 => e1 ... case pn => en } { case p1 => e1 ... case pn => en }
is equivalent to is equivalent to
x => x match { case p1 => e1 ... case pn => en } 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: Exercise:
========= =========
...@@ -146,7 +167,8 @@ A number `n` is \red{prime} if the only divisors of `n` are `1` and `n` itself. ...@@ -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 What is a high-level way to write a test for primality of numbers? For
once, value conciseness over efficiency. 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 \quiz
......
...@@ -32,8 +32,8 @@ One natural way to generate the sequence of pairs is to: ...@@ -32,8 +32,8 @@ One natural way to generate the sequence of pairs is to:
This can be achieved by combining `until` and `map`: This can be achieved by combining `until` and `map`:
(1 until n) map (i => (1 until n).map(i =>
(1 until i) map (j => (i, j))) (1 until i).map(j => (i, j)))
Generate Pairs Generate Pairs
...@@ -43,7 +43,7 @@ The previous step gave a sequence of sequences, let's call it `xss`. ...@@ -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 `++`: 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` Or, equivalently, we use the built-in method `flatten`
...@@ -51,20 +51,20 @@ Or, equivalently, we use the built-in method `flatten` ...@@ -51,20 +51,20 @@ Or, equivalently, we use the built-in method `flatten`
This gives: This gives:
((1 until n) map (i => ((1 until n).map(i =>
(1 until i) map (j => (i, j)))).flatten (1 until i)map(j => (i, j)))).flatten
Generate Pairs (2) Generate Pairs (2)
================== ==================
Here's a useful law: 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 Hence, the above expression can be simplified to
(1 until n) flatMap (i => (1 until n).flatMap(i =>
(1 until i) map (j => (i, j))) (1 until i).map(j => (i, j)))
Assembling the pieces Assembling the pieces
...@@ -72,9 +72,9 @@ Assembling the pieces ...@@ -72,9 +72,9 @@ Assembling the pieces
By reassembling the pieces, we obtain the following expression: By reassembling the pieces, we obtain the following expression:
(1 until n) flatMap (i => (1 until n)
(1 until i) map (j => (i, j))) filter ( pair => .flatMap(i => (1 until i).map(j => (i, j)))
isPrime(pair._1 + pair._2)) .filter((x, y) => isPrime(x + y))
This works, but makes most people's head hurt. 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 ...@@ -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: 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: 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 The for-expression is similar to loops in imperative languages, except
that it builds a list of the results of all iterations. that it builds a list of the results of all iterations.
...@@ -113,7 +116,7 @@ Syntax of For ...@@ -113,7 +116,7 @@ Syntax of For
A for-expression is of the form 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}, where `s` is a sequence of \red{generators} and \red{filters},
and `e` is an expression whose value is returned by an iteration. 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. ...@@ -124,10 +127,6 @@ and `e` is an expression whose value is returned by an iteration.
- The sequence must start with a generator. - The sequence must start with a generator.
- If there are several generators in the sequence, the last generators vary faster than the first. - 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 Use of For
========== ==========
...@@ -151,5 +150,5 @@ a `for`: ...@@ -151,5 +150,5 @@ a `for`:
def scalarProduct(xs: List[Double], ys: List[Double]) : Double = 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 \quiz
...@@ -63,14 +63,13 @@ Implementation ...@@ -63,14 +63,13 @@ Implementation
def queens(n: Int) = def queens(n: Int) =
def placeQueens(k: Int): Set[List[Int]] = def placeQueens(k: Int): Set[List[Int]] =
if k == 0 Set(List()) if k == 0 then Set(List())
else else
for for
queens <- placeQueens(k - 1) queens <- placeQueens(k - 1)
col <- 0 until n col <- 0 until n
if isSafe(col, queens) if isSafe(col, queens)
yield yield col :: queens
col :: queens
placeQueens(n) placeQueens(n)
Exercise Exercise
...@@ -85,3 +84,4 @@ which tests if a queen placed in an indicated column `col` is secure amongst the ...@@ -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`). 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 \quiz
...@@ -23,7 +23,7 @@ Class `Map[Key, Value]` extends the collection type \newline ...@@ -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: Therefore, maps support the same collection operations as other iterables do. Example:
val countryOfCapital = capitalOfCountry.map((x, y) => (y, x)) 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_. Note that maps extend iterables of key/value _pairs_.
...@@ -50,8 +50,8 @@ Applying a map to a non-existing key gives an error: ...@@ -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: 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("US") // Some("Washington")
capitalOfCountry get "Andorra" // None capitalOfCountry.get("Andorra") // None
The result of a `get` operation is an `Option` value. The result of a `get` operation is an `Option` value.
...@@ -64,7 +64,7 @@ The `Option` type is defined as: ...@@ -64,7 +64,7 @@ The `Option` type is defined as:
case class Some[+A](value: A) extends Option[A] case class Some[+A](value: A) extends Option[A]
object None extends Option[Nothing] 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`, - `None` \gap if `map` does not contain the given `key`,
- `Some(x)` \ \ if `map` associates the given `key` with the value `x`. - `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. ...@@ -86,6 +86,26 @@ Options also support quite a few operations of the other collections.
I invite you to try them out! 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 Sorted and GroupBy
================== ==================
...@@ -95,17 +115,19 @@ Two useful operation of SQL queries in addition to for-expressions are ...@@ -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`. `orderBy` on a collection can be expressed by `sortWith` and `sorted`.
val fruit = List("apple", "pear", "orange", "pineapple") val fruit = List("apple", "pear", "orange", "pineapple")
fruit sortWith (_.length < _.length) // List("pear", "apple", "orange", "pineapple") fruit.sortWith(_.length < _.length) // List("pear", "apple", "orange", "pineapple")
fruit.sorted // List("apple", "orange", "pear", "pineapple") fruit.sorted // List("apple", "orange", "pear", "pineapple")
`groupBy` is available on Scala collections. It partitions a collection `groupBy` is available on Scala collections. It partitions a collection
into a map of collections according to a _discriminator function_ `f`. into a map of collections according to a _discriminator function_ `f`.
\example: \example:
fruit groupBy (_.head) //> Map(p -> List(pear, pineapple), fruit.groupBy(_.head) //> Map(p -> List(pear, pineapple),
//| a -> List(apple), //| a -> List(apple),
//| o -> List(orange)) //| o -> List(orange))
<!---
Map Example Map Example
=========== ===========
...@@ -127,7 +149,7 @@ in the map. ...@@ -127,7 +149,7 @@ in the map.
There is an operation `withDefaultValue` that turns a map into a There is an operation `withDefaultValue` that turns a map into a
total function: total function:
val cap1 = capitalOfCountry withDefaultValue "<unknown>" val cap1 = capitalOfCountry.withDefaultValue("<unknown>")
cap1("Andorra") // "<unknown>" cap1("Andorra") // "<unknown>"
Variable Length Argument Lists Variable Length Argument Lists
...@@ -144,7 +166,7 @@ Problem: The number of `key -> value` pairs passed to `Map` can vary. ...@@ -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}: We can accommodate this pattern using a \red{repeated parameter}:
def Polynom(bindings: (Int, Double)*) = 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) Polynom(1 -> 2.0, 3 -> 4.0, 5 -> 6.2)
...@@ -154,31 +176,30 @@ Inside the `Polynom` function, `bindings` is seen as a ...@@ -154,31 +176,30 @@ Inside the `Polynom` function, `bindings` is seen as a
Final Implementation of Polynom Final Implementation of Polynom
=============================== ===============================
class Poly(terms0: Map[Int, Double]) class Polynom(terms0: Map[Int, Double])
def this(bindings: (Int, Double)*) = this(bindings.toMap) def this(bindings: (Int, Double)*) = this(bindings.toMap)
val terms = terms0 withDefaultValue 0.0 val terms = terms0.withDefaultValue(0.0)
def + (other: Polynom) = Polynom(terms ++ other.terms.map(adjust))
def + (other: Poly) = new Poly(terms ++ (other.terms map adjust)) def adjust(term: (Int, Double)): (Int, Double) = {
def adjust(term: (Int, Double)): (Int, Double) =
val (exp, coeff) = term val (exp, coeff) = term
exp -> (coeff + terms(exp)) exp -> (coeff + terms(exp))
}
override def toString = override def toString =
val termStrings = val termStrings =
for (exp, coeff) <- terms.toList.sorted.reverse for (exp, coeff) <- terms.toList.sorted.reverse
yield s"${coeff}x^$exp" yield s"${coeff}x^$exp"
termStrings.mkString(" + ") termStrings.mkString(" + ")
Exercise 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`: Design another version of `+` in terms of `foldLeft`:
def + (other: Poly) = def + (other: Polynom) =
new Poly((other.terms foldLeft ???)(addTerm) Polynom(other.terms.foldLeft(???)(addTerm))
def addTerm(terms: Map[Int, Double], term: (Int, Double)) = def addTerm(terms: Map[Int, Double], term: (Int, Double)) =
??? ???
...@@ -192,3 +213,23 @@ Which of the two versions do you believe is more efficient? ...@@ -192,3 +213,23 @@ Which of the two versions do you believe is more efficient?
\quiz \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
->
-->
...@@ -7,38 +7,244 @@ Task ...@@ -7,38 +7,244 @@ Task
Phone keys have mnemonics assigned to them. Phone keys have mnemonics assigned to them.
val mnemonics = Map( 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") '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. that can serve as mnemonics for the phone number.
\example: The phone number "7225247386" should have the mnemonic \example: The phone number "7225247386" should have the mnemonic
`Scala is fun` as one element of the set of solution phrases. `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 Background
========== ==========
This example was taken from: This example was taken from:
\begin{quote} \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} \end{quote}
Tested with Tcl, Python, Perl, Rexx, Java, C++, C. Tested with Tcl, Python, Perl, Rexx, Java, C++, C.
Code size medians: Code size medians:
- 100 loc for scripting languages - 100 loc for scripting languages
- 200-300 loc for the others - 200-300 loc for the others
The Future? Benefits
=========== ========
Scala's immutable collections are: Scala's immutable collections are:
...@@ -48,4 +254,4 @@ Scala's immutable collections are: ...@@ -48,4 +254,4 @@ Scala's immutable collections are:
- _fast_: collection ops are tuned, can be parallelized. - _fast_: collection ops are tuned, can be parallelized.
- _universal_: one vocabulary to work on all kinds of collections. - _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
...@@ -8,15 +8,15 @@ object ListSpecification extends Properties("List") { ...@@ -8,15 +8,15 @@ object ListSpecification extends Properties("List") {
property("double reverse") = forAll { (lst: List[Int]) => property("double reverse") = forAll { (lst: List[Int]) =>
lst.reverse.reverse == lst lst.reverse.reverse == lst
} }
// property("zip reverse") = forAll { (a: List[Int], b: List[Int]) => // 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]) => // property("zip reverse") = forAll { (a: List[Int], b: List[Int]) =>
// val a1 = a.take(b.length) // val a1 = a.take(b.length)
// val b1 = b.take(a.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
...@@ -2,7 +2,7 @@ package week6 ...@@ -2,7 +2,7 @@ package week6
object maps { object maps {
val romanNumerals = Map('I' -> 1, 'V' -> 5, 'X' -> 10) 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) //| 5, X -> 10)
val capitalOfCountry = Map("US" -> "Washington", "Switzerland" -> "Bern") val capitalOfCountry = Map("US" -> "Washington", "Switzerland" -> "Bern")
//> capitalOfCountry : scala.collection.immutable.Map[String,String] = Map(US - //> capitalOfCountry : scala.collection.immutable.Map[String,String] = Map(US -
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment