programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2....
TRANSCRIPT
![Page 1: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/1.jpg)
011101110101101111010101010100101011010011010101110101010100101010101010101010101010
101010111101010101010101110101010111010110101011010101101010111010101011101010101101011101110101101011101101010111110101010101000101010101010101101010111010101010100101
Programming abstractions and analysis –– recursion
Mikko KiveläDepartment of Computer Science
Aalto University
CS-A1120 Programming 215 April 2020
Lecture notes based on material created by Tommi Junttila & Petteri Kaski
![Page 2: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/2.jpg)
Contents (rounds and modules)• 1. Warmup round
• I The mystery of the computer 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer
• II Programming abstractions and analysis 6. Collections and functions 7. Efficiency 8. Recursion 9. Algorithms and representations of information
• III Frontiers 10. Concurrency and parallelism 11. Virtualization and scalability 12. Machines that learn?
![Page 3: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/3.jpg)
RecursionDefinition: see recursion
![Page 4: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/4.jpg)
Recursion
• Recursion is a basic principle for…
• replacing iteration in functional programming
• defining structural objects such as data types or collections and traverse them
• solving computational problems
![Page 5: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/5.jpg)
Palindromes (reminder from O1)
• Palindromes are words or sentences that read the same forwards and backwards (not including spaces and punctuation)
• E.g., “testset” or “A man, a plan, a canal: Panama”
• Can be defined (and checked for) recursively:
A. String with zero or one characters is a palindrome
B. String is a palindrome if it (1) starts and ends with the same character and (2) the substring between these is a palindrome
![Page 6: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/6.jpg)
Palindromes (reminder from O1)
• Animation for a similar code in Programming 1: https://plus.cs.aalto.fi/o1/2019/w12/ch01/
def isPalindrome(s: String): Boolean = { // Helper inner function doing the actual recursive task def actualSearch(t: String): Boolean = { if (t.length <= 1) true else if (t.head != t.last) false else actualSearch(t.substring(1, t.length - 1)) }
// Remove spaces and special characters val sPlain = s.filter(_.isLetterOrDigit).map(_.toLower) // Call the actual recursive search function actualSearch(sPlain) }
![Page 7: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/7.jpg)
Textual representation of a call stack• A way of visualising what happens when executing a
recursive function
isPalindrome("ufo tofu"): val sPlain = "ufotofu" // We could omit this return actualSearch("ufotofu") actualSearch("ufotofu"): // tests (t.length <= 1) and (t.head != t.last) both fail return actualSearch("fotof") actualSearch("fotof"): return actualSearch("oto"): actualSearch(“oto”):
return actualSearch("t"): actualSearch("t"):
return true // test (t.length <= 1) succeeds
• Not interesting or obvious rows such as sPlain = “ufotofu” can be left out of this illustration
![Page 8: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/8.jpg)
Expanding• Another way of visualising recursive function calls
• Computations in functions without side effects can be illustrated by “expanding” the code:
isPalisPalindrome("ufo tofu") =actualSearch("ufo tofu".filter(_.isLetterOrDigit).map(_.toLower)) =actualSearch("ufotofu".map(_.toLower)) =actualSearch("ufotofu") =actualSearch("fotof") =actualSearch("oto") =actualSearch("t") =true
• Again, not interesting or obvious rows can be left out
![Page 9: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/9.jpg)
Tail calls and recursion
![Page 10: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/10.jpg)
Recursion and the call stack • The main problem in recursion: there is only limited
amount of memory for the call stack
• For example, computing factorial recursively:
• Running this will lead to problems:
def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") if(n == 1) BigInt(1) else n * fact(n-1)}
n! = {1 when n = 1n × (n − 1)! when n > 2
scala> fact(100000)java.lang.StackOverflowError
![Page 11: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/11.jpg)
Recursion and the call stack • Call stack for our recursive factorial function:
fact(4): val temp1 = fact(3) fact(3): val temp2 = fact(2) fact(2): val temp3 = fact(1) fact(1): return BigInt(1)
// temp3 = 1
return 2*temp3 // 2*1 = 2 // temp2 = 2 return 3*temp2 // 3*2 = 6// temp1 = 6
return 4*temp1 // 4*6 = 24
• For every call, we need to remember the value of n and calculate fact(n-1) before we can return the result
➔ Call stack will have the depth of the parameter n
![Page 12: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/12.jpg)
Recursion and the call stack • We can compute the result by expanding the function
(because it doesn’t have side effects)
fact(4) = (4 * fact(3)) = (4 * (3 * fact(2))) = (4 * (3 * (2 * fact(1)))) = (4 * (3 * (2 * 1))) = (4 * (3 * 2)) = (4 * 6) =24
![Page 13: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/13.jpg)
Definition
Tail calls
Function call in a method is a tail call if it is the last operation of the function
def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") if(n == 1) BigInt(1) else n * fact(n-1)}
• BigInt(1) is a tail call
• fact(n-1) is not a tail call; the last operation in the function is n * fact(n-1)
• For example, in the function
![Page 14: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/14.jpg)
Definition
Tail recursion
Function is tail recursive if all of its calls to itself are tail calls
• Here we only consider direct tail recursion where the tail calls do not go through some other function (e.g., def f() = …; g() and def g() = …; f() )
• Scala can optimise tail recursive functions such that they are implemented with iteration
• This is because the compiler knows that no variables cannot be used after the tail call and the the call frame can be released
![Page 15: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/15.jpg)
Tail recursion • Lets make a tail recursive version of the factorial
function (by using tail recursive inner function):
• Note the similarity with the iterative version:
def fact(n: Int): BigInt = { require(n >= 1, "n should be a positive integer") def iterate(i: Int, result: BigInt): BigInt = { if(i == 0) result else iterate(i - 1, result * i) } iterate(n, BigInt(1)) }
def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") var result = BigInt(1) for(i <- n to 1 by -1) result = result * i result }
![Page 16: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/16.jpg)
Tail recursion • Expansion of the tail recursive fact function:
• Same with call stack: the call frame of iterate is reused:
fact(4) =iterate(4, 1) =iterate(3, 4) =iterate(2, 12) =iterate(1, 24) =iterate(0, 24) = 24
fact(4): return iterate(4, 1) iterate(4, 1): return iterate(3, 4) iterate(3, 4): return iterate(2, 12) iterate(2, 12): return iterate(1, 24)
iterate(0, 24):
return 24
![Page 17: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/17.jpg)
@tailrec annotation • The @tailrec annotation in Scala declares that the
compiler must optimise tail recursion into iteration
• If the annotated function is not tail recursive, the compiler will issue an error
• Only direct tail calls are allowed
• It must not be possible to override the function: it must be an inner function or declared as final method
import scala.annotation.tailrec...@tailrec def myFunction(...) {...
}
For more information, see: http://theyougen.blogspot.com/2010/01/why-overrideable-tail-recursive-methods.html
![Page 18: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/18.jpg)
Recursively defined data structures
![Page 19: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/19.jpg)
Recursive data structures
• In addition to functions, data structure can be recursive
• They are most naturally manipulated with recursive functions
• Next we go through two examples:
1. Linked lists (similar to List in Scala)
2. Symbolic arithmetic expressions (example of more general tree data structure)
![Page 20: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/20.jpg)
Linked lists
![Page 21: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/21.jpg)
Linked lists• We consider lists containing elements of type T
• Let define such T-lists recursively:
1. Nil is the empty T-list
2. If l is a T-list and e is an element of type T, then Cons(e, l) is a T-list where e is the first element followed by the elements of the list l
Examples:
• Nil is an empty Int-list []
• Cons(1,Nil) is an Int-list [1]
• Cons(1,Cons(3,Cons(5,Nil))) is an Int-list [1,3,5]
• Cons(1,Cons(Nil,Cons(5,Nil))) is not an Int-list
![Page 22: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/22.jpg)
Linked lists• For a list
Cons(e, l)
• We say that:
• e is the head of the list
• l is the tail of the list
• Empty list does not have head or tail
Examples:
• In Int-list Cons(5, Nil) the head is 5 and tail Nil
• In String-list Cons(“first”, Cons(“second”,Nil)) the head is “first” and tail Cons(“second”,Nil)
![Page 23: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/23.jpg)
Linked lists, the first implementationabstract class LinkedList[A] {
def isEmpty: Boolean def head: A def tail: LinkedList[A]}
class Nil[A]() extends LinkedList[A] { def isEmpty = true def head = throw new java.util.NoSuchElementException("head of empty list") def tail = throw new java.util.NoSuchElementException("tail of empty list") }
class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { def isEmpty = false}
object Nil { def apply[A]() = new Nil[A]()}
object Cons { def apply[A](head : A, tail : LinkedList[A]) = new Cons(head, tail)
}
![Page 24: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/24.jpg)
Linked lists, the first implementation
• We can now write:
val l = Cons(5, Cons(4, Cons(-7, Nil())))
• … and these objects will be created in the memory:
head
Cons
tailhead
Nil
5
Cons
tailhead
4 −7
l Cons
tail
![Page 25: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/25.jpg)
Linked lists, the first implementation• If we write:
val l = Cons(5, Cons(4, Cons(-7, Nil()))) val m = Cons(3, l)
• … these objects will be created:
Cons
tailhead
Nil
5
Cons
tailhead
Cons
tailhead
3
4 −7
l
m
Cons
tailhead
• Note that the list l is not copied
• Adding to (and removing from) the beginning of the list is efficient
![Page 26: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/26.jpg)
Linked lists, contains method• Tests if the list contains e
• Implemented recursively:A. empty list does not contain eB. if head of the list is e, then it contains eC. otherwise it only contains e if the tail contains e
@tailrec final def contains(e: A): Boolean = { if(isEmpty) false else if(head == e) true else tail.contains(e)}
• Example of the call stack:Cons(1, Cons(2, Cons(3, Nil()))).contains(3): return Cons(2, Cons(3, Nil())).contains(3)Cons(2, Cons(3, Nil())).contains(3): return Cons(3, Nil()).contains(3)Cons(3, Nil()).contains(3): return true
![Page 27: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/27.jpg)
Linked lists, length method• Calculates the length of the list
• Implemented recursively:A. empty list has length 0B. the length of list Cons(e, t) is 1 plus length of the list t
abstract class LinkedList[A] { ... def length: Int}class Nil[A]() extends LinkedList[A] { ... def length = 0}class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] {...
def length = 1 + tail.length}
![Page 28: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/28.jpg)
Linked lists, length method• This implementation is not tail recursive!
• With a long list it will result in a StackOverflowError
• Example of a call stack with a short list
Cons(1, Cons(2, Cons(3, Nil()))).length: val tmp1 = Cons(2, Cons(3, Nil())).length Cons(2, Cons(3, Nil())).length: val tmp2 = Cons(3, Nil()).length Cons(3, Nil()).length: val tmp3 = Nil().length Nil().length: return 0
// tmp3 == 0
return 1 + tmp3 // tmp2 == 1 return 1 + tmp2// tmp1 == 2
return 1 + tmp13
![Page 29: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/29.jpg)
Linked lists, length method• A tail recursive version:
def length: Int = { def inner(remaining: LinkedList[A], result:Int): Int = { if(remaining.isEmpty) result else inner(remaining.tail, result+1) } inner(this, 0)}
• Expanded function call:
Cons(1, Cons(2, Cons(3, Nil()))).length =inner(Cons(1, Cons(2, Cons(3, Nil()))), 0) =inner(Cons(2, Cons(3, Nil())), 1) =inner(Cons(3, Nil()), 2) =inner(Nil(), 3) =3
![Page 30: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/30.jpg)
Linked lists, length method• A tail recursive version:
def length: Int = { def inner(remaining: LinkedList[A], result:Int): Int = { if(remaining.isEmpty) result else inner(remaining.tail, result+1) } inner(this, 0)}
• Compare to imperative version:
def length: Int = { var result = 0 var remaining = this while(!remaining.isEmpty) {
result += 1
remaining = remaining.tail }
result
}
![Page 31: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/31.jpg)
case class
• Companion objects are defined automatically!
abstract class LinkedList[A] { def isEmpty: Boolean def head: A def tail: LinkedList[A]}case class Nil[A]() extends LinkedList[A] { def isEmpty = true def head = throw new java.util.NoSuchElementException("head of empty list") def tail = throw new java.util.NoSuchElementException("tail ofempty list")
}case class Cons[A](val head: A, val tail: LinkedList[A]) extends LinkedList[A] { def isEmpty = false}
![Page 32: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/32.jpg)
case class • The previously defined operations now implemented
using case class and the match pattern matching:
@tailrec final def contains(e: A): Boolean = this match { case Nil() => false case Cons(h, t) => if (h == e) true else t.contains(e)}
def length: Int = { @tailrec def inner(remaining: LinkedList[A], result: Int): Int = remaining match { case Nil() => result case Cons(_, t) => inner(t, result + 1) } inner(this, 0)}
![Page 33: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/33.jpg)
• The previously defined operations now implemented using case class and the match pattern matching:
abstract class LinkedList[A]{…@tailrec final def foreach(f : A => Any) : Unit = this match { case Nil() => () // The only value of type Unit is "()" case Cons(v, t) => f(v); t.foreach(f)}
…
foreach method
• Example of a call stack:
Cons(1, Cons(2, Cons(3, Nil()))).foreach(println _): println(1)
return Cons(2, Cons(3, Nil())).foreach(println _)Cons(2, Cons(3, Nil())).foreach(println _):println(2) return Cons(3, Nil()).foreach(println _)
Cons(3, Nil()).foreach(println _):println(3) return Nil().foreach(println _)
Nil().foreach(println _):return ()
![Page 34: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/34.jpg)
• Reverses the order of elements in the list:def reverse: LinkedList[A] = { @tailrec def inner(remaining: LinkedList[A], result: LinkedList[A]): LinkedList[A] = remaining match { case Nil() => result case Cons(h, t) => inner(t, Cons(h, result)) } inner(this, Nil())}
reverse method
• Example expansion:
Cons(1, Cons(2, Cons(3, Nil()))).reverse = inner(Cons(1, Cons(2, Cons(3, Nil()))), Nil()) = inner(Cons(2, Cons(3, Nil())), Cons(1, Nil())) = inner(Cons(3, Nil()), Cons(2, Cons(1, Nil()))) = inner(Nil(), Cons(3, Cons(2, Cons(1, Nil())))) = Cons(3, Cons(2, Cons(1, Nil())))
![Page 35: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/35.jpg)
Symbolic arithmetic expressions
![Page 36: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/36.jpg)
Target:Data structure for simple arithmetic expressions
with variables, constants, additions, multiplications, negations
Example:-((2.0*x) + y)
How to define the expressions in Scala?
Symbolic arithmetic expressions
![Page 37: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/37.jpg)
Symbolic arithmetic expressions• Let define symbolic arithmetic expressions recursively:
1. Every number is an expression (e.g., 1.2)
2. Every variable is an expression (e.g., x)
3. If e1 and e2 are expression, then the following are also expressions: (e1 + e2), (e1 - e2), (e1 * e2), and -e1
All of the following examples are expressions:
• Variables and numbers: 2.0, x, and y
• Their combinations: (2.0 * x)
• Any further combinations: ((2.0 * x) + y) and -((2.0 * x) + y)
![Page 38: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/38.jpg)
Expressions, definition abstract class Expr {
/* To enable familiar infix notation */ def +(other: Expr) = Add(this, other) def -(other: Expr) = Subtract(this, other) def *(other: Expr) = Multiply(this, other) def unary_-() = Negate(this)}
case class Var(name: String) extends Expr { override def toString = name }
case class Num(value: Double) extends Expr { override def toString = value.toString }
case class Multiply(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + "*" + right + “)" }
case class Add(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + " + " + right + “)" }
case class Subtract(left: Expr, right: Expr) extends Expr { override def toString = "(" + left + " - " + right + “)”} case class Negate(expr: Expr) extends Expr { override def toString = " - “ + expr}
![Page 39: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/39.jpg)
Expressions• Expression, and how the objects look like in the memory:
val e = -((Num(2.0) * Var("x")) + Var("y"))
name = "y"
VarMultiply
left
right
name = "x"
Var
value = 2.0
Num
Add
left
right
Negate
expr
![Page 40: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/40.jpg)
Expressions, evaluationclass VariableNotAssignedException(message: String) extends java.lang.RuntimeException(message)/** * Evaluate the expression in the point p, where p is a map * associating each variable name in the expression into a Double value.*/
def evaluate(p: Map[String, Double]): Double = this match { case Var(n) => p.get(n) match { case Some(v) => v case None => throw new VariableNotAssignedException(n) }
case Num(v) => v case Multiply(l, r) => l.evaluate(p) * r.evaluate(p) case Add(l, r) => l.evaluate(p) + r.evaluate(p) case Subtract(l, r) => l.evaluate(p) - r.evaluate(p) case Negate(t) => -t.evaluate(p)}
![Page 41: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/41.jpg)
Expressions, evaluation example
val e = -((Num(2.0) * Var("x")) + Var("y"))val p = Map("x" -> 2.0, "y" -> -3.5)
• By expanding we get:
e.evaluate(p) = ((Num(2.0) * Var("x")) + Var("y")).evaluate(p) =((Num(2.0) * Var("x")).evaluate(p) + Var("y").evaluate(p)) = ((Num(2.0).evaluate(p) * Var("x").evaluate(p)) + Var("y").evaluate(p)) =((2.0 * Var("x").evaluate(p)) + Var("y").evaluate(p)) =((2.0 * 2.0) + Var("y").evaluate(p)) =(4.0 + Var("y").evaluate(p)) =(4.0 + -3.5) =0.5
![Page 42: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/42.jpg)
Expressions, symbolic simplification
def simplify: Expr = { // First simplify sub-expressions val subresult = this match { case Multiply(l, r) => Multiply(l.simplify, r.simplify) case Add(l, r) => Add(l.simplify, r.simplify) case Subtract(l, r) => Subtract(l.simplify, r.simplify) case Negate(t) => Negate(t.simplify) case _ => this // Handles Var and Num } // and then the expression itself subresult match { case Multiply(Num(1.0), r) => r ... case Add(l, Num(0.0)) => l ... case _ => subresult // Could not simplify}
}
![Page 43: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/43.jpg)
Expressions, symbolic simplification
scala> val e = Var("y")* ((Num(1.0) * Var("x")) + Num(0.0))e: expressions.Multiply = (y*((1.0*x) + 0.0))
scala> e.simplifyres2: expression.Expr = (x*y)
• We can now automatically simplify an expression y(1.0x)+0.0
![Page 44: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/44.jpg)
Solving problems recursively
![Page 45: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/45.jpg)
Many problems can be solved recursively (even if they are not defined recursively)
This is usually done by backtracking search:Candidate solutions are built recursively, and backtracking is done if we notice that
the solution is not going to work
Solving problems recursively
![Page 46: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/46.jpg)
The subset sum problem
![Page 47: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/47.jpg)
Definition (subset sum problem)
The subset sum problem
Given a set of integers S = {v1, . . . , vn} and a target number t,
is there a subset R ⊆ S such that ∑v∈R
v = t .
Examples
• S = {-9, -3, 4, 7, 12} and t=10 ⇒ there is a
solution R= {-9, 7, 12}
• S = {-9, -3, 4, 7, 12} and t=6 ⇒ no solution exists
Is this problem easy to solve efficiently?
![Page 48: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/48.jpg)
How can recursion help us to solve this
problem?
The subset sum problem
![Page 49: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/49.jpg)
The subset sum problem• Example: S = {-9, -3, 4, 7, 12} and t=10
• Start by picking arbitrary element, e.g., -9
![Page 50: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/50.jpg)
The subset sum problem• Example: S = {-9, -3, 4, 7, 12} and t=10
• Start by picking arbitrary element, e.g., -9
• If there is a solution R, then -9 either (1.) belongs to or (2.) doesn’t belong to the set R:
1. Since -9 doesn’t belong to R, then S \ {-9} = {-3, 4, 7, 12} must have a subset that sums up to t
2. Since -9 belongs to R, then S \ {-9} = {-3, 4, 7, 12} must have a subset that sums up to t - (-9) = 19
![Page 51: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/51.jpg)
The subset sum problem
• We are still missing the base cases, which determine where the recursion stops
• These are:
• If S = ∅ and t = 0 then R = ∅ is a solution (or more generally if t=0 then R = ∅ is a solution)
• If S = ∅ and t ≠ 0, then there is no solution
![Page 52: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/52.jpg)
The subset sum problemDoes {-9,-3,7,12} have a subset with sum 10?
Does {-3,7,12} have a subset with sum 10?
Does {7,12} have a subset with sum 10?
Does {12} have a subset with sum 10?
Does {} have a subset with sum 10? No!
Does {} have a subset with sum 10-12=-2? No!
=> No!
Does {12} have a subset with sum 10-7 = 3?
Does {} have a subset with sum 3? No!
Does {} have a subset with sum 3-12=-9? No! => No!
=> No!
Does {7,12} have a subset with sum 10- -3 = 13?
[Many rows removed ...]
=> No!
Does {-3,7,12} have a subset with sum 10- -9 = 19?
Does {7,12} have a subset with sum 19?
Does {12} have a subset with sum 19?
[Few lines omitted ...]
=> No!Does {12} have a subset with sum 19-7=12?
Does {} have a subset with sum 12? **No**
Does {} have a subset with sum 12-12=0? **Yes**
=> Yes!, {12}!
The branch in which we included $7$ has a solution **Yes, {7,12}**
The branch in which we omitted $-3$ has a solution **Yes, {7,12}**
The branch in which we included $-9$ has a solution **Yes, {-9,7,12}**
![Page 53: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/53.jpg)
The subset sum problemDoes {-9,-3,7,12} have a subset with sum 10?
Does {-3,7,12} have a subset with sum 10?
Does {7,12} have a subset with sum 10?
Does {12} have a subset with sum 10?
Does {} have a subset with sum 10? No!
Does {} have a subset with sum 10-12=-2? No!
=> No!
Does {12} have a subset with sum 10-7 = 3?
Does {} have a subset with sum 3? No!
Does {} have a subset with sum 3-12=-9? No! => No!
=> No!
Does {7,12} have a subset with sum 10- -3 = 13?
[Many rows removed ...]
=> No!
Does {-3,7,12} have a subset with sum 10- -9 = 19?
Does {7,12} have a subset with sum 19?
Does {12} have a subset with sum 19?
[Few lines omitted ...]
=> No!Does {12} have a subset with sum 19-7=12?
Does {} have a subset with sum 12? **No**
Does {} have a subset with sum 12-12=0? **Yes**
=> Yes!, {12}!
The branch in which we included $7$ has a solution **Yes, {7,12}**
The branch in which we omitted $-3$ has a solution **Yes, {7,12}**
The branch in which we included $-9$ has a solution **Yes, {-9,7,12}**
![Page 54: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/54.jpg)
The subset sum problemdef solve(set: Set[Int], target: Int, elementSelector: (Set[Int], Int) => Int = selectElementSimple): Option[Set[Int]] = {def inner(s: Set[Int], t: Int): Option[Set[Int]] = { if (t == 0) return Some(Set[Int]()) else if (s.isEmpty) return None val e = elementSelector(s, t) val rest = s - e // Search for a solution without e val solNotIn = inner(rest, t) if (solNotIn.nonEmpty) return solNotIn // Search for a solution with e val solIn = inner(rest, t - e) if (solIn.nonEmpty) return Some(solIn.get + e) // No solution found here, backtrack return None}inner(set, target)}
• Let the computer do the heavy lifting:
![Page 55: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/55.jpg)
The subset sum problem• The tree of recursive function call
• execution proceed from top to bottom, left branch before the right branch
• The gray branch is not reached before we find the solution
inner({-9,-3,7,12}, 10)
inner({-3,7,12}, 10)
omit -9
inner({-3,7,12}, 19)
take -9
inner({7,12}, 10)
omit -3
inner({7,12}, 13)
take -3
inner({7,12}, 19)
omit -3
inner({7,12}, 22)
take -3
inner({12}, 10)
omit 7
inner({12}, 3)
take 7
inner({12}, 13)
omit 7
inner({12}, 6)
take 7
inner({}, 10)
omit 12
inner({}, -2)
take 12
inner({}, 3)
omit 12
inner({}, -9)
take 12
inner({}, 13)
omit 12
inner({}, 1)
take 12
inner({}, 6)
omit 12
inner({}, -6)
take 12
inner({12}, 19)
omit 7
inner({12}, 12)
take 7
inner({}, 19)
omit 12
inner({}, 7)
take 12
inner({}, 12)
omit 12
inner({}, 0)
take 12
![Page 56: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/56.jpg)
The subset sum problem• Adding pruning of branches to the inner function
else if (s.filter(_ > 0).sum < t || s.filter(_ < 0).sum > t) // The positive (negative) number cannot add up (down) to treturn None
inner({-9,-3,7,12}, 10)
inner({-3,7,12}, 10)
omit -9
inner({-3,7,12}, 19)
take -9
inner({7,12}, 10)
omit -3
inner({7,12}, 13)
take -3
inner({7,12}, 19)
omit -3
inner({7,12}, 22)
take -3
inner({12}, 10)
omit 7
inner({12}, 3)
take 7
inner({12}, 13)
omit 7
inner({12}, 6)
take 7
inner({}, 10)
omit 12
inner({}, -2)
take 12
inner({}, 3)
omit 12
inner({}, -9)
take 12
inner({}, 6)
omit 12
inner({}, -6)
take 12
inner({12}, 19)
omit 7
inner({12}, 12)
take 7
inner({}, 12)
omit 12
inner({}, 0)
take 12
![Page 57: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/57.jpg)
On computationally hard problems
• The recursion tree in our programs (and hence the run time) will grow exponentially with the size of the set S
• For example, can you solve: S = {316, 5356, 190, 1261, 4474, 4159, 6238, 442, 1702, 820, 3214, 2962, 1765, 5419, 6049, 4852, 4285, 5356, 4348, 1072, 5167, 2899, 1198, 6301, 2962, 2773, 4096, 5860, 3718, 6931, 1513, 4348, 1954, 5104, 4474, 3529, 4348, 3655, 5671, 1261,
6238, 3340, 1450, 1513, 1261, 1261, 5797, 4159, 3718, 5923} and t = 92105?
• Subset sum is an NP-complete problem
• The correctness of the solution is easy to check
• There is no known way of solve the problem in polynomial time
• If we could solve this problem in polynomial time, then we could solve all of them in polynomial time
• There are thousands of known NP-complete problems
• There has been a large number of attempts to find a polynomial time algorithm for them
![Page 58: Programming abstractions and analysis –– recursiona1120.cs.aalto.fi/slides/lecture8.pdf · 2. Bits and data 3. Combinational logic 4. Sequential logic 5. Programmable computer](https://reader034.vdocument.in/reader034/viewer/2022043017/5f81e02ab360241e1f62042f/html5/thumbnails/58.jpg)
Exercises• Linked lists
–– Extending the linked list data structure defined in the lectures
• Expressions continued–– Extending the expressions data structure defined in the lectures
• Group scheduling–– Solving scheduling problems recursively
• Sudoku solver–– Solving sudoku instances recursively
• Subset sums with dynamic programming (challenge problem) –– In some cases dynamic programming is better solution method than recursion for subset sum