# Recursion with helper procedures

*Summary:* In this laboratory, you will explore recursive procedures
in which we pass along intermediate computations, most typically
using a *recursive helper procedure*. When this technique is used in
conjunction with a program structure in which the recursive result
is returned directly (without accumulated actions to perform), this
technique supports *tail recursion*.

## Preparation

a. Discuss the self check with your partner.

b. Do the normal lab setup. That is

- Start DrRacket.
- Make sure that the
`csc151`

package is up to date. - Add
`(require csc151)`

to the top of the definitions pane.

c. Make a copy of the annotated `new-sum-helper`

procedure from the reading,
as well as `new-sum`

and any accompanying documentation.

d. Verify that it does, in fact, print the expected information about intermediate procedure calls.

## Exercises

### Exercise 1: Logging procedure calls

a. Add the following code (taken from the reading) to your definitions pane.

```
;;; Procedure:
;;; furthest-from-zero
;;; Parameters:
;;; numbers, a nonempty list of real numbers
;;; Purpose:
;;; Find the number in the list with the largest absolute value.
;;; Produces:
;;; furthest, a real number
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; furthest is an element of numbers
;;; For any number, n, in numbers
;;; (>= (abs furthest) (abs n))
(define furthest-from-zero
(lambda (numbers)
(furthest-from-zero-helper (car numbers) (cdr numbers))))
(define furthest-from-zero-helper
(lambda (furthest-so-far numbers-remaining)
(if (null? numbers-remaining)
furthest-so-far
(furthest-from-zero-helper
(further-from-zero furthest-so-far (car numbers-remaining))
(cdr numbers-remaining)))))
;;; Procedure:
;;; further-from-zero
;;; Parameters:
;;; val1, a real number
;;; val2, a real number
;;; Purpose:
;;; Choose whichever of val1 and val2 is further from zero.
;;; Produces:
;;; further, a real number
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; further is either val1 or val2
;;; (abs further) >= (abs val1)
;;; (abs further) >= (abs val2)
(define further-from-zero
(lambda (val1 val2)
(if (>= (abs val1) (abs val2))
val1
val2)))
```

b. Add appropriate calls to `display`

and `newline`

so that
you can track all the calls to `furthest-from-zero-kernel`

and
`further-from-zero`

.

c. Sketch (that is, compute by hand) the output you expect to see in
call to `(furthest-from-zero (list -3 5 6 -2 1 -5))`

.

d. Check your answer by asking DrRacket to evaluate that expression.

### Exercise 2: Product, revisited

a. Rewrite the `product`

procedure, which computes the product of a list
of values, using the same technique used for `new-sum`

.

b. Write a similar `my-quotient`

procedure. (Do *not* call it `quotient`

,
which is a built-in procedure that is commonly used.)

For example,

```
> (my-quotient (list 3))
3
> (my-quotient (list 3 5))
3/5
> (my-quotient (list 3 5 7))
3/35
> (my-quotient (list 3 5 7 6))
1/70
> (my-quotient (list 3 5 7 6 1/10))
1/7
```

### Exercise 3: Tallying numbers

You may recall that in the previous lab, you
wrote a procedure, `tally-numbers`

, that took as input a list of mixed
values (both numbers and non-numbers) and returned a count of the number
of numbers in the list.

We will be rewriting `tally-numbers`

using helper recursion. The helper
procedure will likely have two parameters: a count of all the numbers
you’ve seen so far and a list of all the values you have left to look at.

a. Make a table with two column labels: `count-so-far`

and `remaining`

.
Here’s what we might expect those columns to have after we’ve done
the first few steps of the helper on the list `'(5 a b 1 c 2 3 b 4)`

```
tally-so-far remaining
------------ ---------
0 '(5 a b 1 c 2 3 b 4)
1 '(a b 1 c 2 3 b 4)
1 '(b 1 c 2 3 b 4)
1 '(1 c 2 3 b 4)
2 '(c 2 3 b 4)
```

Fill in the next rows to suggest what might/should happen.

b. Using the ideas you gained in those steps, implement `tally-numbers`

and `tally-numbers-helper`

.

c. Check your answers on some of the following lists

```
> (tally-numbers null)
> (tally-numbers (list 1 2 3))
> (tally-numbers (list "a" "b" "c"))
> (tally-numbers (list 1 "a" 2 "b" 3 "c"))
```

d. Update `tally-numbers-helper`

to print out calls, and call it
on the list `'(5 a b 1 c 2 3 b 4)`

to see if it matches your
answer to part a.

### Exercise 4: Selecting numbers

You may recall that in the reading on recursion
basics, we wrote a procedure,
`select-numbers`

, that, given a list of values, returns a list with
just the numbers from the original list.

Here’s an attempt to implement that procedure using helper recursion.

```
;;; Procedure:
;;; select-numbers
;;; Parameters:
;;; values, a list of Scheme values
;;; Purpose:
;;; Create a new list values which consists of only the numbers
;;; in values.
;;; Produces:
;;; nums, a list of Scheme values
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; * number? holds for every value in nums.
;;; * Every element of values for which number? holds appears in nums.
;;; * Every element of nums appears in values.
;;; * If a number appears multiple times in either list, it appears
;;; the same number of times in both lists.
;;; * The values retain their order.
(define select-numbers
(lambda (values)
(select-numbers-helper null values)))
(define select-numbers-helper
(lambda (nums-so-far remaining)
; (display (list 'select-numbers-helper nums-so-far remaining)) (newline)
(cond
[(null? remaining)
nums-so-far]
[(not (number? (car remaining)))
(select-numbers-helper nums-so-far (cdr remaining))]
[else
(select-numbers-helper (cons (car remaining) nums-so-far)
(cdr remaining))])))
```

a. Add the code and documentation to your definitions pane.

b. As you may have observed in the previous problem, it can be helpful to understand tail recursive procedures by creating a table whose columns are labeled with the parameters and which each row gives the values of those parameters in one call.

Make a table with two column labels: `num-so-far`

and `remaining`

.
Here’s what we might expect those columns to have after we’ve done
the first few steps of the helper on the list `'(a b 1 c 2 3 b 4)`

```
num-so-far remaining
---------- ---------
'() '(a b 1 c 2 3 b 4)
'() '(b 1 c 2 3 b 4)
'() '(1 c 2 3 b 4)
'(1) '(c 2 3 b 4)
```

Fill in the remaining five or so rows.

c. What output do you expect to get from
`(select-numbers '(a b 1 c 2 3 b 4))`

?

d. Check your answer experimentally.

e. If you did not get the answer you expected, uncomment the line that logs procedure calls and run it again.

### Exercise 5: Selecting numbers, revisited

As you may have noted, the prior implementation of `select-numbers`

produces the list of values in reverse order that they appear in the
original list. Rewrite the procedure so that it produces the list of
values in the same order as they appear in the original list. You
may not use `append`

in that implementation.

### Exercise 6: Exploring costs

You may recall that we claimed that one of the implementations of
`furthest-from-zero`

using direct recursion was incredibly expensive.

Let’s check that claim.

a. Add the following definition of `furthest-from-zero`

to your
definitions pane.

```
(define furthest-from-zero-alt
(lambda (numbers)
(display (list 'furthest-from-zero-alt numbers)) (newline)
(if (null? (cdr numbers))
(car numbers)
(if (>= (abs (car numbers))
(abs (furthest-from-zero-alt (cdr numbers))))
(car numbers)
(furthest-from-zero-alt (cdr numbers))))))
```

b. What output do you expect from the following procedure call? (Detail both the answer and the log messages.)

```
> (furthest-from-zero-alt (list 8 5 3 1 0))
```

c. Check your answer experimentally.

d. What output do you expect from the following procedure call? (Detail both the answer and the log messages.)

```
> (furthest-from-zero-alt (list 0 1 3 5 8))
```

e. Check your answer experimentally.

f. Explain, in your own words, why one of the two results looks so much worse.

g. How many calls to `furthest-from-zero-alt`

do you expect to see
if we ask for the values of `(furthest-from-zero-alt (iota 8))`

?

h. Check your answer experimentally.

### Exercise 7: An alternate improvement

As we noted in the reading the problem with `furthest-from-zero-alt`

is that we may have *two* recursive calls each time, rather than one.
That’s dangerous. We suggested a few alternative strategies in the
reading. Let’s consider another one. Suppose we use `let`

to bind
names to the `car`

and the result of of the recursive call.

```
(let ([candidate (furthest-from-zero-alt (cdr numbers))]
[alternate (car numbers)])
```

a. Rewrite `furthest-from-zero-alt`

to use these names in place of the
values they name.

b. What effect do you expect this naming to have on the efficiency of the procedure?

c. Check your answer experimentally.

## For those with extra time

The following exercises will challenge you to extend the problem-solving strategies you’ve learned so far.

### Extra 1: Tallying multiple types

Write a procedure, `tally-numbers-strings-and-symbols`

, that takes
as input a mixed list and produces a list of tallys of the number
of numbers, strings, and values.

```
> (tally-numbers-strings-and-symbols null)
'(0 0 0)
> (tally-numbers-strings-and-symbols '(a 1 2 b "c" 3))
'(3 2 1)
> (tally-numbers-strings-and-symbols '(a b "c" "3" "e"))
'(0 2 3)
```

You will find it useful to base `tally-numbers-strings-and-symbols`

on
the helper-recursive `tally-numbers`

your wrote earlier in this lab.

### Extra 2: Checking preconditions

Rewrite the original `furthest-from-zero`

so that the primary procedure
(that is, `furthest-from-zero`

) checks all of its preconditions before
calling the helper.

### Extra 3: Too many ways to find far values

The reading provides at least four ways to find the number furthest from zero in a list of numbers. You’ve developed at least one more in this lab. Which do you prefer, and why?

Let’s consider the first version of `furthest-from-zero`

, which
turned out to be incredibly inefficient.

```
(define furthest-from-zero-alt
(lambda (numbers)
(display (list 'furthest-from-zero-alt numbers)) (newline)
(if (null? (cdr numbers))
(car numbers)
(if (>= (abs (car numbers))
(abs (furthest-from-zero-alt (cdr numbers))))
(car numbers)
(furthest-from-zero-alt (cdr numbers))))))
```