programming abstractions and analysis recursion
play

Programming abstractions and analysis recursion 10101011110101 - PowerPoint PPT Presentation

01110111010110 11110101010101 00101011010011 01010111010101 01001010101010 10101010101010 Programming abstractions and analysis recursion 10101011110101 Mikko Kivel 01010101011101 01010111010110 Department of Computer Science


  1. 01110111010110 11110101010101 00101011010011 01010111010101 01001010101010 10101010101010 Programming abstractions and analysis –– recursion 10101011110101 Mikko Kivelä 01010101011101 01010111010110 Department of Computer Science Aalto University 10101101010110 10101110101010 CS-A1120 Programming 2 11101010101101 15 April 2020 01110111010110 10111011010101 11110101010101 Lecture notes based on material created by Tommi 00010101010101 Junttila & Petteri Kaski 01011010101110 10101010100101

  2. 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?

  3. Recursion Definition: see recursion

  4. 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

  5. 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

  6. Palindromes (reminder from O1) 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) } • Animation for a similar code in Programming 1: https://plus.cs.aalto.fi/o1/2019/w12/ch01/

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

  8. 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

  9. Tail calls and recursion

  10. 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: n ! = { when n = 1 1 n × ( n − 1)! when n > 2 def fact(n : Int) : BigInt = { require(n >= 1, "n should be a positive integer") if(n == 1) BigInt(1) else n * fact(n-1) } • Running this will lead to problems: scala> fact(100000) java.lang.StackOverflowError

  11. 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

  12. 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

  13. Tail calls Definition Function call in a method is a tail call if it is the last operation of the function • For example, in 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)

  14. Tail recursion Definition 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

  15. Tail recursion • Lets make a tail recursive version of the factorial function (by using tail recursive inner function): 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)) } • Note the similarity with the iterative version: 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 }

  16. Tail recursion • Expansion of the tail recursive fact function: fact(4) = iterate(4, 1) = iterate(3, 4) = iterate(2, 12) = iterate(1, 24) = iterate(0, 24) = 24 • Same with call stack: the call frame of iterate is reused: 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

  17. @tailrec annotation • The @tailrec annotation in Scala declares that the compiler must optimise tail recursion into iteration import scala.annotation.tailrec ... @tailrec def myFunction(...) { ... } • 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 For more information, see: http://theyougen.blogspot.com/2010/01/why-overrideable-tail-recursive-methods.html

  18. Recursively defined data structures

  19. 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)

  20. Linked lists

  21. 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

  22. 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)

  23. Linked lists, the first implementation abstract 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) }

  24. 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: l Cons Cons Cons Nil head tail head tail head tail 5 4 − 7

Recommend


More recommend