progs/lecture4.scala
author Christian Urban <urbanc@in.tum.de>
Tue, 26 Nov 2019 01:22:36 +0000
changeset 325 ca9c1cf929fa
parent 320 cdfb2ce30a3d
child 326 e5453add7df6
permissions -rw-r--r--
updated

// Scala Lecture 4
//=================


// expressions (essentially trees)

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

def string(e: Exp) : String = e match {
  case N(n) => s"$n"
  case Plus(e1, e2) => s"(${string(e1)} + ${string(e2)})" 
  case Times(e1, e2) => s"(${string(e1)} * ${string(e2)})"
}

val e = Plus(N(9), Times(N(3), N(4)))
println(string(e))

def eval(e: Exp) : Int = e match {
  case N(n) => n
  case Plus(e1, e2) => eval(e1) + eval(e2) 
  case Times(e1, e2) => eval(e1) * eval(e2) 
}

println(eval(e))

// simplification rules:
// e + 0, 0 + e => e 
// e * 0, 0 * e => 0
// e * 1, 1 * e => e

def simp(e: Exp) : Exp = e match {
  case N(n) => N(n)
  case Plus(e1, e2) => (simp(e1), simp(e2)) match {
    case (N(0), e2s) => e2s
    case (e1s, N(0)) => e1s
    case (e1s, e2s) => Plus(e1s, e2s)
  }  
  case Times(e1, e2) => (simp(e1), simp(e2)) match {
    case (N(0), _) => N(0)
    case (_, N(0)) => N(0)
    case (N(1), e2s) => e2s
    case (e1s, N(1)) => e1s
    case (e1s, e2s) => Times(e1s, e2s)
  }  
}


val e2 = Times(Plus(N(0), N(1)), Plus(N(0), N(9)))
println(string(e2))
println(string(simp(e2)))


// Tokens and Reverse Polish Notation
abstract class Token
case class T(n: Int) extends Token
case object PL extends Token
case object TI extends Token

// transfroming an Exp into a list of tokens
def rp(e: Exp) : List[Token] = e match {
  case N(n) => List(T(n))
  case Plus(e1, e2) => rp(e1) ::: rp(e2) ::: List(PL) 
  case Times(e1, e2) => rp(e1) ::: rp(e2) ::: List(TI) 
}
println(string(e2))
println(rp(e2))

def comp(ls: List[Token], st: List[Int]) : Int = (ls, st) match {
  case (Nil, st) => st.head 
  case (T(n)::rest, st) => comp(rest, n::st)
  case (PL::rest, n1::n2::st) => comp(rest, n1 + n2::st)
  case (TI::rest, n1::n2::st) => comp(rest, n1 * n2::st)
}

comp(rp(e), Nil)

def proc(s: String) : Token = s match {
  case  "+" => PL
  case  "*" => TI
  case  _ => T(s.toInt) 
}

comp("1 2 + 4 * 5 + 3 +".split(" ").toList.map(proc), Nil)




// Sudoku 
//========

// THE POINT OF THIS CODE IS NOT TO BE SUPER
// EFFICIENT AND FAST, just explaining exhaustive
// depth-first search


val game0 = """.14.6.3..
              |62...4..9
              |.8..5.6..
              |.6.2....3
              |.7..1..5.
              |5....9.6.
              |..6.2..3.
              |1..5...92
              |..7.9.41.""".stripMargin.replaceAll("\\n", "")

type Pos = (Int, Int)
val EmptyValue = '.'
val MaxValue = 9

val allValues = "123456789".toList
val indexes = (0 to 8).toList


def empty(game: String) = game.indexOf(EmptyValue)
def isDone(game: String) = empty(game) == -1 
def emptyPosition(game: String) = 
  (empty(game) % MaxValue, empty(game) / MaxValue)


def get_row(game: String, y: Int) = 
  indexes.map(col => game(y * MaxValue + col))
def get_col(game: String, x: Int) = 
  indexes.map(row => game(x + row * MaxValue))

def get_box(game: String, pos: Pos): List[Char] = {
    def base(p: Int): Int = (p / 3) * 3
    val x0 = base(pos._1)
    val y0 = base(pos._2)
    val ys = (y0 until y0 + 3).toList
    (x0 until x0 + 3).toList.flatMap(x => ys.map(y => game(x + y * MaxValue)))
}

//get_row(game0, 0)
//get_row(game0, 1)
//get_col(game0, 0)
//get_box(game0, (3, 1))


// this is not mutable!!
def update(game: String, pos: Int, value: Char): String = 
  game.updated(pos, value)

def toAvoid(game: String, pos: Pos): List[Char] = 
  (get_col(game, pos._1) ++ get_row(game, pos._2) ++ get_box(game, pos))

def candidates(game: String, pos: Pos): List[Char] = 
  allValues.diff(toAvoid(game, pos))

//candidates(game0, (0,0))

def pretty(game: String): String = 
  "\n" + (game.sliding(MaxValue, MaxValue).mkString("\n"))


def search(game: String): List[String] = {
  if (isDone(game)) List(game)
  else {
    val cs = candidates(game, emptyPosition(game))
    cs.map(c => search(update(game, empty(game), c))).toList.flatten
  }
}

search(game0).map(pretty)

val game1 = """23.915...
              |...2..54.
              |6.7......
              |..1.....9
              |89.5.3.17
              |5.....6..
              |......9.5
              |.16..7...
              |...329..1""".stripMargin.replaceAll("\\n", "")

search(game1).map(pretty)

// a game that is in the hard category
val game2 = """8........
              |..36.....
              |.7..9.2..
              |.5...7...
              |....457..
              |...1...3.
              |..1....68
              |..85...1.
              |.9....4..""".stripMargin.replaceAll("\\n", "")

search(game2).map(pretty)

// game with multiple solutions
val game3 = """.8...9743
              |.5...8.1.
              |.1.......
              |8....5...
              |...8.4...
              |...3....6
              |.......7.
              |.3.5...8.
              |9724...5.""".stripMargin.replaceAll("\\n", "")

search(game3).map(pretty).foreach(println)

// for measuring time
def time_needed[T](i: Int, code: => T) = {
  val start = System.nanoTime()
  for (j <- 1 to i) code
  val end = System.nanoTime()
  s"${(end - start) / 1.0e9} secs"
}

time_needed(1, search(game2))



// Tail recursion
//================


def fact(n: Long): Long = 
  if (n == 0) 1 else n * fact(n - 1)


fact(10)              // ok
fact(1000)            // silly
fact(10000)           // produces a stackoverflow

def factB(n: BigInt): BigInt = 
  if (n == 0) 1 else n * factB(n - 1)

factB(1000)


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

// tail recursive version that searches 
// for all Sudoku solutions


def searchT(games: List[String], sols: List[String]): List[String] = games match {
  case Nil => sols
  case game::rest => {
    if (isDone(game)) searchT(rest, game::sols)
    else {
      val cs = candidates(game, emptyPosition(game))
      searchT(cs.map(c => update(game, empty(game), c)) ::: rest, sols)
    }
  }
}

searchT(List(game3), List()).map(pretty)


// tail recursive version that searches 
// for a single solution

def search1T(games: List[String]): Option[String] = games match {
  case Nil => None
  case game::rest => {
    if (isDone(game)) Some(game)
    else {
      val cs = candidates(game, emptyPosition(game))
      search1T(cs.map(c => update(game, empty(game), c)) ::: rest)
    }
  }
}

search1T(List(game3)).map(pretty)
time_needed(1, search1T(List(game3)))
time_needed(1, search1T(List(game2)))

// game with multiple solutions
val game3 = """.8...9743
              |.5...8.1.
              |.1.......
              |8....5...
              |...8.4...
              |...3....6
              |.......7.
              |.3.5...8.
              |9724...5.""".stripMargin.replaceAll("\\n", "")

searchT(List(game3), Nil).map(pretty)
search1T(List(game3)).map(pretty)

// Moral: Whenever a recursive function is resource-critical
// (i.e. works with 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
//===================

// You do not want to write functions like contains, first, 
// length and so on for every type of lists.


def length_string_list(lst: List[String]): Int = lst match {
  case Nil => 0
  case x::xs => 1 + length_string_list(xs)
}

def length_int_list(lst: List[Int]): Int = lst match {
  case Nil => 0
  case x::xs => 1 + length_int_list(xs)
}

length_string_list(List("1", "2", "3", "4"))
length_int_list(List(1, 2, 3, 4))

def length[A](lst: List[A]): Int = lst match {
  case Nil => 0
  case x::xs => 1 + length(xs)
}
length(List("1", "2", "3", "4"))
length(List(1, 2, 3, 4))


def map[A, B](lst: List[A], f: A => B): List[B] = lst match {
  case Nil => Nil
  case x::xs => f(x)::map(xs, f) 
}

map(List(1, 2, 3, 4), (x: Int) => x.toString)



// distinct / distinctBy

val ls = List(1,2,3,3,2,4,3,2,1)
ls.distinct

ls.minBy(_._2)
ls.sortBy(_._1)

def distinctBy[B, C](xs: List[B], 
                     f: B => C, 
                     acc: List[C] = Nil): List[B] = xs match {
  case Nil => Nil
  case x::xs => {
    val res = f(x)
    if (acc.contains(res)) distinctBy(xs, f, acc)  
    else x::distinctBy(xs, f, res::acc)
  }
} 

// distinctBy  with the identity function is 
// just distinct
distinctBy(ls, (x: Int) => x)


val cs = List('A', 'b', 'a', 'c', 'B', 'D', 'd')

distinctBy(cs, (c:Char) => c.toUpper)



// Type inference is local in Scala

def id[T](x: T) : T = x

val x = id(322)          // Int
val y = id("hey")        // String
val z = id(Set[Int](1,2,3,4)) // Set[Int]



// The type variable concept in Scala can get really complicated.
//
// - variance (OO)
// - bounds (subtyping)
// - quantification

// Java has issues with this too: Java allows
// to write the following incorrect code, and
// only recovers by raising an exception
// at runtime.

// Object[] arr = new Integer[10];
// arr[0] = "Hello World";


// Scala gives you a compile-time error, which
// is much better.

var arr = Array[Int]()
arr(0) = "Hello World"




// Cool Stuff in Scala
//=====================


// Implicits or How to Pimp your Library
//======================================
//
// For example adding your own methods to Strings:
// Imagine you want to increment strings, like
//
//     "HAL".increment
//
// you can avoid ugly fudges, like a MyString, by
// using implicit conversions.


implicit class MyString(s: String) {
  def increment = s.map(c => (c + 1).toChar) 
}

"HAL".increment


// Abstract idea:
// In that version implicit conversions were used to solve the 
// late extension problem; namely, given a class C and a class T, 
// how to have C extend T without touching or recompiling C. 
// Conversions add a wrapper when a member of T is requested 
// from an instance of C.

//Another example (TimeUnit in 2.13?)

import scala.concurrent.duration.{TimeUnit,SECONDS,MINUTES}

case class Duration(time: Long, unit: TimeUnit) {
  def +(o: Duration) = 
    Duration(time + unit.convert(o.time, o.unit), unit)
}

implicit class Int2Duration(that: Int) {
  def seconds = new Duration(that, SECONDS)
  def minutes = new Duration(that, MINUTES)
}

5.seconds + 2.minutes   //Duration(125L, SECONDS )
2.minutes + 60.seconds




// Regular expressions - the power of DSLs in Scala
//==================================================

abstract class Rexp
case object ZERO extends Rexp                     // nothing
case object ONE extends Rexp                      // the empty string
case class CHAR(c: Char) extends Rexp             // a character c
case class ALT(r1: Rexp, r2: Rexp) extends Rexp   // alternative  r1 + r2
case class SEQ(r1: Rexp, r2: Rexp) extends Rexp   // sequence     r1 . r2  
case class STAR(r: Rexp) extends Rexp             // star         r*



// writing (ab)* in the format above is 
// tedious
val r0 = STAR(SEQ(CHAR('a'), CHAR('b')))


// some convenience for typing in regular expressions
import scala.language.implicitConversions    
import scala.language.reflectiveCalls 

def charlist2rexp(s: List[Char]): Rexp = s match {
  case Nil => ONE
  case c::Nil => CHAR(c)
  case c::s => SEQ(CHAR(c), charlist2rexp(s))
}
implicit def string2rexp(s: String): Rexp = 
  charlist2rexp(s.toList)


val r1 = STAR("ab")
val r2 = STAR(ALT("ab", "baa baa black sheep"))
val r3 = STAR(SEQ("ab", ALT("a", "b")))

implicit def RexpOps (r: Rexp) = new {
  def | (s: Rexp) = ALT(r, s)
  def % = STAR(r)
  def ~ (s: Rexp) = SEQ(r, s)
}

implicit def stringOps (s: String) = new {
  def | (r: Rexp) = ALT(s, r)
  def | (r: String) = ALT(s, r)
  def % = STAR(s)
  def ~ (r: Rexp) = SEQ(s, r)
  def ~ (r: String) = SEQ(s, r)
}

//example regular expressions
val digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
val sign = "+" | "-" | ""
val number = sign ~ digit ~ digit.% 


//
// Object Oriented Programming in Scala
//
// =====================================

abstract class Animal
case class Bird(name: String) extends Animal {
   override def toString = name
}
case class Mammal(name: String) extends Animal
case class Reptile(name: String) extends Animal

Bird("Sparrow")

println(Bird("Sparrow"))
println(Bird("Sparrow").toString)


// you can override methods
case class Bird(name: String) extends Animal {
  override def toString = name
}


// There is a very convenient short-hand notation
// for constructors:

class Fraction(x: Int, y: Int) {
  def numer = x
  def denom = y
}


case class Fraction(numer: Int, denom: Int)

val half = Fraction(1, 2)

half.denom


// In mandelbrot.scala I used complex (imaginary) numbers 
// and implemented the usual arithmetic operations for complex 
// numbers.

case class Complex(re: Double, im: Double) { 
  // represents the complex number re + im * i
  def +(that: Complex) = Complex(this.re + that.re, this.im + that.im)
  def -(that: Complex) = Complex(this.re - that.re, this.im - that.im)
  def *(that: Complex) = Complex(this.re * that.re - this.im * that.im,
                                 this.re * that.im + that.re * this.im)
  def *(that: Double) = Complex(this.re * that, this.im * that)
  def abs = Math.sqrt(this.re * this.re + this.im * this.im)
}

val test = Complex(1, 2) + Complex (3, 4)

// this could have equally been written as
val test = Complex(1, 2).+(Complex (3, 4))

// this applies to all methods, but requires
import scala.language.postfixOps

List(5, 2, 3, 4).sorted
List(5, 2, 3, 4) sorted


// ...to allow the notation n + m * i
import scala.language.implicitConversions   

val i = Complex(0, 1)
implicit def double2complex(re: Double) = Complex(re, 0)


val inum1 = -2.0 + -1.5 * i
val inum2 =  1.0 +  1.5 * i



// All is public by default....so no public is needed.
// You can have the usual restrictions about private 
// values and methods, if you are MUTABLE !!!

case class BankAccount(init: Int) {

  private var balance = init

  def deposit(amount: Int): Unit = {
    if (amount > 0) balance = balance + amount
  }

  def withdraw(amount: Int): Int =
    if (0 < amount && amount <= balance) {
      balance = balance - amount
      balance
    } else throw new Error("insufficient funds")
}

// BUT since we are completely IMMUTABLE, this is 
// virtually of not concern to us.



// another example about Fractions
import scala.language.implicitConversions
import scala.language.reflectiveCalls


case class Fraction(numer: Int, denom: Int) {
  override def toString = numer.toString + "/" + denom.toString

  def +(other: Fraction) = Fraction(numer + other.numer, denom + other.denom)
  def /(other: Fraction) = Fraction(numer * other.denom, denom * other.numer)
  def /% (other: Fraction) = Fraction(numer * other.denom, denom * other.numer)

}

implicit def Int2Fraction(x: Int) = Fraction(x, 1)


val half = Fraction(1, 2)
val third = Fraction (1, 3)

half + third
half / third

// not sure if one can get this to work
// properly, since Scala just cannot find out
// if / is for ints or for Fractions 
(1 / 3) + half
(1 / 2) + third

// either you have to force the Fraction-type by
// using a method that is not defined for ints
(1 /% 3) + half
(1 /% 2) + third


// ...or explicitly give the type in order to allow
// Scala to do the conversion to Fractions 
((1:Fraction) / 3) + half
(1 / (3: Fraction)) + half



// DFAs in Scala  
//===============
import scala.util.Try


// A is the state type
// C is the input (usually characters)

case class DFA[A, C](start: A,              // starting state
                     delta: (A, C) => A,    // transition function
                     fins:  A => Boolean) { // final states (Set)

  def deltas(q: A, s: List[C]) : A = s match {
    case Nil => q
    case c::cs => deltas(delta(q, c), cs)
  }

  def accepts(s: List[C]) : Boolean = 
    Try(fins(deltas(start, s))) getOrElse false
}

// the example shown in the handout 
abstract class State
case object Q0 extends State
case object Q1 extends State
case object Q2 extends State
case object Q3 extends State
case object Q4 extends State

val delta : (State, Char) => State = 
  { case (Q0, 'a') => Q1
    case (Q0, 'b') => Q2
    case (Q1, 'a') => Q4
    case (Q1, 'b') => Q2
    case (Q2, 'a') => Q3
    case (Q2, 'b') => Q2
    case (Q3, 'a') => Q4
    case (Q3, 'b') => Q0
    case (Q4, 'a') => Q4
    case (Q4, 'b') => Q4 
    case _ => throw new Exception("Undefined") }

val dfa = DFA(Q0, delta, Set[State](Q4))

dfa.accepts("abaaa".toList)     // true
dfa.accepts("bbabaab".toList)   // true
dfa.accepts("baba".toList)      // false
dfa.accepts("abc".toList)       // false

// another DFA with a Sink state
abstract class S
case object S0 extends S
case object S1 extends S
case object S2 extends S
case object Sink extends S

// transition function with a sink state
val sigma : (S, Char) => S = 
  { case (S0, 'a') => S1
    case (S1, 'a') => S2
    case _ => Sink
  }

val dfa2 = DFA(S0, sigma, Set[S](S2))

dfa2.accepts("aa".toList)        // true
dfa2.accepts("".toList)          // false
dfa2.accepts("ab".toList)        // false

//  we could also have a dfa for numbers
val sigmai : (S, Int) => S = 
  { case (S0, 1) => S1
    case (S1, 1) => S2
    case _ => Sink
  }

val dfa3 = DFA(S0, sigmai, Set[S](S2))

dfa3.accepts(List(1, 1))        // true
dfa3.accepts(Nil)               // false
dfa3.accepts(List(1, 2))        // false




// NFAs (Nondeterministic Finite Automata)


case class NFA[A, C](starts: Set[A],          // starting states
                     delta: (A, C) => Set[A], // transition function
                     fins:  A => Boolean) {   // final states 

  // given a state and a character, what is the set of 
  // next states? if there is none => empty set
  def next(q: A, c: C) : Set[A] = 
    Try(delta(q, c)) getOrElse Set[A]() 

  def nexts(qs: Set[A], c: C) : Set[A] =
    qs.flatMap(next(_, c))

  // depth-first version of accepts
  def search(q: A, s: List[C]) : Boolean = s match {
    case Nil => fins(q)
    case c::cs => next(q, c).exists(search(_, cs))
  }

  def accepts(s: List[C]) : Boolean =
    starts.exists(search(_, s))
}



// NFA examples

val nfa_trans1 : (State, Char) => Set[State] = 
  { case (Q0, 'a') => Set(Q0, Q1) 
    case (Q0, 'b') => Set(Q2) 
    case (Q1, 'a') => Set(Q1) 
    case (Q2, 'b') => Set(Q2) }

val nfa = NFA(Set[State](Q0), nfa_trans1, Set[State](Q2))

nfa.accepts("aa".toList)             // false
nfa.accepts("aaaaa".toList)          // false
nfa.accepts("aaaaab".toList)         // true
nfa.accepts("aaaaabbb".toList)       // true
nfa.accepts("aaaaabbbaaa".toList)    // false
nfa.accepts("ac".toList)             // false


// Q: Why the kerfuffle about the polymorphic types in DFAs/NFAs?
// A: Subset construction. Here the state type for the DFA is
//    sets of states.

def subset[A, C](nfa: NFA[A, C]) : DFA[Set[A], C] = {
  DFA(nfa.starts, 
      { case (qs, c) => nfa.nexts(qs, c) }, 
      _.exists(nfa.fins))
}

subset(nfa1).accepts("aa".toList)             // false
subset(nfa1).accepts("aaaaa".toList)          // false
subset(nfa1).accepts("aaaaab".toList)         // true
subset(nfa1).accepts("aaaaabbb".toList)       // true
subset(nfa1).accepts("aaaaabbbaaa".toList)    // false
subset(nfa1).accepts("ac".toList)             // false








// Lazy Evaluation
//=================
//
// Do not evaluate arguments just yet:
// this uses the => in front of the type
// of the code-argument

def time_needed[T](i: Int, code: => T) = {
  val start = System.nanoTime()
  for (j <- 1 to i) code
  val end = System.nanoTime()
  (end - start)/(i * 1.0e9)
}


// Mind-Blowing Regular Expressions

// same examples using the internal regexes
val evil = "(a*)*b"


println("a" * 100)

("a" * 10 ++ "b").matches(evil)
("a" * 10).matches(evil)
("a" * 10000).matches(evil)
("a" * 20000).matches(evil)
("a" * 50000).matches(evil)

time_needed(1, ("a" * 10000).matches(evil))