Advanced Recursion Design by Contract Data Abstraction
Advanced Recursion
Recursive Function Example: Factorial Problem: calculate n! (n factorial) n! = 1 if n = 0 n! = 1 * 2 * 3 *...* n if n > 0 Recursively: if n = 0, then n! = 1 if n > 0, then n! = n * (n-1)!
Solving Recursive Problems See recursive solutions as two sections: –Current –Rest N! = N * (N-1)! 7! = 7 * 6! 7! = 7 * (6 * 5 * 4 * 3 * 2 * 1 * 1)
Factorial Function function Factorial returnsa Num (n isoftype in Num) // Calculates n factorial, n! // Precondition: n is a non-negative // integer if (n = 0) then Factorial returns 1 else Factorial returns n * Factorial(n-1) endif endfunction //Factorial
if (0 = 0) then Fact returns 1 else Fact returns 1 endif endfunction //Fact if (0 = 0) then Fact returns 1 else Fact returns 1 endif endfunction //Fact if (1 = 0) then Fact returns 1 else Fact returns 1 * Fact(0) endif endfunction //Fact if (1 = 0) then Fact returns 1 else Fact returns 1 * Fact(0) endif endfunction //Fact if (2 = 0) then Fact returns 1 else Fact returns 2 * Fact(1) endif endfunction //Fact if (2 = 0) then Fact returns 1 else Fact returns 2 * Fact(1) endif endfunction //Fact if (3 = 0) then Fact returns 1 else Fact returns 3 * Fact(2) endif endfunction //Fact if (3 = 0) then Fact returns 1 else Fact returns 3 * Fact(2) endif endfunction //Fact algorithm Test ans <- Fact(3) endalgorithm algorithm Test ans <- Fact(3) endalgorithm
Tracing Details function Fact returnsa Num (2) if (2 = 0) then Fact returns 1 else Fact returns 2 * Fact(1) endif endfunction //Fact function Fact returnsa Num (2) if (2 = 0) then Fact returns 1 else Fact returns 2 * Fact(1) endif endfunction //Fact 1. Actual parameters stored on the stack 2. Recursive call to Fact 4. Unfinished Business 3. Create a new Stack Frame 5. Return value and release stack frame
Activation Stack for Factorial Call the function: answer <- Fact(5) Main Algorithm: Unfinished: answer <- Fact (5)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2) Fact. 4th: N=2, Unfinished: 2*Fact(1)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2) Fact. 4th: N=2, Unfinished: 2*Fact(1) Fact. 5th: N=1, Unfinished: 1*Fact(0)
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2) Fact. 4th: N=2, Unfinished: 2*Fact(1) Fact. 5th: N=1, Unfinished: 1*Fact(0) Fact. 6th: N=0, Finished: returns 1
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2) Fact. 4th: N=2, Unfinished: 2*Fact(1) Fact. 5th: N=1, Finished: returns 1*1
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Unfinished: 3*Fact(2) Fact. 4th: N=2, Finished: returns 2*1
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Unfinished: 4*Fact(3) Fact. 3rd: N=3, Finished: returns 3*2
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Unfinished: 5*Fact(4) Fact. 2nd: N=4, Finished: returns 4*6
Activation Stack for Factorial Main Algorithm: Unfinished: answer <- Fact (5) Fact. 1st: N=5, Finished: returns 5*24
Activation Stack for Factorial Main Algorithm: Finished: answer <- 120
Exponentiation base exponent e.g. 5 3 Could be written as a function Power(base, exp) LB
Can we write it recursively? b e = b * b (e-1) What’s the limiting case? When e = 0 we have b 0 which always equals? 1 LB
Another Recursive Function function Power returnsa Num (base, exp isoftype in Num) // Computes the value of Base Exp // Pre: exp is a non-negative integer if (exp = 0) then Power returns 1 else Power returns base * Power(base, exp-1) endif endfunction //Power
Function Power returnsa Num (base, exp isoftype in Num) //Computes the value of Base Exp //Preconditions: exp is a non-negative integer if(exp = 0 ) then Power returns 1 else Power returns (base * Power(base, exp – 1)) endif endfunction //Power Activations Stack Example Algo: total <- Power(3,4) Power base = 3 exp = 4 3 *Power(3,3)Power base = 3 exp = 3 3 *Power(3,2)Power base = 3 exp = 2 3 *Power(3,1)Power base = 3 exp = 1 3 *Power(3,0) 1 Power base = 3 exp = 0 Finished: 1 Total <
Bunnies? The Time: 13th Century The Place: Italy The Man: Fibonacci The Problem: We start with a pair of newborn rabbits. At the end of the 2nd month, and each month thereafter the female gives birth to a new pair of rabbits: one male and one female. The babies mature at the same rate as the parents and begin to produce offspring on the same schedule. So how many rabbits do we have at the end of one year? LB
= 1 pair bunnies (m/f) LB
Fibonacci Number Sequence if n = 1, then Fib(n) = 1 if n = 2, then Fib(n) = 1 if n > 2, then Fib(n) = Fib(n-2) + Fib(n-1) Numbers in the series: 1, 1, 2, 3, 5, 8, 13, 21, 34,... A More Complex Recursive Function
Fibonacci Sequence Function function Fib returnsa Num (n iot in Num) // Calculates the nth Fibonacci number // Precondition: N is a positive integer if ((n = 1) OR (n = 2)) then Fib returns 1 else Fib returns Fib(n-2) + Fib(n-1) endif endfunction //Fibonacci
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4) Fib(3): Fib returns Fib(1) + Fib(2)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4) Fib(3): Fib returns Fib(1) + Fib(2) Fib(1): Fib returns 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4) Fib(3): Fib returns 1 + Fib(2)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4) Fib(3): Fib returns 1 + Fib(2) Fib(2): Fib returns 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns Fib(3) + Fib(4) Fib(3): Fib returns 1 + 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns Fib(2) + Fib(3)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns Fib(2) + Fib(3) Fib(2): Fib returns 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3) Fib(3): Fib returns Fib(1) + Fib(2)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3) Fib(3): Fib returns Fib(1) + Fib(2) Fib(1): Fib returns 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3) Fib(3): Fib returns 1 + Fib(2)
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3) Fib(3): Fib returns 1 + Fib(2) Fib(2): Fib returns 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + Fib(3) Fib(3): Fib returns 1 + 1
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + Fib(4) Fib(4): Fib returns 1 + 2
Tracing with Multiple Recursive Calls Main Algorithm: answer <- Fib(5) Fib(5): Fib returns 2 + 3
Tracing with Multiple Recursive Calls Main Algorithm: answer <- 5
Mutual Recursion Recursion doesn’t always occur because a routine calls itself... Mutual Recursion occurs when two routines call each other. A B B A
Mutual Recursion Example Problem: Determine whether a number, N, is odd or even. If N is equal to 0, then n is even N is odd if N-1 is even
Example Implementation function Odd returnsa Boolean (n iot in Num) if (n = 0) then Odd returns FALSE else Odd returns Even (n - 1) endif endfunction //Odd function Even returnsa Boolean (n iot in Num) if (n = 0) then Even returns TRUE else Even returns Odd (n - 1) endif endfunction //Even
Building Recursive Modules Decide on the terminating condition Decide the final actions when you terminate –There may be none –If you are computing something, this usually starts a “ripple” of returned data Decide how to take a step closer to terminating –think about how to make your original, complex problem simpler Decide how to call a “clone” of this module Decide what work to do at this step in recursion
Questions?
Design by Contract
An Example Module function Fibonacci returnsa Num (n isoftype in num) // Returns Nth fibonacci number if ((n=0) OR (n=1)) then Fibonacci returns 1 else Fibonacci returns Fibonacci(n-1) + Fibonacci(n-2) endif endfunction // Fibonacci
Correctness What if we call it via: some_var <- Fibonacci(-1) or some_var <- Fibonacci(4.3) The module will never reach its terminating condition and will never end!
A First Try at a Correction function Fibonacci returnsa num (n iot in num) // Returns Nth fibonacci number // Ensure n is a non-negative integer if ((n 0)) then print( "Illegal n in function!" ) else // Old module body follows... if((n = 0) OR (n = 1)) then Fibonacci returns 1 else New_Fibonacci returns Fibonacci(n-1) + Fibonacci(n-2) endif endfunction // Fibonacci
But That Solution Had Problems! Inefficient because we’re doing the test unnecessarily at each iteration Functions must always return a value Mixes “correctness” part with “carrying out algorithm” part Doing more than one thing Functions cannot print!
Design by Contract Like a business contract (house-building) The module’s client is the caller of the module. The supplier is the called module The pre-condition states the responsibilities of the client The post-condition states the guarantees made by the supplier
Module Documentation as a Contract Make both client and supplier responsibilities explicit as part of documentation. Both client and supplier know what is expected of them and what to expect of the other. function Fibonacci returnsa num(n isoftype in num) // Purpose: Returns nth fibonacci number // Pre-condition: n is a non-negative integer // Post-condition: The returned number is the nth // number in the fibonacci // sequence
Questions?
Introduction to Data Abstraction: Records
Where ya been? Krogers. What’s in the bag? Answer 1 –beans –canned peaches –bread –chips –coke –dental floss –ice cream –shoe polish –John Tesh CD –carrots Answer 2 –groceries LB
Data Abstraction Separating the essence from the particular instance or implementation. The most fundamental technique: –Descriptive identifiers that are brief and informative (not cryptic or cute) Example: For the purpose of representing an employee’s ID number, which one features the highest data abstraction? this_num isofype Num someone_num isoftype Num employee_ID_num isoftype Num
Data Abstraction Data Abstraction allows us to: –Combine data that belongs together –Consider the logical grouping of data –Apart from the specific implementation Records are the mechanism for data grouping. –Defined in the algorithm –A heterogeneous collection of data (we can mix different types of data in a single structure)
Representing Related Data Information concerning a student: name isoftype String age isoftype Num is_male isoftype Boolean letter_grade isoftype Char This should be abstractly grouped together!
Grouping Related Data Together name (String) age (Num) is_male (Boolean) Letter_grade (Char)
Record Definition Example Student_Type definesa record name isoftype String age isoftype Num is_male isoftype Boolean letter_grade isoftype Char endrecord // Student_Type Instead of a collection of 4 variables, we have defined a new data type and now have the ability to create variables of Student_Type, each with the appropriate information.
Record Templates Definition: definesa record endrecord Declaration: isoftype LB
Record Example Student_Type definesa record name isoftype String age isoftype Num is_male isoftype Boolean letter_grade isoftype Char endrecord // Student_Type Stu1, Stu2, Stu3 isoftype Student_Type This creates three variables of type Student_Type. Each of these variables contains information about name, age, is_male, and letter_grade.
Creating New Data Types We use records to construct new “user- defined” data types The newly created data type is treated like any of the “built-in” types Cannot read or print user defined data types directly. Since the new type is new to the language, read and print don’t know how to process the request. Creating variables of a new data type is a two-step process: define & then declare Creating the new data type does not provide any variables, only the template by which variables may then be declared.
Creating New Data Types Example Task: Create a database to keep track of the kids in an elementary school To make the example simple, we will only store: Student number Age Grade in math this year
Step 1: Define the new type: Kid_Record definesa Record student_num isoftype Num age isoftype Num grade isoftype Char endrecord Step 2: Declare Record variables of this new type: mary, zach, herbert isoftype Kid_Record best_grade isoftype Char Creating New Data Types Example
Accessing Fields of Records We can access the individual fields of a record using the dot (. ) operator:. Examples: mary.student_num < mary.age <- 13 mary.grade <- ‘A’ best_grade <- mary.grade zach.grade <- best_grade zach.age <- 12
Operators and Records Assignment works just as before with built-in types (both sides must be of the same type): // assigns ALL fields in single op zach <- mary Printing and reading can only take built-in types as parameters, so we print and read fields, not records. read(zach.student_num, zach.age, zach.grade) print(zach.student_num, zach.age, zach.grade)
Using New Data Types Note: Items of the form some_record_var.some_field are L-values - they may appear on the left of an assignment & may be passed as in, in/out, or out parameters.
Combining Abstractions Often, data abstractions (for example, records) need corresponding procedural abstractions (modules) as helpers. Common examples include: –Modifier modules: procedures used to read in or update data values –Accessor modules: procedures used to print out information in a record, or functions which return data values to other modules We will revisit this idea when we talk about behavioral abstractions later in the course
Combining Abstractions For example: Student_Rec definesa Record name isoftype String ssn isoftype Num num_hours isoftype Num endrecord procedure Print_Student(somebody isoftype in Student_Rec) // An accessor module which prints out all // data values stored in a Student_Rec print(“Name: ”, somebody.name) print( “SSN: ”, somebody.ssn ) print( “Hours:”, somebody.num_hours ) endprocedure
Combining Abstractions procedure Read_Student (somebody isoftype out Student_Rec) // A modifer module which obtains data // values and stores them in a Student_Rec print(“Enter the student’s name: ” ) read( somebody.name ) print(“Enter the student’s ssn: ” ) read( somebody.ssn ) print( “Enter the student’s hours taken: ” ) read( somebody.num_hours ) endprocedure // Read_Student
Combining Abstractions... Student_1 isoftype Student_Rec // call to modifier Read_Student(Student_1) // call to accessor Print_Student(Student_1)... Student_2 isoftype Student_Rec // call to modifier Read_Student(Student_2) // call to accessor Print_Student(Student_2)
Records Within Records There is nothing to prevent us from placing records inside of records (a field within a record): Date_Type definesa record day, month, year isoftype num Endrecord Student_Type definesa record name isoftype string gpa isoftype num birth_day isoftype Date_Type graduation_day isoftype Date_Type endrecord
Record Within Records Date_Type: Student_Type: bob isoftype Student_Type bob.birth_day.month <- 6 day month year namegpa day month year birth_day graduation_day
Types vs. Variables TYPE Definitions –Create templates for new kinds of variables –Do not create a variable – no storage space is allocated –Have unlimited scope VARIABLE Declarations –Actually create storage space –Have limited scope - only module containing the variable can “see” it –Must be based on an existing data type
Anonymous Data Types This_Record isoftype record silly isoftype Num stupid isoftype Char endrecord // This_Record Anonymous data types combine the declaration of a complex variable with a data type definition. Thus, the data type itself has no name. Some languages allow this Removes benefits of data typing Is in general bad; do not do this even if a language allows it
Anonymous Data Types If two different variables are anonymous data types with identical structure, then they can hold the same data, but: They are not of the same type; they are of no type Cannot assign one to another Cannot pass grouped data between them via parameter Must assign field-by-field Must make changes in multiple places Don’t do it!! In our pseudo-code, cannot do it
Questions?