Lecture 11 Slides - Intro to Recursion

1 of 54

Lecture 11

Recursion - Approaching Complex Problems in a Different Way

2 of 54

Difficulty curve of the course

Writing

functions

Lists and

higher order functions

Recursive functions

Imperative programming

and object-oriented programming

3 of 54

Programming as com/position

  • Expression composition
    • Primitive expressions: Constants, variables
    • Compound expressions: Function calls, special forms
  • Function composition
    • Primitive functions:�+,-,*,/, rectangle, overlay, etc.
    • Compound functions:�Created by λ expressions
  • Image composition
    • Primitive images: Rectangles, ellipses
    • Compound images: Overlays, iterated-overlays

4 of 54

Levels of abstraction

  • Functions are defined in terms of simpler functions
    • That are defined in terms of simpler ones
      • That are defined in terms of simpler ones
        • And so on

  • You can never completely describe a function

computing tip

multiplication

addition

single-digit addition

counting

5 of 54

The story so far

We’ve been focusing on

  • Building complicated constructions
  • From simple prefab primitives

6 of 54

Algorithm design

  • Now we're going to go in the other direction
    • Reduce some of our “primitive” components to even simpler ones
  • Recursion
    • General technique for building algorithms
    • Really just means “function calls”, which you already know about
  • Minimalism
    • Important theme in computer science
    • What's the simplest, most versatile set of primitive components we can start from?

7 of 54

How do we write an iterator?

  • We've used a lot of iterators
    • iterated-beside, map, filter, etc.
  • And we’ve defined new iterators in terms of old ones
  • But it turns out our “primitive” iterators aren’t primitive

8 of 54

Universality

  • Today, we'll show how
    • To build iteration
    • From non-iterative primitives
  • Turing-complete system
    • Function calls
    • λ expressions
    • If expressions

(Actually, Church proved you don't even need if expressions!)

9 of 54

Summing a list

10 of 54

Simple example: summing a list

  • Let's start with something simpler than foldl or iterated-beside

  • Summing a list

> (sum-list (list 1 2 3))

6

11 of 54

Simple list accessors

(first list), (second list), …, (eighth list)�;; first, etc. : (listof T) -> T��Extracts the specified element of list

(rest list)�;; rest: (listof T) -> (listof T)

Returns all but the first element of list

12 of 54

Summing a 2-element list

  • Let's start by assuming the list only has two elements
  • Then we can just add them
  • Easy!

;; sum-list-2:

;; (list number number) -> number

;; Sums a two-element list

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list)))

(check-expect (sum-list-2 (list 1 2))

3)

13 of 54

Summing a 3-element list

  • What about a three element list?
  • Same thing!

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(second a-list)

(third a-list))))

14 of 54

Summing a 3-element list

  • But remember, we want to be lazy!
  • Why rewrite the part of our program that calculates 2-element lists…while writing the program for 3-element lists?
  • But we can also define sum-list-3 in terms of sum-list-2

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2 (rest

a-list)))))

15 of 54

Summing a 4-element list

  • And we can use the same trick to sum a 4 element list
    • Add the first element
    • To the sum of the other three

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2

(rest a-list)))))

(define sum-list-4

(lambda (a-list)

(+ (first a-list)

(sum-list-3

(rest a-list)))))

16 of 54

Summing a 5-element list

  • And the same again for a 5-element list
    • Add the first element
    • To the sum of the rest of the elements

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2

(rest a-list)))))

(define sum-list-4

(lambda (a-list)

(+ (first a-list)

(sum-list-3

(rest a-list)))))

(define sum-list-5

(lambda (a-list)

(+ (first a-list)

(sum-list-4

(rest a-list)))))

17 of 54

We can sum any number of elements this way

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(second a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2

(rest a-list)))))

(define sum-list-4

(lambda (a-list)

(+ (first a-list)

(sum-list-3

(rest a-list)))))

(define sum-list-5

(lambda (a-list)

(+ (first a-list)

(sum-list-4

(rest a-list)))))

(define sum-list-6

(lambda (a-list)

(+ (first a-list)

(sum-list-5

(rest a-list)))))

(define sum-list-7

(lambda (a-list)

(+ (first a-list)

(sum-list-6

(rest a-list)))))

(define sum-list-8

(lambda (a-list)

(+ (first a-list)

(sum-list-7

(rest a-list)))))

(define sum-list-9

(lambda (a-list)

(+ (first a-list)

(sum-list-8

(rest a-list)))))

18 of 54

Summing a list under the substitution model

19 of 54

A quick aside: quoting lists

  • Double-quotes let you type a string as a constant in your code

  • "this is a test string"
    • Means a string
    • Whose characters are t,h,i,s, space, i, s, etc.
  • A single quote lets you type a list as a constant
  • '(1 2 3 4)
    • Means a list
    • Whose elements are: 1,2,3, & 4

  • Note that we don't need a close quote because the parens tell Racket where the list ends

Note: I'm going to use quoting to simplify the following slides, it's never required or necessary to quote.

Warning: this works with CONSTANTS but provides strange behavior with more complex expressions.

20 of 54

Execution under the substitution model

(sum-list-4 (list 1 2 3 4))

  • (sum-list-4 '(1 2 3 4))
  • (+ (first '(1 2 3 4))

(sum-list-3 (rest '(1 2 3 4))))

  • (+ 1

(sum-list-3 '(2 3 4)))

  • (+ 1

(+ (first '(2 3 4))

(sum-list-2 (rest '(2 3 4)))))

  • (+ 1

(+ 2

(sum-list-2 '(3 4))))

  • (+ 1

(+ 2

(+ (first '(3 4))

(sum-list-1 (rest '(3 4)))))

  • (+ 1

(+ 2

(+ 3

(sum-list-1 '(4)))))

  • (+ 1

(+ 2

(+ 3

(+ (first '(4))

(sum-list-0

(rest '(4)))))))

  • (+ 1

(+ 2

(+ 3

(+ 4

(sum-list-0

'())))))

21 of 54

Execution under the substitution model

  • (+ 1

(+ 2

(+ 3

(+ 4

0))))

  • (+ 1

(+ 2

(+ 3

4)))

  • (+ 1

(+ 2

7))

  • (+ 1

9)

  • 10

22 of 54

But this is still a dumb way to write it

  • Have to write a separate function for each length
    • That's a lot of typing…
  • Might not know the length of the list in advance
    • So how do we know which function to call?
  • Code for most the functions is nearly the same

(define sum-list-0

(lambda (a-list)

0))

(define sum-list-1

(lambda (a-list)

(+ (first a-list)

(sum-list-0 (rest a-list)))))

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(sum-list-1 (rest a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2 (rest a-list)))))

23 of 54

Recursion

24 of 54

Recursion

  • Writing functions that call themselves
  • A recursive function is just one where, in its definition, it calls itself.
  • Basic idea
    • Most problems can be broken down into smaller problems
    • Often those subproblems are simpler or smaller versions of the original problem

General outline

  • Check if it's a hard problem or an easy problem
    • Hard problem?
      • Break it down into simpler versions of itself
      • Call myself on the simpler versions
    • Easy problem?
      • Just do it

25 of 54

Recursively�summing a list

26 of 54

We can sum any number of elements this way

(define sum-list-0

(lambda (a-list)

0))

(define sum-list-1

(lambda (a-list)

(+ (first a-list)

(sum-list-0 (rest a-list)))))

(define sum-list-2

(lambda (a-list)

(+ (first a-list)

(sum-list-1 (rest a-list))))

(define sum-list-3

(lambda (a-list)

(+ (first a-list)

(sum-list-2 (rest a-list)))))

(define sum-list-4

(lambda (a-list)

(+ (first a-list)

(sum-list-3 (rest a-list)))))

(define sum-list-5

(lambda (a-list)

(+ (first a-list)

(sum-list-4 (rest a-list)))))

(define sum-list-6

(lambda (a-list)

(+ (first a-list)

(sum-list-5 (rest a-list))))

(define sum-list-7

(lambda (a-list)

(+ (first a-list)

(sum-list-6 (rest a-list)))))

27 of 54

A better way to write it

  • Just one function
  • Calls itself on the rest of the list
    • And adds the first element
  • Unless the list is empty
    • empty? returns true if the length of a list is 0

;; sum-list:

;; (listof number) -> number

;; Total of elements of a list

(define sum-list

(lambda (a-list)

(if (empty? a-list)

0

(+ (first a-list)

(sum-list (rest a-list))))))

(check-expect

(sum-list (list 1 2 3 4))

10)

(check-expect (sum-list (list)) 0)

Note: in the video, this function call was missing

28 of 54

Execution under the substitution model

  • (sum-list (list 1 2 3 4))

  • (sum-list '(1 2 3 4))

  • (if (empty? '(1 2 3 4))

0

(+ (first '(1 2 3 4))

(sum-list (rest '(1 2 3 4)))))

  • (if false

0

(+ (first '(1 2 3 4))

(sum-list (rest '(1 2 3 4)))))

  • (+ (first '(1 2 3 4))

(sum-list (rest '(1 2 3 4)))

(+ 1

(sum-list '(2 3 4)))

  • (+ 1

(if (empty? '(2 3 4))

0

(+ (first '(2 3 4))

(sum-list (rest '(2 3 4))))))

  • (+ 1

(if false

0

(+ (first '(2 3 4))

(sum-list (rest ''(2 3 4))))))

  • (+ 1

(+ (first '(2 3 4))

(sum-list (rest '(2 3 4)))))

  • (+ 1

(+ 2

(sum-list '(3 4))))

29 of 54

Execution under the substitution model

  • (+ 1

(+ 2

(sum-list '(3 4))))

  • (+ 1

(+ 2

(if (empty? '(3 4))

0

(+ (first '(3 4))

(sum-list (rest '(3 4)))))))

  • (+ 1

(+ 2

(if false

0

(+ (first '(3 4))

(sum-list (rest '(3 4)))))))

  • (+ 1

(+ 2

(+ (first '(3 4))

(sum-list (rest '(3 4))))))

  • (+ 1

(+ 2

(+ 3

(sum-list '(4)))))

  • (+ 1

(+ 2

(+ 3

(if (empty? '(4))

0

(+ (first '(4))

(sum-list

(rest '(4))))))))

30 of 54

Execution under the substitution model

  • (+ 1

(+ 2

(+ 3

(+ 4

(sum-list '())))))

  • (+ 1

(+ 2

(+ 3

(+ 4

(if (empty? '())

0

(+ (first '())

(sum-list

(rest '()))))))))

  • (+ 1

(+ 2

(+ 3

(if false

0

(+ (first '(4))

(sum-list

(rest '(4))))))))

  • (+ 1

(+ 2

(+ 3

(+ (first '(4))

(sum-list

(rest '(4)))))))

31 of 54

Execution under the substitution model

  • (+ 1

(+ 2

(+ 3

(+ 4

(if true

0

(+ (first '())

(sum-list

(rest '()))))))))

  • (+ 1

(+ 2

(+ 3

(+ 4

0))))

  • (+ 1

(+ 2

(+ 3

(+ 4

0))))

  • (+ 1

(+ 2

(+ 3

4)))

  • (+ 1

(+ 2

7))

  • (+ 1

9)

  • 10

32 of 54

Basic template�For recursion

33 of 54

Basic template for recursion

  • Recursion is about
    • Solving a complicated problem
    • by solving a simpler version of the problem

  • But you have to stop sometime
    • Stop when you get to “easy” problem

  • You need to write code for
    • Recognizing easy cases
    • Solving easy (base) cases
    • Simplifying the problem (get one step closer to the base/easy cases)
    • Fixing the simplified solution into a solution to the full problem

(define function

(lambda (args)� (if easy-case? solve-easy-case� (fix-solution (function simplified))))

34 of 54

Basic template for recursion

  • You often simplify the problem by splitting it
    • At least one of them will be a recursive call
  • Then the fixup step consists of combining the answers to the two problems

(define function

(lambda (args …) � (if easy-case?solve-easy-case (combine solve-one-part

(function

other-part)))))

35 of 54

Look familiar?

  • Just one function
  • Calls itself on the rest of the list
    • And adds the first element
  • Unless the list is empty
    • empty? returns true if the length of a list is 0

;; sum-list:

;; (listof number) -> number

;; Total of elements of a list

(define sum-list

(lambda (sum-list)

(if (empty? list)

0

(+ (first list)

(sum-list (rest list))))))

(check-expect

(sum-list (list 1 2 3 4))

10)

(check-expect (sum-list (list)) 0)

Correction! (missing function call)

36 of 54

Writing foldl/r

Using recursion

37 of 54

How do we write foldr?

  • sum-list just adds
  • How do we make something more general like foldl/r?

(define sum-list

(lambda (a-list)

(if (empty? a-list)

0

(+ (first a-list)

(sum-list (rest a-list))))))

;; foldr: (X X -> X) X (listof X) -> X

;; Aggregates elements of list

;; using proc.

(define foldr

(lambda (func start list)

???))

38 of 54

How do we write foldr?

  • This is the same pattern as sum-list!
  • Foldr is just further abstracted
    • Change + to func
    • Change 0 to start value
  • We’ll show how to write foldl next time

(define sum-list

(lambda (a-list)

(if (empty? a-list)

0

(+ (first a-list)

(sum-list (rest a-list))))))

;; foldr: (X X -> X) X (listof X) -> X

;; Aggregates elements of list

;; using func.

(define foldr

(lambda (func start list)

(if (empty? list)

start

(func (first list)

(foldr func

start

(rest list))))))

39 of 54

Writing length

Using recursion

40 of 54

Example

  • Write the length function using empty?, +, and rest

;; length: (listof any) -> number�;; Returns number of elements in list

41 of 54

Example

  • Write the length function using empty?, +, and rest

;; length: (listof any) -> number�;; Returns number of elements in list

(define length�(λ (list) � (if (empty? list)� 0 (+ 1(length (rest list))))))

(check-expect (length (list 1 2 3))� 3)

42 of 54

Execution in the substitution model

  • (length (list 1 2 3))

  • (if (empty? '(1 2 3))� 0� (+ 1 (length (rest '(1 2 3)))))
  • (if false� 0� (+ 1 (length (rest '(1 2 3)))))
  • (+ 1 (length (rest '(1 2 3))))
  • (+ 1 (length '(2 3)))
  • (+ 1 (if (empty? '(2 3))� 0� (+ 1 (length (rest '(2 3))))))
  • (+ 1 (if false� 0� (+ 1 (length (rest '(2 3))))))
  • (+ 1 (+ 1 (length (rest '(2 3))))))
  • (+ 1 (+ 1 (length '(3))))
  • (+ 1 (+ 1 (if (empty? '(3))� 0� (+ 1(length(rest '(3)))))))
  • (+ 1 (+ 1 (if false� 0� (+ 1 (length (rest '(3)))))))
  • (+ 1 (+ 1 (+ 1 (length (rest '(3))))))
  • (+ 1 (+ 1 (+ 1 (length '()))))
  • (+ 1 (+ 1 (+ 1 (if (empty? '())� 0� (+ 1 (length

(rest '())))))))

  • (+ 1 (+ 1 (+ 1 (if true� 0� (+ 1 (length

(rest '())))))))

  • (+ 1 (+ 1 (+ 1 0)))
  • (+ 1 (+ 1 1))
  • (+ 1 2)
  • 3

43 of 54

Sussman-form definitions

44 of 54

This is a good time to mention Sussman-form definitions

(define (function-name inputs …)�output)

  • Used extensively in the books
  • Sussman form is a more "readable shorthand" for:� (define function-name� (λ (inputs …) output))

  • Makes the part after define look like a call to the function so just beware…
  • It means exactly the same thing
    • The computer processes it by first translating it into the long form, and then executing it.

45 of 54

Writing iterated-beside

Using recursion

46 of 54

Example

  • Write iterated-beside as a recursion (without using apply)

;; iterated-beside: (number -> picture) number -> picture

;; Makes a horizontal series of pictures using�;; generator function

47 of 54

Example

  • Easy case:
    • When count is zero, make a blank picture

(define (iterated-beside func count)�(if (= count 0)empty-image ; this is a magic var with a blank image

to be filled in ))

48 of 54

Example

  • Recursive case:
    • Make one picture

(define (iterated-beside func count)�(if (= count 0)� empty-image� (func count)))

49 of 54

Example

  • Recursive case:
    • Make the rest of the pictures

(define (iterated-beside func count)�(if (= count 0)� empty-image

(combine (func count) (iterated-beside func (- count 1)))))

50 of 54

Example

  • Recursive case:
    • Make the rest of the pictures
    • And combine them

(define (iterated-beside func count)�(if (= count 0)� empty-image

(beside (func (- count 1)) ; we need to subtract 1 here (iterated-beside func (- count 1)))))

51 of 54

Execution is recursive

52 of 54

Rules of computation in Racket

Look at the expression

  • If it’s a constant (i.e. a number or string), it’s its own value
  • If it’s a variable name (i.e. a word, hyphenated phrase, etc.), look its value up in the dictionary
  • Parentheses? (Check if it’s one of the special cases from the next slide)
  • Otherwise it’s a function call
    • (func-expression arg-expression1arg-expressionn)
    • Execute all the subexpressions�(func-expression and arg-expression1 through arg-expressionn)
    • Call the value of func-expression, passing it the values of arg-expression1 through arg-expressionn as inputs
    • Use its output as the value of the expression

53 of 54

Example: running code

  • Executing Racket code is a recursive process too
  • Primitive expressions are easy:
    • Constants are their own values
    • Variables you look up in the dictionary
  • function calls involve recursively executing all the subexpressions

Is it a primitive expression?

    • Yes (simple case)
        • Look up its value
    • No (recursive case)
      • Find the values of all its subexpressions
      • Call
        • The value of the first subexpression
        • With the values of the other subexpressions as inputs

54 of 54

Some code that almost works

(define interpreter

(λ (expression)

(if (number? expression)

expression

(if (name? expression)

(look-it-up expression)

(local [(define values

(map interpreter expression))]

(apply (first values)

(rest values)))))))

This is a teaser for the students completing the advanced tutorials.