Lecture 12 Slides - Iterative Recursion

1 of 35

Lecture 12

Iterative Recursion - A Slightly Different Flavour

2 of 35

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

3 of 35

Special kinds of recursion

  • Multiple kinds of easy cases
  • Iterative
    • Fix-up part is blank
    • Instead update accumulator in recursive step
  • Tree recursion
    • Function calls itself more than once
  • Infinite recursion (usually bad)
    • No check for easy case
    • Broken check for easy case
    • Broken code for simplifying the args

(define function

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

4 of 35

Iterative recursion

5 of 35

Summing a list using recursion

We can add up the elements of a list by

  • Removing the first element
  • Adding the remaining elements (recursive step)
  • Adding the first element into the total

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

6 of 35

Is that the only way to do it?

  • This code adds the first element after recursing
  • Is there a way to add before recursing?

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

7 of 35

Keeping a partial sum (an accumulator)

  • We just keep track of the running total
  • That ends up having to be an additional argument So we call it by saying �(sum-list (list 1 2 3) 0)
  • Saves the computer having to remember to do the addition afterward

;; sum-list:

;; (listof number) -> number

;; Sum of all numbers in list

(define sum-list

(lambda (a-list)

(if (empty? a-list)

0

(+ (first a-list)

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

;; sum-list:

;; (listof number) number -> number

;; Sum of all numbers in list plus partial-sum

(define sum-list

(lambda (a-list partial-sum)

(if (empty? a-list)

partial-sum

(sum-list (rest a-list)

(+ (first a-list)

partial-sum)))))

8 of 35

Why it matters

Adding last:

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

'())))))

  • (+ 1 (+ 2 (+ 3 (+ 4 0))))
  • (+ 1 (+ 2 (+ 3 4)))
  • (+ 1 (+ 2 7))
  • (+ 1 9)
  • 10

Adding first:

  • (sum-list '(1 2 3 4) 0)
  • (sum-list '(2 3 4) (+ 1 0))
  • (sum-list '(2 3 4) 1)
  • (sum-list '(3 4) (+ 2 1))
  • (sum-list '(3 4) 3)
  • (sum-list '(4) (+ 3 3))
  • (sum-list '(4) 6)
  • (sum-list '() (+ 4 6))
  • (sum-list '() 10)
  • 10

Holding + until last requires more stack space to keep track pending operations.

9 of 35

Iteration and recursion

  • When a procedure calls itself, it’s called a recursive function.
  • When the procedure returns the value of the call directly, without a fixup step, it’s called iterative recursion
    • Also known as tail recursion (or looping)
    • Doesn’t require the computer to remember pending operations
      • Less memory to execute
      • CS 211 Preview: iterative recursion can re-use the current stack frame, allowing the recursion to be compiled as a while loop
  • Iterative recursion generally requires that an accumulator argument hold the intermediate result
    • The base case is usually just to return that argument

10 of 35

How do we write foldl?

  • We can use the same trick to write foldl!

;; sum-list:

;; (listof number) number -> number

;; Sum of all numbers in list plus partial-sum

(define sum-list

(lambda (a-list partial-sum)

(if (empty? a-list)

partial-sum

(sum-list (rest a-list)

(+ (first a-list)

partial-sum)))))

11 of 35

How do we write foldl?

  • We can use the same trick to write foldl!
  • What we’ve been calling the start argument before
  • Is really just the partial result (an accumulator)

;; foldl:

;; (X X -> X) X (listof X) -> X

;; Uses func to collapse all elements of list

;; plus partial-sum

(define foldl

(lambda (func partial-result a-list)

(if (empty? a-list)

partial-result

(foldl func

(func (first a-list)

partial-result)

(rest a-list))))

In the video there's a typo on this slide (extra paren) it's been corrected here.

12 of 35

Helper procedures

13 of 35

Example

  • Write length iteratively (it requires two functions)

;; length: (listof X) -> number

;; Number of elements in list

14 of 35

Example

  • Write length iteratively (you’ll need to write two procedures)�

;; length: (listof X) -> number

;; Number of elements in list

(define length

(lambda (a-list)

(length-loop a-list 0)))

;; length-loop: (listof X) number -> number

;; Number of elements in list plus length-so-far

(define length-loop

(lambda (list-remaining length-so-far)

(if (empty? list-remaining)

length-so-far

(length-loop (rest list-remaining)

(+ length-so-far 1)))))

15 of 35

Execution under the substitution model

  • (length ‘(1 2 3))
  • (length-loop '(1 2 3) 0)
  • (if (empty? '(1 2 3))

0

(length-loop (rest '(1 2 3))

(+ 0 1)))

  • (if false

0

(length-loop (rest '(1 2 3))

(+ 0 1)))

  • (length-loop (rest '(1 2 3))

(+ 0 1)))

  • (length-loop '(2 3) (+ 0 1))
  • (length-loop '(2 3) 1)
  • (if (empty? '(2 3))

1

(length-loop (rest '(2 3))

(+ 1 1)))

  • (if false

1

(length-loop (rest '(2 3))

(+ 1 1)))

  • (length-loop (rest '(2 3))

(+ 1 1)))

  • (length-loop '(3) (+ 1 1)))
  • (length-loop '(3) 2)
  • (if (empty? '(3))

2

(length-loop (rest '(3)) (+ 2 1)))

  • (if false

2

(length-loop (rest '(3)) (+ 2 1)))

  • (length-loop (rest '(3)) (+ 2 1))

16 of 35

Execution under the substitution model

  • (length-loop '() (+ 2 1))
  • (length-loop '() 3)
  • (if (empty? '())

3

(length-loop (rest '())

(+ 3 1)))

  • (if true

3

(length-loop (rest '())

(+ 3 1)))

  • 3

17 of 35

Making it prettier

  • You can place function definitions within other functions
    • Those variables are then only available within the procedure
    • The expressions within them can use the other variables of the procedure�

;; length: (listof X) -> number

;; Number of elements in list

(define my-length

(lambda (a-list)

;; loop: (listof X) number -> number

;; Number of elements in list plus length-so-far

(local [(define loop

(lambda (a-list length-so-far)

(if (empty? a-list)

length-so-far

(loop (rest a-list)

(+ length-so-far 1)))))]

(loop a-list 0))))

18 of 35

Iterative Recursion

  • Instead of waiting until the end to "combine" all the simpler answers
  • We could instead keep track of the "answer so far" in an accumulator
  • This is called iterative recursion
  • Uses a helper function to maintain the correct number of args.

(define (help args … accum)

(if easy-case?accum (help args …

(updater accum …))))

(define (func args …) � (help args … start-accum))

19 of 35

Tree recursion

20 of 35

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

21 of 35

Basic template for recursion

  • You can often simplify the problem by splitting it
    • At least one of the splits will then be a recursive call
  • Then the fixup step consists of combining the answers to the two problems
  • This is called tree recursion

(define (func args …) � (if easy-case?solve-easy-case (combine (func one-part) (func other-part)))))

22 of 35

How google sums a list

  • Google uses parallel algorithms do their searches
    • Parallel means it runs on several computers at once
  • Google splits the list up into chunks and runs each chunk on a different machine
    • This is called a parallel-prefix scan operation
    • But it really just means folding
  • This allows them to use large networks of cheap PCs to do their searches quickly

(define sum-list

(lambda (a-list)

(if (list-fits-on-one-processor? a-list)

(add-it-up a-list)

(+ (sum-list (first-half a-list))

(sum-list (second-half a-list)))))

  • A very approximate version of the algorithm Google uses
  • Note: the two recursive calls run on different machines

23 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list:

24 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list: + Sum this list:

25 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list: + Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list: + Sum this list: Sum this list: + Sum this list:

26 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list: + Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

10 + 26 42 + 58

27 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Sum this list:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

36 + 100

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

10 + 26 42 + 58

28 of 35

Parallel summation

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

136

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

36 + 100

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

10 + 26 42 + 58

29 of 35

Optional reading

Chapter 1 of Abelson and Sussman

30 of 35

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

31 of 35

Iterative Recursion

  • You can often simplify the problem by splitting it
  • Then the fixup step consists of combining the answers to the two problems
  • This is called tree recursion

(define (func args …) � (if easy-case?solve-easy-case (combine (func one-part) (func other-part)))))

Tree Recursion

  • Instead of waiting until the end to "combine" all the simpler answers
  • We could instead keep track of the "answer so far" in an accumulator
  • This is called iterative recursion
  • Uses a helper function to maintain the correct number of args.

(define (help args … accum)

(if easy-case?accum (help args …

(updater accum …))))

(define (func args …) � (help args … start-accum))

32 of 35

Sussman-form definitions

From Tutorial 3

33 of 35

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.

34 of 35

Got a lot of cases?

From Tutorial 4

35 of 35

That's what cond is for!

(if test R1 R2) ; if test is true, then R1 otherwise R2

(if (> x 5) "big" "small")

(cond [test-1 R1] ; if test-1 is true, then R1

[test-2 R2] ; otherwise if test-2 is true, then R2

[test-n Rn]); otherwise if test-n is true, then Rn

(cond [(> x 5) "big"] ; if x > 5, then "big"

[(= x 5) "medium"] ; otherwise if x = 5, "medium"

[else "small"]) ; otherwise "small"