--- a/progs/lecture4.scala Mon Nov 06 21:49:55 2023 +0000
+++ b/progs/lecture4.scala Fri Dec 08 00:54:36 2023 +0000
@@ -1,38 +1,227 @@
// Scala Lecture 4
//=================
+// pattern-matching
// tail-recursion
// polymorphic types
-// implicits
+
+
+
+// Pattern Matching
+//==================
+
+// A powerful tool which has even landed in Java during
+// the last few years (https://inside.java/2021/06/13/podcast-017/).
+// ...Scala already has it for many years and the concept is
+// older than your friendly lecturer, that is stone old ;o)
-import scala.annotation.tailrec
+// The general schema:
+//
+// expression match {
+// case pattern1 => expression1
+// case pattern2 => expression2
+// ...
+// case patternN => expressionN
+// }
+
+
+// recall
+def len(xs: List[Int]) : Int = {
+ if (xs == Nil) 0
+ else 1 + len(xs.tail)
+}
-def fact(n: BigInt): BigInt =
- if (n == 0) 1 else n * fact(n - 1)
-
-
-def factT(n: BigInt, acc: BigInt): BigInt =
- if (n == 0) acc else factT(n - 1, n * acc)
+def len(xs: List[Int]) : Int = xs match {
+ case Nil => 0
+ case _::xs => 1 + len(xs)
+}
+
+len(Nil)
+len(List(1,2,3,4))
+
+
+List(1,2,3,4).map(x => x * x)
+
+def my_map_int(lst: List[Int], f: Int => Int) : List[Int] =
+ lst match {
+ case Nil => Nil
+ case foo::xs => f(foo) :: my_map_int(xs, f)
+ }
+
+def my_map_option(opt: Option[Int], f: Int => Int) : Option[Int] =
+ opt match {
+ case None => None
+ case Some(x) => {
+ Some(f(x))
+ }
+ }
+
+my_map_option(None, x => x * x)
+my_map_option(Some(8), x => x * x)
-println(factT(1000000), 1))
+// you can also have cases combined
+def season(month: String) : String = month match {
+ case "March" | "April" | "May" => "It's spring"
+ case "June" | "July" | "August" => "It's summer"
+ case "September" | "October" | "November" => "It's autumn"
+ case "December" => "It's winter"
+ case "January" | "February" => "It's unfortunately winter"
+ case _ => "Wrong month"
+}
+
+// pattern-match on integers
+
+def fib(n: Int) : Int = n match {
+ case 0 | 1 => 1
+ case _ => fib(n - 1) + fib(n - 2)
+}
+
+fib(10)
+
+// pattern-match on results
+
+// Silly: fizz buzz
+def fizz_buzz(n: Int) : String = (n % 3, n % 5) match {
+ case (0, 0) => "fizz buzz"
+ case (0, _) => "fizz"
+ case (_, 0) => "buzz"
+ case _ => n.toString
+}
+
+for (n <- 1 to 20)
+ println(fizz_buzz(n))
+
+// guards in pattern-matching
+
+def foo(xs: List[Int]) : String = xs match {
+ case Nil => s"this list is empty"
+ case x :: xs if x % 2 == 0
+ => s"the first elemnt is even"
+ case x if len(x) ==
+ => s"this list has exactly two elements"
+ case x :: y :: rest if x == y
+ => s"this has two elemnts that are the same"
+ case hd :: tl => s"this list is standard $hd::$tl"
+}
+
+foo(Nil)
+foo(List(1,2,3))
+foo(List(1,1))
+foo(List(1,1,2,3))
+foo(List(2,2,2,3))
+
+
+
+
+abstract class Colour
+case object Red extends Colour
+case object Green extends Colour
+case object Blue extends Colour
+case object Yellow extends Colour
+
+
+def fav_colour(c: Colour) : Boolean = c match {
+ case Green => true
+ case Red => true
+ case _ => false
+}
+
+fav_colour(Blue)
-def foo[A](args: List[A]) = ???
+// ... a tiny bit more useful: Roman Numerals
+
+sealed abstract class RomanDigit
+case object I extends RomanDigit
+case object V extends RomanDigit
+case object X extends RomanDigit
+case object L extends RomanDigit
+case object C extends RomanDigit
+case object D extends RomanDigit
+case object M extends RomanDigit
+
+type RomanNumeral = List[RomanDigit]
+
+List(I, M,C,D,X,X,V,I,I, A)
+
+/*
+I -> 1
+II -> 2
+III -> 3
+IV -> 4
+V -> 5
+VI -> 6
+VII -> 7
+VIII -> 8
+IX -> 9
+X -> 10
+*/
-foo(List("1","2","3","4"))
+def RomanNumeral2Int(rs: RomanNumeral): Int = rs match {
+ case Nil => 0
+ case M::r => 1000 + RomanNumeral2Int(r)
+ case C::M::r => 900 + RomanNumeral2Int(r)
+ case D::r => 500 + RomanNumeral2Int(r)
+ case C::D::r => 400 + RomanNumeral2Int(r)
+ case C::r => 100 + RomanNumeral2Int(r)
+ case X::C::r => 90 + RomanNumeral2Int(r)
+ case L::r => 50 + RomanNumeral2Int(r)
+ case X::L::r => 40 + RomanNumeral2Int(r)
+ case X::r => 10 + RomanNumeral2Int(r)
+ case I::X::r => 9 + RomanNumeral2Int(r)
+ case V::r => 5 + RomanNumeral2Int(r)
+ case I::V::r => 4 + RomanNumeral2Int(r)
+ case I::r => 1 + RomanNumeral2Int(r)
+}
+
+RomanNumeral2Int(List(I,V)) // 4
+RomanNumeral2Int(List(I,I,I,I)) // 4 (invalid Roman number)
+RomanNumeral2Int(List(V,I)) // 6
+RomanNumeral2Int(List(I,X)) // 9
+RomanNumeral2Int(List(M,C,M,L,X,X,I,X)) // 1979
+RomanNumeral2Int(List(M,M,X,V,I,I)) // 2017
+
+abstract class Tree
+case class Leaf(x: Int)
+case class Branch(tl: Tree, tr: Tree)
-// from knight1.scala
-def first(xs: List[Pos], f: Pos => Option[Path]) : Option[Path] = ???
+abstract class Rexp
+case object ZERO extends Rexp // matches nothing
+case object ONE extends Rexp // matches the empty string
+case class CHAR(c: Char) extends Rexp // matches a character c
+case class ALT(r1: Rexp, r2: Rexp) extends Rexp // alternative
+case class SEQ(r1: Rexp, r2: Rexp) extends Rexp // sequence
+case class STAR(r: Rexp) extends Rexp // star
-// should be
-def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ???
+def depth(r: Rexp) : Int = r match {
+ case ZERO => 1
+ case ONE => 1
+ case CHAR(_) => 1
+ case ALT(r1, r2) => 1 + List(depth(r1), depth(r2)).max
+ case SEQ(r1, r2) => 1 + List(depth(r1), depth(r2)).max
+ case STAR(r1) => 1 + depth(r1)
+}
+
+
+// Trees (example of an Algebraic Datatype)
+
+
+abstract class Tree
+case class Leaf(x: Int) extends Tree
+case class Node(s: String, left: Tree, right: Tree) extends Tree
+
+val lf = Leaf(20)
+val tr = Node("foo", Leaf(10), Leaf(23))
+
+val lst : List[Tree] = List(lf, tr)
+
// expressions (essentially trees)
-abstract class Exp
+sealed abstract class Exp
case class N(n: Int) extends Exp // for numbers
case class Plus(e1: Exp, e2: Exp) extends Exp
case class Times(e1: Exp, e2: Exp) extends Exp
@@ -44,6 +233,7 @@
}
val e = Plus(N(9), Times(N(3), N(4)))
+println(e.toString)
println(string(e))
def eval(e: Exp) : Int = e match {
@@ -59,7 +249,7 @@
// e * 0, 0 * e => 0
// e * 1, 1 * e => e
//
-// (....0 ....)
+// (....9 ....)
def simp(e: Exp) : Exp = e match {
case N(n) => N(n)
@@ -83,6 +273,11 @@
println(string(simp(e2)))
+
+
+
+
+
// Tokens and Reverse Polish Notation
abstract class Token
case class T(n: Int) extends Token
@@ -116,6 +311,53 @@
comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil)
+// Tail recursion
+//================
+
+def fact(n: BigInt): BigInt =
+ if (n == 0) 1 else n * fact(n - 1)
+
+
+fact(10)
+fact(1000)
+fact(100000)
+
+
+def factT(n: BigInt, acc: BigInt): BigInt =
+ if (n == 0) acc else factT(n - 1, n * acc)
+
+
+factT(10, 1)
+println(factT(100000, 1))
+
+
+// there is a flag for ensuring a function is tail recursive
+import scala.annotation.tailrec
+
+@tailrec
+def factT(n: BigInt, acc: BigInt): BigInt =
+ if (n == 0) acc else factT(n - 1, n * acc)
+
+factT(100000, 1)
+
+// for tail-recursive functions the Scala compiler
+// generates loop-like code, which does not need
+// to allocate stack-space in each recursive
+// call; Scala can do this only for tail-recursive
+// functions
+
+// Moral: Whenever a recursive function is resource-critical
+// (i.e. works with a large recursion depth), then you need to
+// write it in tail-recursive fashion.
+//
+// Unfortuantely, Scala because of current limitations in
+// the JVM is not as clever as other functional languages. It can
+// only optimise "self-tail calls". This excludes the cases of
+// multiple functions making tail calls to each other. Well,
+// nothing is perfect.
+
+
+
// Polymorphic Types
//===================
@@ -158,9 +400,6 @@
map(List(1, 2, 3, 4), (x: Int) => x.toString)
-// from knight1.scala
-def first(xs: List[Pos], f: Pos => Option[Path]) : Option[Path] = ???
-
// should be
def first[A, B](xs: List[A], f: A => Option[B]) : Option[B] = ???
@@ -278,55 +517,6 @@
// Source.fromFile(name)(encoding)
-// Tail recursion
-//================
-
-def fact(n: Int): Int =
- if (n == 0) 1 else n * fact(n - 1)
-
-
-fact(10)
-fact(1000)
-fact(100000)
-
-def factB(n: BigInt): BigInt =
- if (n == 0) 1 else n * factB(n - 1)
-
-def factT(n: BigInt, acc: BigInt): BigInt =
- if (n == 0) acc else factT(n - 1, n * acc)
-
-
-factB(1000)
-
-
-factT(10, 1)
-println(factT(500000, 1))
-
-
-// there is a flag for ensuring a function is tail recursive
-import scala.annotation.tailrec
-
-@tailrec
-def factT(n: BigInt, acc: BigInt): BigInt =
- if (n == 0) acc else factT(n - 1, n * acc)
-
-factT(100000, 1)
-
-// for tail-recursive functions the Scala compiler
-// generates loop-like code, which does not need
-// to allocate stack-space in each recursive
-// call; Scala can do this only for tail-recursive
-// functions
-
-// Moral: Whenever a recursive function is resource-critical
-// (i.e. works with a large recursion depth), then you need to
-// write it in tail-recursive fashion.
-//
-// Unfortuantely, Scala because of current limitations in
-// the JVM is not as clever as other functional languages. It can
-// only optimise "self-tail calls". This excludes the cases of
-// multiple functions making tail calls to each other. Well,
-// nothing is perfect.