• No results found

Designing Conditional Functions

In document How to Design Programs (Page 58-64)

Developing conditional functions is more difficult than designing a plain function. The key is to recognize that the problem statement lists cases and to identify the different cases. To emphasize the importance of this idea, we introduce and discuss a design recipe for designing conditional functions. The new recipe introduces a new step, DATA ANALYSIS, which requires a programmer to understand the different situations that the problem statement discusses. It also modifies the Examples and the Body steps of the design recipe in section 2.5:

Data Analysis and Definition:

After we determine that a problem statement deals with distinct situations, we must identify all of them. The second step is a DATADEFINITION, an idea that we will explore a lot more.

For numeric functions, a good strategy is to draw a number line and to identify the intervals that correspond to a specific situation. Consider the contract for the

interest-rate function:

;; interest-rate : number -> number

;; to determine the interest rate for the given amount >=

0

(define (interest-rate amount) ...)

It inputs non-negative numbers and produces answers for three distinct situations:

For functions that process booleans, the cond-expression must distinguish between exactly two situations: true and false. We will soon encounter other forms of data that require case-based reasoning.

Function Examples:

Our choice of examples accounts for the distinct situations. At a minimum, we must develop one function example per situation. If we characterized the situations as numeric intervals, the examples should also include all borderline cases.

For our interest-rate function, we should use 0, 1000, and 5000 as borderline cases. In addition, we should pick numbers like 500, 2000, and 7000 to test the interiors of the three intervals.

The Function Body -- Conditions:

The function's body must consist of a cond-expression that has as many clauses as there are distinct situations. This requirement immediately suggests the following body of our solution:

(define (interest-rate amount) (cond

[... ...]

[... ...]

[... ...]))

Next we must formulate the conditions that characterize each situation. The conditions are claims about the function's parameters, expressed with Scheme's relational operators or with our own functions.

The number line from our example translates into the following three conditions:

1. (and (<= 0 amount) (<= amount 1000))

2. (and (< 1000 amount) (<= amount 5000))

3. (< 5000 amount)

Adding these conditions to the function produces a better approximation of the final definition:

(define (interest-rate amount) (cond

[(and (<= 0 amount) (<= amount 1000)) ...]

[(and (< 1000 amount) (<= amount 5000)) ...]

[(> amount 5000) ...]))

At this stage, a programmer should check that the chosen conditions distinguish inputs in an appropriate manner. Specifically, if some input belongs to a

particular situation and cond-line, the preceding conditions should evaluate to

false and the condition of the line should evaluate to true. The Function Body -- Answers:

Finally, it is time to determine what the function should produce for each cond -clause. More concretely, we consider each line in the cond-expression

separately, assuming that the condition holds.

In our example, the results are directly specified by the problem statement. They are 4.0, 4.5, and 5.0. In more complicated examples, we may have to determine

an expression for each cond-answer following the suggestion of our first design recipe.

Hint: If the answers for each cond-clause are complex, it is good practice to develop one answer at a time. Assume that the condition evaluates to true, and develop an answer using the parameters, primitives, and other functions. Then apply the function to inputs that force the evaluation of this new answer. It is legitimate to leave ``...'' in place of the remaining answers.

Simplification:

When the definition is complete and tested, a programmer might wish to check whether the conditions can be simplified. In our example, we know that amount

is always greater than or equal to 0, so the first condition could be formulated as

(<= amount 1000)

Furthermore, we know that cond-expressions are evaluated sequentially. That is, by the time the second condition is evaluated the first one must have produced

false. Hence we know that the amount is not less than or equal to 1000, which makes the left component of the second condition superfluous. The appropriately simplified sketch of interest-rate is as follows:

(define (interest-rate amount) (cond

[(<= amount 1000) ...]

[(<= amount 5000) ...]

[(> amount 5000) ...]))

Figure 6 summarizes these suggestions on the design of conditional functions. Read it in conjunction with figure 4 and compare the two rows for ``Body.'' Reread the table when designing a conditional function!

Phase Goal Activity Data

Analysis to determine the distinct situations a function deals with

inspect the problem statement for distinct situations enumerate all possible situations

choose at least one example per situation for intervals or enumerations, the examples must include borderline cases

write down the skeleton of a cond expression, with one clause per situation formulate one condition per situation, using the parameters ensure that the conditions distinguish the examples appropriately

deal with each cond-line separately assume the condition holds and develop a Scheme expression that computes the appropriate answer for this case

Figure 6: Designing the body of a conditional function (Use with the recipe in figure 4 (pg. 5))

Exercise 4.4.1. Develop the function interest. Like interest-rate, it consumes a deposit amount. Instead of the rate, it produces the actual amount of interest that the money earns in a year. The bank pays a flat 4% for deposits of up to $1,000, a flat 4.5%

per year for deposits of up to $5,000, and a flat 5% for deposits of more than

$5,000. Solution

Exercise 4.4.2. Develop the function tax, which consumes the gross pay and produces the amount of tax owed. For a gross pay of $240 or less, the tax is 0%; for over $240 and $480 or less, the tax rate is 15%; and for any pay over $480, the tax rate is 28%.

Also develop netpay. The function determines the net pay of an employee from the number of hours worked. The net pay is the gross pay minus the tax. Assume the hourly pay rate is $12.

Hint: Remember to develop auxiliary functions when a definition becomes too large or too complex to manage. Solution

Exercise 4.4.3. Some credit card companies pay back a small portion of the charges a customer makes over a year. One company returns

1. .25% for the first $500 of charges,

2. .50% for the next $1000 (that is, the portion between $500 and $1500),

3. .75% for the next $1000 (that is, the portion between $1500 and $2500),

4. and 1.0% for everything above $2500.

Thus, a customer who charges $400 a year receives $1.00, which is 0.25 · 1/100 · 400, and one who charges $1,400 a year receives $5.75, which is 1.25 = 0.25 · 1/100 · 500 for the first $500 and 0.50 · 1/100 · 900 = 4.50 for the next $900.

Determine by hand the pay-backs for a customer who charged $2000 and one who charged $2600.

Define the function pay-back, which consumes a charge amount and computes the corresponding pay-back amount. Solution

Exercise 4.4.4. An equation is a claim about numbers; a quadratic equation is a special kind of equation. All quadratic equations (in one variable) have the following general shape:

In a specific equation, a, b and c are replaced by numbers, as in

or

The variable x represents the unknown.

Depending on the value of x, the two sides of the equation evaluate to the same value (see exercise 4.2.3). If the two sides are equal, the claim is true; otherwise it is false. A number that makes the claim true is a solution. The first equation has one solution, - 1, as we can easily check:

The second equation has two solutions: + 1 and - 1.

The number of solutions for a quadratic equation depends on the values of a, b, and c. If the coefficient a is 0, we say the equation is degenerate and do not consider how many solutions it has. Assuming a is not 0, the equation has

1. two solutions if b2 > 4 · a · c,

2. one solution if b2 = 4 · a · c, and

3. no solution if b2 < 4 · a · c.

To distinguish this case from the degenerate one, we sometimes use the phrase proper quadratic equation.

Develop the function how-many, which consumes the coefficients a, b, and c of a proper quadratic equation and determines how many solutions the equation has:

(how-many 1 0 -1) = 2 (how-many 2 4 2) = 1

Make up additional examples. First determine the number of solutions by hand, then with DrScheme.

How would the function change if we didn't assume the equation was proper?

Solution

13 In truth, the operations and and or are different from not, which is why they are typeset in different fonts. We ignore this minor difference for now.

14 The use of brackets, that is, [ and ], in place of parentheses is optional, but it sets apart the conditional clauses from other expressions and helps people read functions.

15 If the cond-expression has no else clause and all conditions evaluate to false, an error is signaled in Beginning Student Scheme.

Section 5

In document How to Design Programs (Page 58-64)