progs/lecture3.scala
author Christian Urban <urbanc@in.tum.de>
Tue, 21 Nov 2017 16:31:11 +0000
changeset 152 114a89518aea
parent 77 3cbe3d90b77f
child 153 4383809c176a
permissions -rw-r--r--
updated

// Scala Lecture 3
//=================

// adding two binary strings very, very lazy manner

def badd(s1: String, s2: String) : String = 
  (BigInt(s1, 2) + BigInt(s2, 2)).toString(2)


// collatz function on binary numbers

def bcollatz(s: String) : Long = (s.dropRight(1), s.last) match {
  case ("", '1') => 1                                  // we reached 1
  case (rest, '0') => 1 + bcollatz(rest)               // even number => divide by two
  case (rest, '1') => 1 + bcollatz(badd(s + '1', s))   // odd number => s + '1' is 2 * s + 1
                                                       //               add another s gives 3 * s + 1  
} 

bcollatz(9.toBinaryString)
bcollatz(837799.toBinaryString)
bcollatz(100000000000000000L.toBinaryString)
bcollatz(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))

def conv(c: Char) : Int = c match {
  case '0' => 0
  case '1' => 1
}

def badds(s1: String, s2: String, carry: Int) : String = (s1, s2, carry) match {
  case ("", "", 1) => "1"
  case ("", "", 0) => ""
  case (cs1, cs2, carry) => (conv(cs1.last) + conv(cs2.last) + carry) match {
    case 3 => badds(cs1.dropRight(1), cs2.dropRight(1), 1) + '1'
    case 2 => badds(cs1.dropRight(1), cs2.dropRight(1), 1) + '0'
    case 1 => badds(cs1.dropRight(1), cs2.dropRight(1), 0) + '1'
    case 0 => badds(cs1.dropRight(1), cs2.dropRight(1), 0) + '0'
  }
} 

def bcollatz2(s: String) : Long = (s.dropRight(1), s.last) match {
  case ("", '1') => 1                                          // we reached 1
  case (rest, '0') => 1 + bcollatz2(rest)                      // even number => divide by two
  case (rest, '1') => 1 + bcollatz2(badds(s + '1', '0' + s, 0))   // odd number => s + '1' is 2 * s + 1
                                                                  //         add another s gives 3 * s + 1  
} 

bcollatz2(9.toBinaryString)
bcollatz2(837799.toBinaryString)
bcollatz2(100000000000000000L.toBinaryString)
bcollatz2(BigInt("1000000000000000000000000000000000000000000000000000000000000000000000000000").toString(2))



// One of only two places where I conceded to mutable
// data structures: The following function generates 
// new labels

var counter = -1

def fresh(x: String) = {
  counter += 1
  x ++ "_" ++ counter.toString()
}

fresh("x")
fresh("x")

// this can be avoided, but would have made my code more
// complicated


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

def my_contains(elem: Int, lst: List[Int]): Boolean = lst match {
  case Nil => false
  case x::xs => 
    if (x == elem) true else my_contains(elem, xs)
}

my_contains(4, List(1,2,3))
my_contains(2, List(1,2,3))

my_contains(1000000, (1 to 1000000).toList)
my_contains(1000001, (1 to 1000000).toList)


//factorial V0.1
import scala.annotation.tailrec


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

fact(10000)                        // produces a stackoverflow

@tailrec
def factT(n: BigInt, acc: BigInt): BigInt =
  if (n == 0) acc else factT(n - 1, n * acc)


println(factT(10000, 1))

// the functions my_contains and factT are tail-recursive 
// you can check this with 

import scala.annotation.tailrec

// and the annotation @tailrec

// 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

// consider the following "stupid" version of the
// coin exchange problem: given some coins and a
// total, what is the change can you get?

val coins = List(4,5,6,8,10,13,19,20,21,24,38,39,40)

def first_positive[B](lst: List[Int], f: Int => Option[B]): Option[B] = lst match {
  case Nil => None
  case x::xs => 
    if (x <= 0) first_positive(xs, f)
    else {
      val fx = f(x)
      if (fx.isDefined) fx else first_positive(xs, f)
  }
}


import scala.annotation.tailrec

def search(total: Int, coins: List[Int], cs: List[Int]): Option[List[Int]] = {
  if (total < cs.sum) None 
  else if (cs.sum == total) Some(cs) 
  else first_positive(coins, (c: Int) => search(total, coins, c::cs))
}

search(11, coins, Nil)
search(111, coins, Nil)
search(111111, coins, Nil)

val junk_coins = List(4,-2,5,6,8,0,10,13,19,20,-3,21,24,38,39, 40)
search(11, junk_coins, Nil)
search(111, junk_coins, Nil)


import scala.annotation.tailrec

@tailrec
def searchT(total: Int, coins: List[Int], 
            acc_cs: List[List[Int]]): Option[List[Int]] = acc_cs match {
  case Nil => None
  case x::xs => 
    if (total < x.sum) searchT(total, coins, xs)
    else if (x.sum == total) Some(x) 
    else searchT(total, coins, coins.filter(_ > 0).map(_::x) ::: xs)
}

val start_acc = coins.filter(_ > 0).map(List(_))
searchT(11, junk_coins, start_acc)
searchT(111, junk_coins, start_acc)
searchT(111111, junk_coins, start_acc)

// Moral: Whenever a recursive function is resource-critical
// (i.e. works with large recursion depths), then you need to
// write it in tail-recursive fashion.
// 
// Unfortuantely, the Scala is because of current limitations in 
// the JVM 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 
// 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)
}

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


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


def map_int_list(lst: List[Int], f: Int => Int): List[Int] = lst match {
  case Nil => Nil
  case x::xs => f(x)::map_int_list(xs, f) 
}

map_int_list(List(1, 2, 3, 4), square)


// Remember?
def first[A, B](xs: List[A], f: A => Option[B]): Option[B] = ...


// polymorphic classes
//(trees with some content)

abstract class Tree[+A]
case class Node[A](elem: A, left: Tree[A], right: Tree[A]) extends Tree[A]
case object Leaf extends Tree[Nothing]

val t0 = Node('4', Node('2', Leaf, Leaf), Node('7', Leaf, Leaf))

def insert[A](tr: Tree[A], n: A): Tree[A] = tr match {
  case Leaf => Node(n, Leaf, Leaf)
  case Node(m, left, right) => 
    if (n == m) Node(m, left, right) 
    else if (n < m) Node(m, insert(left, n), right)
    else Node(m, left, insert(right, n))
}


// the A-type needs to be ordered

abstract class Tree[+A <% Ordered[A]]
case class Node[A <% Ordered[A]](elem: A, left: Tree[A], 
                                 right: Tree[A]) extends Tree[A]
case object Leaf extends Tree[Nothing]


def insert[A <% Ordered[A]](tr: Tree[A], n: A): Tree[A] = tr match {
  case Leaf => Node(n, Leaf, Leaf)
  case Node(m, left, right) => 
    if (n == m) Node(m, left, right) 
    else if (n < m) Node(m, insert(left, n), right)
    else Node(m, left, insert(right, n))
}


val t1 = Node(4, Node(2, Leaf, Leaf), Node(7, Leaf, Leaf))
insert(t1, 3)

val t2 = Node('b', Node('a', Leaf, Leaf), Node('f', Leaf, Leaf))
insert(t2, 'e')



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


abstract class Rexp
case object ZERO extends Rexp
case object ONE extends Rexp
case class CHAR(c: Char) extends Rexp
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*


// (ab)*
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("")
val r3 = STAR(ALT("ab", "baa baa black sheep"))

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)
}

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



// Lazyness with style
//=====================

// The concept of lazy evaluation doesn’t really exist in 
// non-functional languages, but it is pretty easy to grasp. 
// Consider first 

def square(x: Int) = x * x

square(42 + 8)

// this is called strict evaluation


def expensiveOperation(n: BigInt): Boolean = expensiveOperation(n + 1) 
val a = "foo"
val b = "bar"

val test = if ((a == b) || expensiveOperation(0)) true else false

// this is called lazy evaluation
// you delay compuation until it is really 
// needed; once calculated though, does not 
// need to be re-calculated

// a useful example is
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) + " secs"
}


// streams (I do not care how many)
// primes: 2, 3, 5, 7, 9, 11, 13 ....

def generatePrimes (s: Stream[Int]): Stream[Int] =
  s.head #:: generatePrimes(s.tail filter (_ % s.head != 0))

val primes: Stream[Int] = generatePrimes(Stream.from(2))

primes.take(10).toList

primes.filter(_ > 100).take(2000).toList

time_needed(1, primes.filter(_ > 100).take(2000).toList)
time_needed(1, primes.filter(_ > 100).take(2000).toList)



// streams are useful for implementing search problems ;o)




// The End
//=========

// A function should do one thing, and only one thing.

// Make your variables immutable, unless there's a good 
// reason not to.

// You can be productive on Day 1, but the language is deep.

// I like best about Scala that it lets me write
// concise, readable code