5 Working with conditional expressions | (7 activities) |
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). |
Introduction to ConditionalsSo 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. |
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:
(if (> 5 4) (+ 3 7) (- 3 7))Since 5 is bigger than 4, Scheme does (+ 3 7). It never even looks at (- 3 7). |
Experiment with the built-in predicate procedures in the Scheme interpreter. In particular, answer the questions:
|
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) ___ ) |
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:
|
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). |
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. |
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. |
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. |
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) ) ) |
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) |
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 falseIf 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
|
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. |
Test supporting proceduresIf 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. |
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. |
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. |
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) ) |
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! |
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) ) ) |
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. |
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. |