130 likes | 283 Views
Conditionals and Recursion. "To iterate is human, to recurse divine." - L. Peter Deutsch. If, When, Unless. We’ve already learned about LISP’s if construct ( if test then-result else-result) The else-result is actually optional, with a default return value of NIL
E N D
Conditionals and Recursion "To iterate is human, to recurse divine." - L. Peter Deutsch
If, When, Unless • We’ve already learned about LISP’s if construct (if test then-result else-result) • The else-result is actually optional, with a default return value of NIL • There are also macros for similar conditionals: (when test result) (unless test result) • when returns its result if the test is true, NIL otherwise • unless returns its result when the test is not true, NIL otherwise
Cond • A more useful construct that allows for choosing between multiple conditions is cond (cond (test S-expression*)*) >(cond ((> 5 20) 'wrong) ((< 5 20) 'right) (t 'not-reached)) RIGHT • Good programming style always includes a t condition at the end • Default return value is NIL
Recursion • A function calls itself • An extremely powerful and expressive technique • Easier to do in LISP than in imperative languages (Java, C++), but still challenging at first • Using LISP will give you new respect for and understanding of recursion
Example 1: Height of a Binary Tree (defun tree-height (x) (if (atom x) ;Test 0 ;Base Case (1+ (max (tree-height (car x)) ;Recursive Case (tree-height (cdr x)))) ))
Cases • Any working recursive function must have at least two cases: • Base Case: • Indicates when to stop • Smallest possible problem instance (empty list, zero, etc.) • Recursive Case: • Calls the function again, but on different arguments • Formally, the arguments in recursive calls must be “smaller” in some way (shorter list, closer to zero, etc.) • May have multiple base and/or recursive cases
Example 2: Average (defun count (x) ; Same as LISP’s length function (if (atom x) 0 (1+ (count (cdr x))) )) (defun sum (x) (if (atom x) 0 (+ (car x) (sum (cdr x))) )) (defun average (x) (/ (sum x) (count x)))
Tail Recursion • Tail recursive functions do no work after the recursive call. The function returns whatever the recursive call returns • Can be executed more efficiently by the LISP system • Often accomplished by introducing an extra accumulator parameter
Example 3: Tail Recursive Average (defun count (x acc) (if (atom x) acc (count (cdr x) (1+ acc)) )) (defun sum (x acc) (if (atom x) acc (sum (cdr x) (+ (car x) acc)) )) (defun average (x) (/ (sum x 0) (count x 0)))
Example 4: Single Pass Average • The last example still had to traverse the list twice: once for count and once for sum • Why not give average accumulators? (defun average-help (x count sum) (if (atom x) (/ sum count) (average-help (cdr x) (1+ count) (+ (car x) sum)) )) (defun average (x) (average-help x 0 0))
Conclusion on Tail Recursion • Often need to introduce accumulator(s) and helper functions to hide accumulators from users • Not every recursive function can be rewritten using tail recursion • For example, tree-height has two recursive calls
Trace • When debugging, it is often useful to trace calls of the recursive function (trace function-name) • Causes every call of the recursive function to be printed along with its call depth • Turn off using untrace (untrace function-name)
Trace Example >(trace average-help) (AVERAGE-HELP) >(average '(5 1 30 4)) 1> (AVERAGE-HELP (5 1 30 4) 0 0) 2> (AVERAGE-HELP (1 30 4) 1 5) 3> (AVERAGE-HELP (30 4) 2 6) 4> (AVERAGE-HELP (4) 3 36) 5> (AVERAGE-HELP NIL 4 40) <5 (AVERAGE-HELP 10) <4 (AVERAGE-HELP 10) <3 (AVERAGE-HELP 10) <2 (AVERAGE-HELP 10) <1 (AVERAGE-HELP 10) 10 >(untrace average-help) (AVERAGE-HELP)