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

Updates to slides

parent b66436a4
No related branches found
No related tags found
No related merge requests found
Showing with 168 additions and 232 deletions
......@@ -143,11 +143,9 @@ Exercise
The `sum` function uses linear recursion. Write a tail-recursive version by replacing the `???`s.
def sum(f: Int => Int, a: Int, b: Int): Int = {
def loop(a: Int, acc: Int): Int = {
def sum(f: Int => Int, a: Int, b: Int): Int =
def loop(a: Int, acc: Int): Int =
if ??? then ???
else loop(???, ???)
}
loop(???, ???)
}
......@@ -23,12 +23,11 @@ Functions Returning Functions
Let's rewrite `sum` as follows.
def sum(f: Int => Int): (Int, Int) => Int = {
def sum(f: Int => Int): (Int, Int) => Int =
def sumF(a: Int, b: Int): Int =
if a > b then 0
else f(a) + sumF(a + 1, b)
sumF
}
`sum` is now a function that returns another function.
......@@ -86,19 +85,19 @@ Expansion of Multiple Parameter Lists
In general, a definition of a function with multiple parameter lists
$$\btt
def\ f (args_1) ... (args_n) = E
def\ f (ps_1) ... (ps_n) = E
$$
where $\btt n > 1$, is equivalent to
$$\btt
def\ f (args_1) ... (args_{n-1}) = \{ def\ g (args_n) = E ; g \}
def\ f (ps_1) ... (ps_{n-1}) = \{ def\ g (ps_n) = E ; g \}
$$
where `g` is a fresh identifier.
Or for short:
$$\btt
def\ f (args_1) ... (args_{n-1}) = ( args_n \Rightarrow E )
def\ f (ps_1) ... (ps_{n-1}) = ( ps_n \Rightarrow E )
$$
Expansion of Multiple Parameter Lists (2)
......@@ -106,11 +105,11 @@ Expansion of Multiple Parameter Lists (2)
By repeating the process $n$ times
$$\btt
def\ f (args_1) ... (args_{n-1}) (args_n) = E
def\ f (ps_1) ... (ps_{n-1}) (ps_n) = E
$$
is shown to be equivalent to
$$\btt
def\ f = (args_1 \Rightarrow ( args_2 \Rightarrow ... ( args_n \Rightarrow E ) ... ))
def\ f = (ps_1 \Rightarrow ( ps_2 \Rightarrow ... ( ps_n \Rightarrow E ) ... ))
$$
This style of definition and function application is called _currying_,
......
......@@ -22,16 +22,16 @@ Programmatic Solution
This leads to the following function for finding a fixed point:
val tolerance = 0.0001
def isCloseEnough(x: Double, y: Double) =
abs((x - y) / x) / x < tolerance
def fixedPoint(f: Double => Double)(firstGuess: Double) = {
def iterate(guess: Double): Double = {
def fixedPoint(f: Double => Double)(firstGuess: Double) =
def iterate(guess: Double): Double =
val next = f(guess)
if isCloseEnough(guess, next) then next
else iterate(next)
}
iterate(firstGuess)
}
Return to Square Roots
======================
......
......@@ -47,7 +47,7 @@ This definition introduces two entities:
- A \red{constructor} `Rational` to create elements of this type.
Scala keeps the names of types and values in \red{different namespaces}.
So there's no conflict between the two definitions of `Rational`.
So there's no conflict between the two entities named `Rational`.
Objects
=======
......@@ -113,6 +113,9 @@ Implementing Rational Arithmetic
\verb@makeString(addRational(Rational(1, 2), Rational(2, 3)))@ \wsf 7/6
\end{worksheet}
\note\ `s"..."` in `makeString` is an _interpolated string_, with values `r.numer`
and `r.denom` in the places enclosed by `${...}`.
Methods
=======
......@@ -133,7 +136,7 @@ Methods for Rationals
Here's a possible implementation:
class Rational(x: Int, y: Int) {
class Rational(x: Int, y: Int)
def numer = x
def denom = y
def add(r: Rational) =
......
% More Fun with Rationals
% \
% \
% Data Abstraction
%
%
Data Abstraction
================
......@@ -21,13 +21,13 @@ the class when the objects are constructed:
Rationals with Data Abstraction
===============================
class Rational(x: Int, y: Int) {
private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
class Rational(x: Int, y: Int)
private def gcd(a: Int, b: Int): Int =
if b == 0 then a else gcd(b, a % b)
private val g = gcd(x, y)
def numer = x / g
def denom = y / g
...
}
`gcd` and `g` are \red{private} members; we can only access them
from inside the `Rational` class.
......@@ -41,11 +41,11 @@ Rationals with Data Abstraction (2)
It is also possible to call `gcd` in the code of
`numer` and `denom`:
class Rational(x: Int, y: Int) {
private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
class Rational(x: Int, y: Int)
private def gcd(a: Int, b: Int): Int =
if b == 0 then a else gcd(b, a % b)
def numer = x / gcd(x, y)
def denom = y / gcd(x, y)
}
This can be advantageous if it is expected that the functions `numer`
and `denom` are called infrequently.
......@@ -55,11 +55,11 @@ Rationals with Data Abstraction (3)
It is equally possible to turn `numer` and `denom` into `val`s, so that they are computed only once:
class Rational(x: Int, y: Int) {
private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
class Rational(x: Int, y: Int)
private def gcd(a: Int, b: Int): Int =
if b == 0 then a else gcd(b, a % b)
val numer = x / gcd(x, y)
val denom = y / gcd(x, y)
}
This can be advantageous if the functions `numer` and `denom` are called often.
......@@ -83,14 +83,13 @@ on which the current method is executed.
Add the functions `less` and `max` to the class `Rational`.
class Rational(x: Int, y: Int) {
class Rational(x: Int, y: Int)
...
def less(that: Rational) =
def less(that: Rational): Rational =
numer * that.denom < that.numer * denom
def max(that: Rational) =
def max(that: Rational): Rational =
if this.less(that) then that else this
}
Self Reference (2)
==================
......@@ -99,7 +98,7 @@ Note that a simple name `x`, which refers to another member
of the class, is an abbreviation of `this.x`. Thus, an equivalent
way to formulate `less` is as follows.
def less(that: Rational) =
def less(that: Rational): Rational =
this.numer * that.denom < that.numer * this.denom
Preconditions
......@@ -109,10 +108,9 @@ Let's say our `Rational` class requires that the denominator is positive.
We can enforce this by calling the `require` function.
class Rational(x: Int, y: Int) {
class Rational(x: Int, y: Int)
require(y > 0, "denominator must be positive")
...
}
`require` is a predefined function.
......@@ -156,15 +154,14 @@ Scala also allows the declaration of \red{auxiliary constructors}.
These are methods named `this`
\example Adding an auxiliary constructor to the class `Rational`.
\example\ Adding an auxiliary constructor to the class `Rational`.
class Rational(x: Int, y: Int) {
class Rational(x: Int, y: Int)
def this(x: Int) = this(x, 1)
...
}
\begin{worksheet}
new Rational(2) \wsf 2/1
Rational(2) \wsf 2/1
\end{worksheet}
......
......@@ -10,12 +10,12 @@ a computation model based on substitution. Now we extend this
model to classes and objects.
\red{Question:} How is an instantiation of the class
$\btt new\ C(e_1, ..., e_m)$ evaluted?
$\btt C(e_1, ..., e_m)$ evaluted?
\red{Answer:} The expression arguments $\btt e_1, ..., e_m$
are evaluated like the arguments of a normal function. That's it.
The resulting expression, say, $\btt new\ C(v_1, ..., v_m)$, is
The resulting expression, say, $\btt C(v_1, ..., v_m)$, is
already a value.
Classes and Substitutions
......@@ -37,17 +37,17 @@ have omitted the parameter types.)
\red{Question:} How is the following expression evaluated?
$$\btt
new\ C(v_1, ..., v_m).f(w_1, ..., w_n)
C(v_1, ..., v_m).f(w_1, ..., w_n)
$$
Classes and Substitutions (2)
=============================
\red{Answer:} The expression $\btt new\ C(v_1, ..., v_m).f(w_1, ..., w_n)$ is rewritten to:
\red{Answer:} The expression $\btt C(v_1, ..., v_m).f(w_1, ..., w_n)$ is rewritten to:
$$\btt
[w_1/y_1, ..., w_n/y_n]
[v_1/x_1, ..., v_m/x_m]
[new\ C(v_1, ..., v_m)/this]\,b
[C(v_1, ..., v_m)/this]\,b
$$
There are three substitutions at work here:
......@@ -55,27 +55,27 @@ There are three substitutions at work here:
arguments $\btt w_1, ..., w_n$,
- the substitution of the formal parameters $\btt x_1, ..., x_m$ of the class $\btt C$ by the class arguments
$\btt v_1, ..., v_m$,
- the substitution of the self reference $this$ by the value of the object $\btt new\ C(v_1, ..., v_n)$.
- the substitution of the self reference $this$ by the value of the object $\btt C(v_1, ..., v_n)$.
Object Rewriting Examples
=========================
` new Rational(1, 2).numer`
` Rational(1, 2).numer`
->
$\rightarrow$ $\btt [1/x, 2/y]\ []\ [new\ Rational(1, 2)/this]$ `x`
$\rightarrow$ $\btt [1/x, 2/y]\ []\ [Rational(1, 2)/this]$ `x`
->
=$\,$ `1`
\medskip
->
` new Rational(1, 2).less(new Rational(2, 3))`
` Rational(1, 2).less(Rational(2, 3))`
->
$\rightarrow$ $\btt [1/x, 2/y]\ [new Rational(2, 3)/that]\ [new\ Rational(1, 2)/this]$
$\rightarrow$ $\btt [1/x, 2/y]\ [Rational(2, 3)/that]\ [Rational(1, 2)/this]$
\nl\gap `this.numer * that.denom < that.numer * this.denom`
->
=$\,$ `new Rational(1, 2).numer * new Rational(2, 3).denom <`
=$\,$ `Rational(1, 2).numer * Rational(2, 3).denom <`
\nl\gap `new Rational(2, 3).numer * new Rational(1, 2).denom`
\nl\gap `Rational(2, 3).numer * Rational(1, 2).denom`
->
$\rightarrow\dhd$ `1 * 3 < 2 * 2`
......@@ -96,21 +96,10 @@ difference:
In Scala, we can eliminate this difference. We proceed in two steps.
Step 1: Infix Notation
======================
Any method with a parameter can be used like an infix operator.
It is therefore possible to write
r add s r.add(s)
r less s /* in place of */ r.less(s)
r max s r.max(s)
Step 2: Relaxed Identifiers
Step 1: Relaxed Identifiers
===========================
Operators can be used as identifiers.
Operators such as `+` or `<` count as identifiers in Scala.
Thus, an identifier can be:
......@@ -124,35 +113,60 @@ Examples of identifiers:
x1 * +?%& vector_++ counter_=
Step 1: Relaxed Identifiers
===========================
Since operators are identifiers, it is possible to use them as method names. E.g.
class Rational
def + (x: Rational): Rational = ...
def < (x: Rational): Rational = ...
Step 2: Infix Notation
======================
An operator method with a single parameter can be used as an infix operator.
A normal method with a single parameter can also be used as an infix operator if it
is declared @infix. E.g.
class Rational
@infix def max(that Rational): Rational = ...
It is therefore possible to write
r + s r.+(s)
r < s /* in place of */ r.<(s)
r max s r.max(s)
Operators for Rationals
=======================
A more natural definition of class `Rational`:
class Rational(x: Int, y: Int) {
private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a % b)
class Rational(x: Int, y: Int)
private def gcd(a: Int, b: Int): Int =
if b == 0 then a else gcd(b, a % b)
private val g = gcd(x, y)
def numer = x / g
def denom = y / g
def + (r: Rational) =
new Rational(
numer * r.denom + r.numer * denom,
denom * r.denom)
def - (r: Rational) = ...
def * (r: Rational) = ...
def + (r: Rational) = Rational(
numer * r.denom + r.numer * denom,
denom * r.denom)
def - (r: Rational): Rational = ...
def * (r: Rational): Rational = ...
...
}
Operators for Rationals
=======================
... and rational numbers can be used like `Int` or `Double`:
This allows rational numbers to be used like `Int` or `Double`:
val x = new Rational(1, 2)
val y = new Rational(1, 3)
val x = Rational(1, 2)
val y = Rational(1, 3)
x * x + y * y
Precedence Rules
......@@ -176,7 +190,7 @@ The following table lists the characters in increasing order of priority precede
Exercise
========
Provide a fully parenthized version of
Provide a fully parenthesized version of
a + b ^? c ?^ d less a ==> b | c
......
......@@ -12,7 +12,8 @@ at the top of your source file.
package progfun.examples
object Hello { ... }
object Hello
...
This would place `Hello` in the package `progfun.examples`.
......@@ -91,11 +92,11 @@ Here, you could use `trait`s.
A trait is declared like an abstract class, just with `trait` instead of
`abstract class`.
trait Planar {
trait Planar
def height: Int
def width: Int
def surface = height * width
}
Traits (2)
==========
......@@ -105,7 +106,7 @@ arbitrary many traits.
Example:
class Square extends Shape with Planar with Movable ...
class Square extends Shape, Planar, Movable ...
Traits resemble interfaces in Java, but are more powerful because they can contains fields and concrete methods.
......
......@@ -90,9 +90,8 @@ Exercise
In package `week4`, define an
object List {
object List
...
}
with 3 functions in it so that users can create lists of lengths 0-2 using syntax
......
......@@ -149,20 +149,18 @@ Suppose we want to sort a list of numbers in ascending order:
This idea describes \red{Insertion Sort} :
def isort(xs: List[Int]): List[Int] = xs match {
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 {
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`?
......@@ -178,10 +176,9 @@ 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 {
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`?
......
......@@ -49,11 +49,10 @@ 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 {
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`
========================
......@@ -64,11 +63,10 @@ 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 {
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`
========================
......@@ -79,11 +77,10 @@ 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 {
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`.
......@@ -92,11 +89,10 @@ Exercise
Implement `init` as an external function, analogous to `last`.
def init[T](xs: List[T]): List[T] = xs match {
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 => ???
}
\quiz
......@@ -105,11 +101,10 @@ Exercise
Implement `init` as an external function, analogous to `last`.
def init[T](xs: List[T]): List[T] = xs match {
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 =>
}
\quiz
......@@ -129,10 +124,9 @@ 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 {
def concat[T](xs: List[T], ys: List[T]) = xs match
case List() =>
case z :: zs =>
}
Implementation of Concatenation
===============================
......@@ -141,10 +135,9 @@ 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 {
def concat[T](xs: List[T], ys: List[T]) = xs match
case List() => ys
case z :: zs =>
}
Implementation of Concatenation
===============================
......@@ -153,10 +146,9 @@ 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 {
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`?
......@@ -169,10 +161,9 @@ How can reverse be implemented?
Let's try by writing a stand-alone function:
def reverse[T](xs: List[T]): List[T] = xs match {
def reverse[T](xs: List[T]): List[T] = xs match
case List() =>
case y :: ys =>
}
Implementation of `reverse`
===========================
......@@ -181,10 +172,9 @@ How can reverse be implemented?
Let's try by writing a stand-alone function:
def reverse[T](xs: List[T]): List[T] = xs match {
def reverse[T](xs: List[T]): List[T] = xs match
case List() => List()
case y :: ys =>
}
......@@ -195,10 +185,9 @@ How can reverse be implemented?
Let's try by writing a stand-alone function:
def reverse[T](xs: List[T]): List[T] = xs match {
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`?
......
......@@ -24,15 +24,13 @@ First MergeSort Implementation
Here is the implementation of that algorithm in Scala:
def msort(xs: List[Int]): List[Int] = {
val n = xs.length/2
def msort(xs: List[Int]): List[Int] =
val n = xs.length / 2
if n == 0 then xs
else {
else
def merge(xs: List[Int], ys: List[Int]) = ???
val (fst, snd) = xs.splitAt(n)
merge(msort(fst), msort(snd))
}
}
Definition of Merge
===================
......@@ -40,18 +38,17 @@ Definition of Merge
Here is a definition of the `merge` function:
def merge(xs: List[Int], ys: List[Int]) =
xs match {
xs match
case Nil =>
ys
case x :: xs1 =>
ys match {
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
====================
......@@ -86,52 +83,6 @@ Pairs can also be used as patterns:
This works analogously for tuples with more than two elements.
Translation of Tuples
=====================
A 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)
$$
The Tuple class
===============
Here, all `Tuple`\mbox{\it n} classes are modeled after the following pattern:
case class Tuple2[T1, T2](_1: +T1, _2: +T2) {
override def toString = "(" + _1 + "," + _2 +")"
}
The fields of a tuple can be accessed with names `_1`, `_2`, ...
So instead of the pattern binding
val (label, value) = pair
one could also have written:
val label = pair._1
val value = pair._2
But the pattern matching form is generally preferred.
Exercise
========
......@@ -142,6 +93,5 @@ 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 {
(xs, ys) match
???
}
......@@ -22,19 +22,17 @@ 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) = {
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 {
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
==========================
......
......@@ -26,10 +26,10 @@ return the list of results.
For example, to multiply each element of a list by the same factor, you could write:
def scaleList(xs: List[Double], factor: Double): List[Double] = xs match {
def scaleList(xs: List[Double], factor: Double): List[Double] = xs match
case Nil => xs
case y :: ys => y * factor :: scaleList(ys, factor)
}
Map
===
......@@ -39,10 +39,9 @@ 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] { ...
def map[U](f: T => U): List[U] = this match {
def map[U](f: T => U): List[U] = this match
case Nil => this
case x :: xs => f(x) :: xs.map(f)
}
(in fact, the actual definition of `map` is a bit more complicated,
because it is tail-recursive, and also because it works for arbitrary
......@@ -61,13 +60,11 @@ return the result. Complete the two following equivalent definitions of
`squareList`.
def squareList(xs: List[Int]): List[Int] = xs match {
def squareList(xs: List[Int]): List[Int] = xs match
case Nil => ???
case y :: ys => ???
}
def squareList(xs: List[Int]): List[Int] =
xs map ???
xs.map(???)
Exercise
========
......@@ -77,13 +74,12 @@ return the result. Complete the two following equivalent definitions of
`squareList`.
def squareList(xs: List[Int]): List[Int] = xs match {
def squareList(xs: List[Int]): List[Int] = xs match
case Nil =>
case y :: ys =>
}
def squareList(xs: List[Int]): List[Int] =
xs map
xs.map
Filtering
=========
......@@ -91,23 +87,21 @@ Filtering
Another common operation on lists is the selection of all elements
satisfying a given condition. For example:
def posElems(xs: List[Int]): List[Int] = xs match {
def posElems(xs: List[Int]): List[Int] = xs match
case Nil => xs
case y :: ys => if y > 0 then y :: posElems(ys) else posElems(ys)
}
Filter
======
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 x :: xs => if p(x) then x :: xs.filter(p) else xs.filter(p)
}
}
Using `filter`, `posElems` can be written more concisely.
......@@ -141,10 +135,9 @@ should give
You can use the following template:
def pack[T](xs: List[T]): List[List[T]] = xs match {
def pack[T](xs: List[T]): List[List[T]] = xs match
case Nil => Nil
case x :: xs1 => ???
}
Exercise
========
......
......@@ -13,10 +13,9 @@ For example:
We can implement this with the usual recursive schema:
def sum(xs: List[Int]): Int = xs match {
def sum(xs: List[Int]): Int = xs match
case Nil => 0
case y :: ys => y + sum(ys)
}
ReduceLeft
==========
......@@ -71,16 +70,16 @@ Implementations of ReduceLeft and FoldLeft
`foldLeft` and `reduceLeft` can be implemented in class `List` as follows.
abstract class List[T] { ...
def reduceLeft(op: (T, T) => T): T = this match {
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)
}
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 x :: xs => (xs foldLeft op(z, x))(op)
}
}
FoldRight and ReduceRight
=========================
......@@ -98,15 +97,14 @@ Implementation of FoldRight and ReduceRight
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 x :: Nil => x
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 x :: xs => op(x, (xs foldRight z)(op))
}
case x :: xs => op(x, xs.foldRight(z)(op))
Difference between FoldLeft and FoldRight
=========================================
......@@ -121,7 +119,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?
......@@ -136,7 +134,7 @@ We now develop a function for reversing lists which has a linear cost.
The idea is to use the operation `foldLeft`:
def reverse[T](xs: List[T]): List[T] = (xs foldLeft z?)(op?)
def reverse[T](xs: List[T]): List[T] = xs.foldLeft(z?)(op?)
All that remains is to replace the parts `z?` and `op?`.
......@@ -151,9 +149,9 @@ We know `reverse(Nil) == Nil`, so we can compute as follows:
Nil
->
= reverse(Nil)
= reverse(Nil)
->
= (Nil foldLeft z?)(op)
= (Nil foldLeft z?)(op)
->
= z?
......
......@@ -104,10 +104,9 @@ Let's show that, for lists `xs`, `ys`, `zs`:
To do this, use structural induction on `xs`. From the previous implementation of `concat`,
def concat[T](xs: List[T], ys: List[T]) = xs match {
def concat[T](xs: List[T], ys: List[T]) = xs match
case List() => ys
case x :: xs1 => x :: concat(xs1, ys)
}
distill two _defining clauses_ of `++`:
......
......@@ -57,7 +57,7 @@ This gives:
Generate Pairs (2)
==================
Here's a useful law:
Here's a useful law:
xs flatMap f = (xs map f).flatten
......@@ -93,11 +93,11 @@ In this case, Scala's `for` expression notation can help.
For-Expression Example
======================
Let `persons` be a list of elements of class `Person`, with fields `name` and `age`.
Let `persons` be a list of elements of class `Person`, with fields `name` and `age`.
case class Person(name: String, age: Int)
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
......@@ -116,10 +116,10 @@ A for-expression is of the form
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.
and `e` is an expression whose value is returned by an iteration.
- A \red{generator} is of the form `p <- e`,
where `p` is a pattern and `e` an expression whose value is a collection.
- A \red{generator} is of the form `p <- e`,
where `p` is a pattern and `e` an expression whose value is a collection.
- A \red{filter} is of the form `if f` where `f` is a boolean expression.
- The sequence must start with a generator.
- If there are several generators in the sequence, the last generators vary faster than the first.
......@@ -136,11 +136,11 @@ Here are two examples which were previously solved with higher-order functions:
Given a positive integer `n`, find all the pairs of positive integers
`(i, j)` such that `1 <= j < i < n`, and `i + j` is prime.
for {
for
i <- 1 until n
j <- 1 until i
if isPrime(i + j)
} yield (i, j)
yield (i, j)
Exercise
......
......@@ -22,9 +22,8 @@ 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 {
case(x, y) => (y, x)
} // Map("Washington" -> "US", "Bern" -> "Switzerland")
val countryOfCapital = capitalOfCountry.map((x, y) => (y, x))
// Map("Washington" -> "US", "Bern" -> "Switzerland")
Note that maps extend iterables of key/value _pairs_.
......@@ -76,10 +75,9 @@ Decomposing Option
Since options are defined as case classes, they can be decomposed
using pattern matching:
def showCapital(country: String) = capitalOfCountry.get(country) match {
def showCapital(country: String) = capitalOfCountry.get(country) match
case Some(capital) => capital
case None => "missing data"
}
showCapital("US") // "Washington"
showCapital("Andorra") // "missing data"
......@@ -156,19 +154,22 @@ Inside the `Polynom` function, `bindings` is seen as a
Final Implementation of Polynom
===============================
class Poly(terms0: Map[Int, Double]) {
class Poly(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) = {
def adjust(term: (Int, Double)): (Int, Double) =
val (exp, coeff) = term
exp -> (coeff + terms(exp))
}
override def toString =
(for ((exp, coeff) <- terms.toList.sorted.reverse)
yield coeff+"x^"+exp) mkString " + "
}
override def toString =
val termStrings =
for (exp, coeff) <- terms.toList.sorted.reverse
yield s"${coeff}x^$exp"
termStrings.mkString(" + ")
Exercise
========
......
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