Lecture 9 Slides - Lecture Slides

1 of 53

Lecture 9

Recursion - Approaching Complex Problems in a Different Way

2 of 53

Difficulty curve of the course

Writing

functions

Lists and

higher order functions

Recursive functions

Imperative programming

and object-oriented programming

3 of 53

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 53

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 53

The story so far

We’ve been focusing on

  • Building complicated constructions
  • From simple prefab primitives

​

6 of 53

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 53

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 53

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 53

Summing a list

10 of 53

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 53

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 53

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 53

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 53

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 53

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 53

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 53

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 53

Summing a list under the substitution model

19 of 53

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 53

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 53

Execution under the substitution model

  • (+ 1

(+ 2

(+ 3

(+ 4

0))))

​

  • (+ 1

(+ 2

(+ 3

4)))

​

  • (+ 1

(+ 2

7))

​

  • (+ 1

9)

​

  • 10

22 of 53

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 53

Recursion

24 of 53

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 53

Recursively�summing a list

26 of 53

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 53

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 53

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 53

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 53

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 53

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 53

Basic template�For recursion

33 of 53

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 53

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 53

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 53

Writing foldl/r

Using recursion

37 of 53

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 53

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 53

Writing length

Using recursion

40 of 53

Example

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

​

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

41 of 53

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 53

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 53

Sussman-form definitions

44 of 53

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 53

Writing iterated-beside

Using recursion

46 of 53

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 53

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 53

Example

  • Recursive case:
    • Make one picture

​

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

49 of 53

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 53

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 53

Execution is recursive

52 of 53

Example: running code

  • Executing ISL 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

53 of 53

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

​