- Assigned
- Monday, Sep 10, 2018
- Due
- Lab writeups are due at 10:30pm on the day of the next class meeting. For example, a Wednesday lab is due at 10:30pm on Friday. Each lab writeup will be announced at the end of class on a lab day.
- Summary
- We explore mechanisms for creating procedures in Scheme.

Define procedures with `(define NAME PROCEDURE-EXPRESSION)`

Compose procedures with `(o f g)`

Section (fill in some parameters) of a procedure with
`(section PROC <> CONSTANT)`

and variants thereof.

a. If you have not done so already, you may want to open a separate tab or window in your browser for the reading on procedures.

b. We will be updating the `csc151`

library throughout the semester.
Using the **Install Package** or **Package Manager** menu item,
make sure that you update our library to the latest version.
Use https://github.com/grinnell-cs/csc151.git.

c. In your definitions window, require the relevant portions of the
`csc151`

library.

```
(require csc151/lists)
(require csc151/hop)
(require csc151/numbers)
(require csc151/square)
```

d. Add the following lists of numbers to your definitions pane.

```
(define assorted-integers
(list -10 5 8 -2 18 4 23 16 22 -5 -6 11 42 -42))
(define coffee-prices
(list 2.34 1.50 1.60 2.18 1.11 2.12 1.90 2.50 2.90 2.01 2.02 .89))
```

e. Make your own list of a dozen or so non-integer inexact real numbers.
Name it `tea-prices`

.

f. Save your definitions on the Desktop as as `procedures-lab.rkt`

.

Unless we have done so as a class, discuss with your partner the problems in the self check.

a. The reading contains a definition of the `average`

procedure. Add
that definition to your definitions pane and conduct some experiments to
verify that it works as you expect.

b. The *geometric mean* of a list of `n`

numbers is the `n`

th root
of the product of those numbers. Write a procedure, `geometric-mean`

,
that takes a list of numbers as input and returns its geometric mean.

In the self-check, you wrote a procedure that bounded its input between 0 and 100. It is likely that you wrote something like the following.

```
(define bound-grade
(lambda (grade)
(min (max grade 0) 100)))
```

Rewrite `bound-grade`

without using `lambda`

.

*Hint*: Think about the steps involved. First you compute the maximum
of 0 and some number. Then you compute the minimum of the result of
that expression and some number.

When working with larger data sets, data scientists will often “clean” the data by, for example, removing partial data points or by simplifying the data. A typical approach to computing some characteristic of a set of data is to (a) filter out the inappropriate or incomplete values, (b) transform any remaining values as appropriate (e.g., rounding), (c) potentially join it with other similar sets of data, and (d) compute or visualize the result. If our final step is averaging, we might write that process as

```
(define fancy-average
(o average (section append <> other-data) transform filter))
```

But that’s a lot to think about right now, so let’s look at a simpler version in which we just transform the data and then average it.

```
(define semi-fancy-average (o average transform))
```

Let’s explore the simple list of prices you added to your definitions pane in the preparation phase of this assignment. That list contains fictitious prices of a cup of black coffee at a variety of venues.

Let’s suppose we just wanted dollar amounts for the coffee. We have
three basic options: We could round with `round`

, round up with `ceiling`

,
or round down with `floor`

. (Since the prices are all positive, there
is no difference between `floor`

and `truncate`

.)

a. Write a definition of `transform`

(called `transform-1`

) that takes
a list of prices as input and produces a list of those prices all of
which are rounded to the nearest integer using `round`

.

```
(define transform-1 ...)
(define semi-fancy-average-1 (o average transform-1))
```

b. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.

```
> (semi-fancy-average-1 coffee-prices)
> (semi-fancy-average-1 tea-prices)
```

c. Write a definition of `transform`

(called `transform-2`

) that takes
a list of prices as input and produces a list of those prices all of
which are rounded up using `ceiling`

.

```
(define transform-2 ...)
(define semi-fancy-average-2 (o average transform-2))
```

d. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.

```
> (semi-fancy-average-2 coffee-prices)
> (semi-fancy-average-2 tea-prices)
```

e. Write a definition of `transform`

(called `transform-3`

) that takes
a list of prices as input and produces a list of those prices all of
which are rounded down using `floor`

.

```
(define transform-3 ...)
(define semi-fancy-average-3 (o average transform-3))
```

f. Using that definition, compute the semi-fancy-average of the lists of coffee and tea prices.

```
> (semi-fancy-average-3 coffee-prices)
> (semi-fancy-average-3 tea-prices)
```

As you may have noted, we saw some potentially significant effects on the average when we rounded up vs rounding down vs just rounding. We might want to measure those potential effects.

a. Write a procedure, `list-rounding-differences`

that takes a
list of real numbers as input and creates a list that, for each
number, shows the difference between rounding up and rounding down.
You will end up with a list of 0’s and 1’s.

```
> (list-rounding-differences (list 0.1 0.7 10 11 0.5))
`(1.0 1.0 0.0 0.0 1.0)
```

b. Write a procedure or procedures that help you determine, for an
arbitrary list of real numbers, whether using `round`

to round
values produces a result closer to rounding up or rounding down.
(Yes, this question is intentionally left vague. It’s to help you
think about the ways in which you might think about your data.)

You may recall that a few problems back, we suggested that we often use multiple steps as we analyze data. Sometimes, our first step is to filter out meaningless or incomplete data. Let’s explore that issue a bit more.

As you may have noted, the list `assorted-integers`

contains both
positive and negative integers. Perhaps we would like to remove
the negative integers, assuming that negative numbers represent
incorrectly entered data.

Write a procedure, `filter-out-negatives`

, that takes a list of
integers as input and removes the negative numbers. Your procedure
need not present the values in the same order.

Here’s one valid output.

```
> (filter-out-negatives (list -5 10 2 8 5 -1 3))
'(10 2 8 5 3)
```

Here’s an equally valid output.

```
> (filter-out-negatives (list -5 10 2 8 5 -1 3))
`(2 3 5 8 10)
```

*Hint*: You may find the following ideas helpful. You can put lists in
order from smallest to largest with `(sort lst <)`

. You can add another
value to a list with `(append (list value) lst)`

. You can find the
position of the first instance of a value with `(index-of value lst)`

.

You can remove the first `k`

elements with `(drop lst k)`

.

Write a procedure, `median`

, that takes as input an odd-length list of
real numbers and returns the median of the list.

Write a procedure, `scale-by-median`

, that takes as input a list of
real numbers and returns a list of the result of dividing each number
by the median.

```
> (define numbers (list 4.0 2.0 3.0 4.0 5.0 8.0 1.0))
> (median numbers)
4.0
> (scale-by-median numbers)
'(1.0 0.5 0.75 1.0 1.25 2.0 0.25)
```

Now that we have `scale-by-median`

, we are able to convert different kinds
of data to the same general “approach”. For example, if we have multiple
lists of prices, one for each kind of good, we can transform them each
to a list of how much each price varies from the median price. Then we
can put the values together and, perhaps, compute some other interesting
characteristics of those values.

a. Write expressions to compute the average and geometric mean of the variances from the median for all the prices we have, both coffee and tea. You’ll want to separately compute ratio to median for coffee prices and ratio to median for tea prices.

b. Write a procedure that takes two lists and does a similar process to the two lists. That is, your procedure should find the ratio to median for each value in each list, combine those ratios together, and then find the arithmetic or geometric mean.

Write a procedure, `filter-out-of-bounds`

, that takes three inputs:
a real number representing a lower bound, a real number representing
an upper bound, and a list of real numbers. Your procedure should return
a new list that contains only the values in the list that are between
those two bounds (inclusive). You need not return them in the
same order.

Write a procedure, `unify`

, that takes a list of lists of real
numbers as input. It should then take each list and compute the
ratio of the values in that list to the mean of each list. Afterwards,
it should combine them into a single list.

```
> (unify (list (list 1.0 2.0 3.0) (list 4.0 10.0 8.0) (list 0.8 0.4 0.1)))
'(0.5 1.0 1.5 0.5 1.25 1.0 2.0 1.0 0.25)
```

Hint: You can join the resulting lists together with `reduce`

and
`append`

. You should be able to convert each list with an appropriate
call to `map`

.