5 Working with conditional expressions
(7 activities)
5.1 Quiz: "Working with words and sentences"(1 step)
5.1.1 (Student Assessment) Distinguish words and sentences.
1.Evaluate
(butfirst (first (butfirst '(ab cd))))
2.Consider the following procedure.
(define (weird x)
  (sentence x '(sentence x 14)) )
Evaluate (weird 15).
3.Write a procedure named knight that, given a person's name as argument, returns the name preceded by sir. For example, (knight '(mike clancy)) should return (sir mike clancy).
5.2 Practice with predicates and the "if" expression.(7 steps)
5.2.1 (Display page) Conditionals

Introduction to Conditionals

So far, you have written programs that can, with enough code, transform numbers and words in very complex ways. But, the programs that you can create right now will lack a most basic feature of intelligence: the ability to do completely different things depending on what they "see" (i.e., what is passed as input). Today you will focus on conditionals, or statements that let your program branch in two or more different ways depending on some input. In the course of this, you'll also learn about how scheme can test inputs, in order to determine which branch to follow.
5.2.2 (Display page) Here is how "if" works, and "predicate" procedures that it works with.

Here is how "if" works, and "predicate" procedures that it works with.

The if expression has the form
(if test-expression true-result false-result)

It allows the programmer to write an expression whose value depends on the outcome of a test expression. An example, representing today's choice for dinner, is
(if (is-monday? today) 
  'macaroni-and-cheese
  'chinese-food)
if takes three arguments. The first is an expression whose value is either true or false (these are defined below). The second and third are also expressions; one of these will be evaluated, and that value will be the result of the if expression, depending on whether the first argument's value was true or false. False is represented by the symbol #f. True is any value that's not #f. The symbol #t is often used for this purpose. Procedures used because they return true or false are called predicates. Scheme has a number of builtin predicate procedures, almost all of which have names that end in a question mark:
  • word?, given a single argument, returns #t if the argument is a word and returns #f otherwise.
  • sentence?, given a single argument, returns #t if the argument is a sentence and returns #f otherwise.
  • number?, given a single argument, returns #t if the argument is a number and returns #f otherwise.
  • empty?, given a single argument, returns #t if the argument is the empty word or the empty sentence; it returns #f otherwise.
  • member?, given two arguments, returns #t if the first argument is a "member" of the second--a character within a word or a word within a sentence. It returns #f otherwise.
  • equal?, given two arguments, returns #t if they have the same value and #f otherwise.
  • before?, given two words as arguments, returns #t if the first word comes alphabetically before the second; it returns #f otherwise.
  • =, >, <, <=, and >=, given two numbers as arguments, return the result of the corresponding numeric comparisons.
Here is an example:
(if (> 5 4)
  (+ 3 7)
  (- 3 7))
Since 5 is bigger than 4, Scheme does (+ 3 7). It never even looks at (- 3 7).
5.2.3 (Display page) Experiment with builtin predicate procedures.
Experiment with the built-in predicate procedures in the Scheme interpreter. In particular, answer the questions:
  • What happens when you try to compare letters with =?
  • Does before? treat upper-case and lower-case letters identically?
  • Are the decimal value 12.00 and the integer 12 equal? (Make this test using both equal? and =.)
Put the results in your Extra Brain.
5.2.4 (Display page) Experiment with "if".

Experiment with if.

Complete the bodies of the following procedures, and test the results using the interpreter. Put the completed procedures and the results from your tests into a file named if-tests.scm. Your TA or lab assistant may ask you how you chose your test expressions.
; k is an integer.
; Return the smallest even number that's greater than k.
(define (next-higher-even k)
  (if (odd? k)
   ___
   
   ___ ) )

; x is a sentence or word.
; If x isn't empty, return its first word or letter.
; If x is empty, return the word ILLEGAL.
(define (non-crashing-first x)
  (if ___
   ___
   
   ___ ) )

; x and y are two numbers.
; Return the smaller of the two.
(define (smaller x y)
 ___ )
5.2.5 (Display page) "if" expressions can be nested.

If expressions can be nested.

Any of the three expressions in an if may itself be an if expression. Here's an example:
(if (> water-temp 212) 
  'steam 
  (if (< water-temp 32)
      'ice
      'liquid) )
Note that with the two nested if statements, there are three possible outcomes: steam, liquid, and ice.
Write and test a procedure named inches-in-range that takes three arguments. The first is a number in inches. The second two are sentences that represent measurements in feet and inches, like in the activity you did yesterday. The first measurement should be smaller than the second. If the number in inches is between the two measurements, inclusive, inches-in-range should return the word in-range; otherwise it should return out-of-range. Examples:
  • (inches-in-range 17 '(1 2) '(3 5)) should return in-range, since 17 inches is between 1' 2" and 3' 5".
  • (inches-in-range 14 '(1 2) '(3 5)) should also return in-range, since 14 inches is exactly 1' 2".
  • (inches-in-range 59 '(2 0) '(4 0)) should return out-of-range, since 59 inches is bigger than 4".
Use only if, the code from the measurements.scm file you wrote yesterday, and numerical comparisons to define inches-in-range. Add inches-in-range to your measurements.scm file.
5.2.6 (Display page) Practice with "and", "or", and "not".

Practice with and, or, and not.

Two procedures named and and or are useful for combining true-or-false results. And, given any number of expressions, returns true if all the expressions have true values. Or returns true if at least one of its arguments is true. Both and and or evaluate their arguments one at a time from left to right. And stops evaluating when it finds a false result; or similarly stops evaluating when it finds a true result. Here are examples:
(if (and (is-monday? today) (equal? (location 'mom) 'home))
  'broccoli
  'pizza)

(if (or (is-monday? today) (is-tuesday? today))
  'macaroni-and-cheese
  'pizza)
One more procedure that's sometimes useful is not. Not takes one argument. If the argument's value is true, not returns #f; otherwise it returns #t. Rewrite the inches-in-range procedures from the previous step to use and (and perhaps not) rather than a nested if expression. Put the rewritten procedure into your file measurements.scm. (Note, you can have two definitions of the same procedure in your file. When the file is loaded as a whole, the second definition will be the one that scheme uses).
5.2.7 (Brainstorm) Provide a good comment

Provide a good comment.

Consider the following procedure.
(define (mystery a b)
  (if (odd? a)
      (if (odd? b) #f #t)
      (if (odd? b) #t #f) ) ) 
Fill in the blank in the following sentence with ten or fewer words: The mystery procedure returns #t when ______________________________________, and returns #f otherwise.
5.3 Practice with "cond".(3 steps)
5.3.1 (Display page) Here's how "cond" works.

Here's how cond works.

The cond procedure provides a somewhat more convenient way to do a sequence of tests to decide what value to return. We earlier saw an example of a nested if expression:
(if (> water-temp 212) 
  'steam 
  (if (< water-temp 32) 'ice 'liquid) )
This can be rewritten to use a cond as follows.
(cond
  ((> water-temp 212) 'steam)
  ((< water-temp 32) 'ice)
  (else 'liquid) )
The parenthesis structure of a cond is different from everything we've seen up until now. Here is how a cond is built.
(cond
  ( question-1 value-1 )
  ( question-2 value-2 )
    ...
  ( else value-n ) )
In the water classifying example above, question-1 was (> water-temp 212), question-2 was (< water-temp 32), value-1 was 'steam, value-2 was 'ice, and value-n was 'liquid. A cond is evaluated by evaluating the questions one after another until encountering either a condition that's true or the else. When it finds a true question, it returns the corresponding value.
5.3.2 (Display page) Experiment with "cond".

Experiment with cond.

Write and test a procedure named sign that, given a number as argument, returns one of the words negative, zero, or positive depending on whether the argument is less than zero, equal to zero, or greater than zero. Use only a cond and numeric comparisons in your solution. Using the interpreter, test your procedure with inputs -1, 1, and 0.
5.3.3 (Display page) Reorganize a "cond" expression.

Reorganize a cond expression.

Since a cond is evaluated by successively evaluating the condition expressions, each false value provides information that can simplify the subsequent tests. Consider an example seen earlier:
(cond
  ((> water-temp 212) 'steam)
  ; At this point, we know that water-temp is at most 212.
  ; Thus we don't need to check that again.
  ((< water-temp 32) 'ice)
  ; Now we know that water-temp is less than or equal to 212
  ; and greater than or equal to 32.
  (else 'liquid) )
Rewrite the following procedure so that each condition in the cond takes advantage of all the tests that have been done before it in the cond. You should be able to remove about half of the code.
; The argument is a sentence of three integers.
; Return whichever of the words all-the-same,
; one-pair, or all-different is relevant.
(define (seq-type sent)
  (cond
    ((and (= (first sent) (second sent))
          (= (second sent) (third sent))) 'all-the-same)
    ((and (= (first sent) (second sent))
          (not (= (second sent) (third sent)))) 'one-pair)
    ((and (= (first sent) (third sent))
          (not (= (second sent) (third sent)))) 'one-pair)
    ((and (= (second sent) (third sent))
          (not (= (first sent) (third sent)))) 'one-pair)
    (else 'all-different) ) )
5.4 Devise good tests.(5 steps)
5.4.1 (Display page) Here's how to test an "if" or "cond".

Here's how to test an if or cond.

We test our procedures to provide evidence that they work. Good test cases will uncover errors wherever they exist in a procedure, and thus must exercise every possibility for a procedure's return value. For an if, there are two cases, one where the test expression is satisfied, the other where it's not. Thus to test the procedure
(define (classification candy-type)
  (if (equal? candy-type 'chocolate) 
      'wonderful
      'ok) )
we need at least one call where the test is satisfied, i.e.
(classification 'chocolate)
and at least one where it's not, e.g.
(classification 'peanut-brittle)
For a cond, there are as many possibilities as there are question expressions. In the procedure
(define (sign x)
  (cond
    ((< x 0) 'negative)
    ((= x 0) 'zero)
    (else 'positive) ) )
there are three possibilities, so all should be tested. An example set of tests that would do this is
(sign -5)
(sign 0)
(sign 3)
5.4.2 (Display page) Here's how to test an "and" or an "or".

Here's how to test an and or an or.

With either an and or an or, we again should test all possibilities:
true and/or true
true and/or false
false and/or true
false and/or false
If some of these are logically impossible, the number of test cases would be less. Here's an example.
; Return true if the integer argument is in the range 
; from 0 to 100 (greater than 0, less than or equal to 100).
(define (in-range? x)
  (and (> x 0) (<= x 100)) )
The cases to test are
  1. x > 0 is true, x <= 100 is true
  2. x > 0 is true, x <= 100 is false
  3. x > 0 is false, x <= 100 is true
  4. x > 0 is false, x <= 100 is false
Values in the first category are those between 1 and 100, inclusive. In the second category are values greater than 100; the third category contains 0 and negative numbers. The fourth category contains no values at all and thus doesn't need to be tested.
5.4.3 (Display page) Consider "boundary cases".

Consider boundary cases.

Often programs work with numbers in a given range, for instance, in the range 1 to 100 in the previous step. Careless programmers sometimes accidentally use comparisons with < where they meant <= or vice versa, or > instead of >=. Thus one should test these boundary values in addition to the more typical values not at the ends of the range.
5.4.4 (Display page) Test supporting procedures

Test supporting procedures

If one procedure you are writing relies on other smaller procedures you have written, you can't just test the big procedure and go home satisfied. As your code gets larger and you have more procedures, there are more and more places for errors to hide. The easiest way to find them is to test every procedure on its own. If you wanted to test your inch-in-range procedure, you would also need to test the other procedures in measurements.scm. There's an analogy with house building. The idea here is to make sure that the "foundation" of your program—the "building-block" procedures—is secure, via thorough testing, before adding to the building.
5.4.5 (Display page) Write a procedure and devise a set of test calls.

Write a procedure and devise a set of test calls.

Write a procedure named outranks? that, given two card ranks as arguments, returns true if the first rank is greater than the second and returns false otherwise. Put your procedure in a file named outranks.scm. A rank is represented by a word that is either one of the integers 2 through 10 or one of the letters a, k, q, or j (for "ace", "king", "queen", and "jack", respectively). An ace outranks everything, followed by a king, a queen, and a jack, followed by the numbered ranks in descending order. (For example, a king outranks a jack, which outranks a 10, which outranks a 2.) If the ranks are the same, outranks? should return false. Once you have your procedure designed, test it thoroughly and design a convincing set of test calls for the next step.
5.5 Simplify your solutions.(4 steps)
5.5.1 (Display page) Here are ways to rewrite a boolean expression.

Here are ways to rewrite a boolean expression.

You have a lot of freedom when you work with and, or, and not. You can use these three procedures in many different ways to get the same result. One can transform an expression that uses not and and into an expression that uses not and or, and vice versa. In particular, the expression
(and (not x) (not y))
is equivalent to the expression
(not (or x y))
for expressions x and y that both return true or false. Similarly, the expression
(or (not x) (not y))
is equivalent to the expression
(not (and x y))
Here's an example. Suppose that a gymnasium is reserved from the 4th to the 20th of every month. A procedure that says when it's reserved is

(define (reserved? date)
  (and (>= date 4) (<= date 20)) )
In English, this says it is reserved if it is both on or after the 4th and on or before the 20th. Someone wanting to use the gymnasium, however, would be more interested in when it's not reserved:
(define (free? date)
  (not (and (>= date 4) (<= date 20))) )
This is somewhat complicated. In English, it says that the gym is free if it is not both on or after the 4th and on or before the 20th. It can be made easier to understand by using the equivalence just described:
(not (and (>= date 4) (<= date 20)))
is equivalent to
(or (not (>= date 4)) (not (<= date 20)))
The not expressions can be simplified:
(or (< date 4) (> date 20))
In English, this is is like saying the gym is free if it's before the 4th or after the 20th.
5.5.2 (Display page) Try to avoid using "not", when possible.

Try to avoid using not.

Generally, it's easier to understand conditions that don't involve the use of not. Compare
(define (job person)
  (if (member? person '(clint alex ))
    'ta-or-instructor
    'lab-assistant) )
with
(define (job person)
  (if (not (member? person '(clint alex)))
    'lab-assistant
    'ta-or-instructor) )
5.5.3 (Brainstorm) Compare "and" and "or" with "if" and "cond".
Sometimes a condition is easily stated in English using and or or. For example, in fall and spring course loads of between 13 and 19 units do not require a petition:
(define (acceptable-course-load? unit-count)
  (and (>= unit-count 13) (<= unit-count 19)) )
Sometimes, however, it is easier to separate the cases of a condition using if or cond. Write two versions of a procedure named exactly-one?. This procedure takes two boolean arguments; it returns #t if exactly one of the arguments is true, and returns #f if either both are true or both are false. One of your versions should use one or more of and, or, and not without using if or cond. The other should use if or cond without using and, or, or not. Here are examples of how exactly-one? should work.
expression                        value to return
(exactly-one? (= 6 4) (= 4 4))    #t
(exactly-one? #t (= 4 4))         #f
(exactly-one?                     #f
  (member? 'v 'aeiou)
  (> 5 8))
Make sure you test your code before you post it!
5.5.4 (Display page) Use "member?" to simplify comparisons.

Use member? to simplify comparisons.

When you're designing a test to check whether a given word or character is one of a bunch of alternatives, it is typically better to use member? than to do all the comparisons individually. (Recall that member? tests whether its first argument is a member of its second, that is, one of the letters in a word or one of the words in a sentence.) For example, use
(define (vowel? ltr)
  (member? ltr '(a e i o u)) )
rather than
(define (vowel? ltr)
  (or 
    (equal? ltr 'a) 
    (equal? ltr 'e)
    (equal? ltr 'i)
    (equal? ltr 'o)
    (equal? ltr 'u) ) )
and
(define (weekend? day-name)
  (member? day-name '(saturday sunday)) )
rather than
(define (weekend? day-name)
  (or 
    (equal? day-name 'saturday) 
    (equal? day-name 'sunday) ) )
5.6 Test your understanding.(3 steps)
5.6.1 (Display page) Write a procedure using "if" or "cond" but not "and" or "or".

Write a procedure using if or cond but not and or or.

Without using and or or, write a procedure named legal?. Legal? should return #t if its argument is a two-character word that starts with a vowel and ends with a digit; it should return #f otherwise. You should make no assumptions about the argument. For example, legal? should return #f for all of the following arguments.
'(a 5)
+
'abc5
""
'a15
'(x y z)
( )
Put your procedure in a file named legal1.scm.
5.6.2 (Display page) Rewrite the procedure to use "and" or "or" but not "if" or "cond".

Rewrite the procedure to use and or or but not if or cond.

Now rewrite the legal? procedure to use and or or but not if or cond. Put this in a file named legal2.scm.
5.6.3 (WebScheme) Predict the behavior of "and," "or," and "if"

Predict the behavior of and , or , and if

Fill in the blanks below to get the right answer. To see if you are right, press the arrow. If you see a green check, you got it. You will need to wait until you see the word "SchemeHandler" in the upper left corner of the page before you start. If you are unable to run this step, copy the definitions below to your Scheme listener and check your answers there. (Your TA can help. Use cntl-Y to paste in Emacs). Assume you have the following procedures defined:
(define (old-macdonald1? letter)
   (equal? letter (or 'e 'i 'o)))

(define (old-macdonald2? letter)
   (or (equal? letter 'e)
       (equal? letter 'i)
       (equal? letter 'o)))
Scheme Expression Value Correct?
(old-macdonald1? 'e) Status icon
(old-macdonald1? 'i) Status icon
(old-macdonald1? 'q) Status icon
(old-macdonald2? 'e) Status icon
(old-macdonald2? 'i) Status icon
(old-macdonald2? 'q) Status icon
5.7 Homework activities(5 steps)
5.7.1 (Display page) Comment on yesterday's discussions.

Homework 3

There are 5 parts in this homework assignment:


  1. Go back to the last homework and give thoughtful comments for the two discussion questions.

  2. Answer the quiz in the following step titled "Analyze a procedure"

  3. Contribute a post to the discussion "Test the legal? procedure", in a following step.

  4. Contribute a post to the discussion "Compare leap year checkers", in a following step.

  5. Write and submit a conditional expression, as detailed in the final step below. Instructions on how to submit this homework are included in the step.
5.7.2 (Student Assessment) Analyze a procedure
1.Describe, as completely as possible, the argument values for which mystery will return a true value. The answer may be "none".
2.Describe, as completely as possible, the argument values for which mystery will return a false value. The answer may be "none".
3.Describe, as completely as possible, the argument values for which mystery will not return any value, but will instead generate an error message. The answer may be "none".
5.7.3 (Discussion Forum) Test the "legal?" procedure

Testing the legal? procedure

In lab, you programmed the legal? procedure that returns true if its argument is a two-character word that starts with a vowel and ends with a digit, and returns #f otherwise. Here, you are to devise and defend test cases for your procedure. Specifically, you should post a thorough set of test cases. Explain why you picked each case, as well as why you think they collectively would provide evidence that your code works perfectly. We're looking for tests so thorough that after running them, you could put your code into a program that someone's life depends on. For example, legal? should be so well tested that if an airplane navigation program needed to call it, you would feel safe flying on the airplane. Tomorrow, we'll ask you to comment on the thoroughness or incompleteness of at least two other collections of tests that your classmates provide. A comment such as "you may have forgotten to test for ..." would be appropriate.
5.7.4 (Discussion Forum) Compare leap year checkers.

Compare leap year checkers

A year in our calendar is a leap year—it has 366 days—if it is either divisible by 400 or divisible by 4 but not by 100. Thus 2000 was a leap year, 1900 was not, 1996 was, and 1999 wasn't. All of the following procedures return true if the argument year is a leap year and false otherwise. Provide a post that says which version you prefer, and why, explaining what is it that makes one easier to read. Tomorrow, we'll ask you to comment on at least two other posts.
(define (is-leap-year? year)
  (or 
    (is-divisible-by? year 400)
    (and 
      (is-divisible-by? year 4) 
      (not (is-divisible-by? year 100)) ) ) )

(define (is-leap-year? year)
  (cond
    ((is-divisible-by? year 400) #t)
    ((is-divisible-by? year 100) #f)
    (else (is-divisible-by? year 4)) ) )

(define (is-leap-year? year)
  (if (is-divisible-by? year 4)
    (if (is-divisible-by? year 100) 
      #f 
      (is-divisible-by? year 400) )
    #f) )
5.7.5 (Display page) Write a conditional expression

Writing a conditional expression

The game of "rock/paper/scissors" is played by two players. They simultaneously display "rock" (a clenched fist), "paper" (a flat hand), or "scissors" (a two-fingered "V"). If they both show the same thing, the same is a tie; otherwise, "rock" beats "scissors", "scissors" beats "paper", and "paper" beats "rock". Write and test a procedure named game-result that, given two arguments representing what player 1 and player 2 display, returns either tie, win, or loss, depending on whether the two players' choices were the same, player 1's choice beat player 2's, or player 2's beat player 1's. For example:
(game-result 'scissors 'paper)
should return win, while
(game-result 'rock 'paper)
should return loss. Your procedure should consist of calls to if, cond, and/or equal?, and it should make as few calls to equal? or member? as possible. (It is possible to do this with a total of two calls to equal? or member?, but that takes a lot of thinking. We expect most people to be able to do it with a total of seven calls. If you use more than this, you will lose some points.)

Submission instructions

Put your procedure into the file hwk3.scm in a folder called hwk3. For this and future homeworks, you will submit you file(s) electronically. Do this from within unix. Move to the hwk3 directory (cd hwk3) and type:
    > submit hwk3

You should then be asked several questions by the submit program regarding which files to send (depending on the names of files within the directory). It is crucial that your homework file be named hwk3.scm exactly for the submit program to automatically find it. It is also necessary that you invoke the submit hwk3 command from within the hwk3 directory, where hwk3.scm resides. Note that the other files you created in this lab should not be submitted as homework. However, it is likely that one of them will be examined and graded as the "random step" portion of the course.