progs/lecture4.scala
changeset 481 e03a0100ec46
parent 455 557d18cce0f0
--- 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.