Logo
  • Home
  • Classes
  • Conveying Computing
  • Exams
  • Fractal Gallery
  • Guides
  • Problem Sets
  • Syllabus

PS1 Comments

Overall, nearly all teams were able to pass all of the tests and do well on Problem Set 1. There are several subtle points about both the evaluation questions, and better ways to defining the procedures, though, so please read through the comments even if you received a Gold Star on PS1.

Questions 1 and 2: For most of the questions, systematically following the evaluation rules we have seen in class should lead you to the correct answer. But for at least two of the parts, the evaluation rules we have seen in class (and in the book) are not enough to know how the expression evaluates.

  1. 1120
    This is a PrimitiveExpression. It is a number that evaluates to the expected value 1120.
  2. (+ 1010 110)
    This is an ApplicationExpression. To evaluate an ApplicationExpression, evaluate all the subexpressions, and apply the value of the first subexpression to the values of the other subexpressions. The first subexpression is the PrimitiveExpression +, which evaluates to the primitive procedure that adds its inputs. The other expressions are also PrimitiveExpressions, which evaluate to number values. The result is the value we get by applying the primitive plus procedure to the operand values 1010 and 110. Since + is a primitive procedure, to apply it we need to know what it does. It adds the operand values, so the result is the number 1120.
  3. +
    This is a PrimitiveExpression. It evaluates to the primitive procedure +, which DrRacket prints out as #<procedure:+>. (Many people incorrectly guessed the + would be an error, but if you followed the evaluation rules we learned you should have been able to determine the correct result.)
  4. (1100 + 20)
    This is an ApplicationExpression. To evaluate an ApplicationExpression, evaluate all the subexpressions, and apply the value of the first subexpression to the values of the other subexpressions. In this case, the first subexpression is 1100 which is a PrimitiveExpression that evaluates to the number value 1100. Since the number is not a procedure, it cannot be applied. Hence, the expression has no value but instead produces and error. The actual error reported by DrRacket is:

    procedure application: expected procedure, given: 1100; arguments were: #<procedure:+> 20

    This makes sense: the application expression expects the first subexpression to be a procedure, but the actual value is the number 1100.

  5. (> 1120 150)
    This is an ApplicationExpression that applies the primitive procedure > to the operand values 1120 and 150. Since 1120 is greater than 150, the result of the application is true, which DrRacket displays as #t.
  6. (and (> 3 2) (> 4 5))
    This is actually a special form. Although it seems like and could be a primitive procedure that performs logical conjunction, the behavior of and is similar to the if special form in that the subexpressions are not necessarily evaluated. In this case, it doesn’t make a difference and you could correctly predict the given expression would evaluate to #f. If you try evaluating (and false (error "You lose!")) you can see that and is not evaluated as a normal application expression. The and expression, (and p q) is evaluated as the if expression, (if p q #f).
  7. (if (> 12 10) "good move" "try again")
    This is an IfExpression, so we follow the evaluation rule for the if expression by evaluating the predicate expression first. It evaluates to true, so the value of the if expression is the value of the consequence expression, "good move". This is a string in double quotes, which evaluates to the string “good move”.
  8. (if (not "cookies") "eat" "starve")
    This one requires following the evaluation rules very carefully. The rule for evaluating an if expression is:

    Evaluation Rule 5: If. To evaluate an if expression:
    a. Evaluate the predicate expression.
    b. If it evaluates to false, the value of the if expression is the value of the alternate expression. Otherwise, the value of the if expression is the value of consequent expression.

    The key is to notice that any non-false value is treated as true. The predicate expression is (not "cookies") which is an ApplicationExpression. The first subexpression is the primitive procedure not. So, to evaluate the application of not we follow the application rule for primitive procedures, “To apply a primitive procedure, just do it.” If we don’t know what the not primitive procedure does, we can only guess why this might mean. It would be (fairly) reasonable to guess the not only works on Boolean values, so (not "cookies") should produce an error. If we don't know what a primitive procedure does, we could find out why looking it up in the DrRacket documentation. You can search the documentation using the search box at the top of http://docs.racket-lang.org/. Searching for not leads to this page, which describes not as:

    Returns #t if v is #f, #f otherwise.

    Hence, if the input value is "cookies", which is not #f, the result of the not application is #f. Hence, the value of the if expression is the value of the alternate expression, "starve".

  9. (+ 1 2 3)
    An ApplicationExpression that evaluates to the number 6.
  10. (if (> 10 5) "true")
    Students who carefully followed the grammar presented in class (and in the book) should have concluded that this is ungrammatical, so should have produced an error. In our grammar, the rule for IfExpression is IfExpression ::= ( ExpressionPredicate ExpressionConsequent ExpressionAlternate). This would not match the given expression, since there is no ExpressionAlternate (and there is no way to produce empty from Expression).

    In fact, Scheme includes another grammar and evaluation rule for IfExpression:

    IfExpression ::= ( ExpressionPredicate ExpressionConsequent)

    Evaluation Rule: To evaluate an IfExpression with no alternate expression, evaluate the predicate expression. If it evaluates to a non-false value, the value of the if expression is the value of the consequence expression. Otherwise, the if expression has no value.

    In this case, the predicate evaluates to true, which is a non-false value, so the if expression evaluates to the string, "true". (Note that this is a string, which is very different from the Boolean value true.)

Question 3: Define a new color orange that looks like orange. Use make-color. If you can't find the right numbers to make the color you want, you can find a chart of the RGB (red, green and blue) values of some popular colors here (or better, think of how the kindergartner would make orange from red and yellow). Use the show-color procedure to check that the color you defined looks like orange.

The kindergartner would make orange by mixing red and yellow. Note that when you make new colors by mixing paints that is working with subtractive colors since paints are reflective, rather than additive colors when working with light (which is what we are doing with the RGB colors). This is why the kindergartner thinks the primary colors are magenta (which is white - green), cyan (which is white - red), and yellow (which is white - blue), but with light they are red, blue, and green.

(The kindergartner has actually been tricked by an evil conspiracy between paint manufacturers and kindergarten teachers into thinking the primary subtractive colors are red, blue, and yellow, which is pretty close to magenta, cyan, and yellow, but not close enough to be able to make all the colors by mixing them in the right quantities. This is why the kindergartner isn't happy with just three paint colors, but insists of getting lots of colors. The primary colors is probably the first "scientific" thing the kindergartner is taught in school, so it is unfortunate that her education starts with being deceived and lied to. Alas, most children never recover from this.)

Fortunately, in this case, red and yellow are pretty close to magenta and yellow, so mixing red and yellow additively produces a color similar to orange. We can use the average-colors procedure defined in mosaic.rkt to produce orange by averaging the red and yellow colors (this requires passing in a list of the colors to average, which you hadn't seen for PS1, but should understand now):

   (define orange (average-colors (list red yellow)))

This makes a pretty good approximation of orange.

Question 4:
Define a procedure brighter? that takes two colors as parameters and evaluates to true (#t) if the first color is brighter than the second color; otherwise, it evaluates to false.

Most of the submitted definitions were like this:

(define brighter?
   (lambda (color1 color2)
      (if (> (+ (get-red color1)
                (get-green color1)
                (get-blue color1))
              (+ (get-red color2)
                 (get-green color2)
                 (get-blue color2))
          true
          false)))

This works pretty well (and was enough to pass all the test cases), but we can do better. Fist, observe that the if expression is unnecessary! The > application already evaluates to a Boolean value, so (if b true false) is equivalent to the shorter and clearer expression b (but only in cases where b always evaluates to either true or false; as we saw in question 1h, the if expression works for predicate expressions of any value). So, a smaller and simpler brighter? definition would be:

(define brighter?
   (lambda (color1 color2)
      (> (+ (get-red color1) (get-green color1) (get-blue color1))
         (+ (get-red color2) (get-green color2) (get-blue color2)))))

This is better, but still could be improved. Note that there is a lot of duplication here, since we need to sum the red, green, and blue parts of both color1 and color2. Anytime there is a lot of duplicated code we should be thinking if there is a way to abstract the duplicated code to make our definition shorter and more versatile. Here, it makes better sense to define a brightness procedure and define brighter? using it:

(define brightness
   (lambda (color)
       (+ (get-red color) (get-green color) (get-blue color))))

(define brighter?
   (lambda (color1 color2)
      (> (brightness color1) (brightness color2)))

This has the advantage that if a cognitive scientist (I think we may have one or (twenty) two in this class) who has studied perception points out that the way human eyes perceive color does not view red, green, and blue as equally bright. This graph shows how the human eye absorbs different colors:



(from Robyn Owens' computer vision lecture)

Hence, a better brightness procedure would be:

(define brightness
   (lambda (color)
       (+ (* 0.299 (get-red color))
          (* 0.587 (get-green color))
          (* 0.114 (get-blue color)))))

Note that because we defined brighter? to use brightness, there is no need to change the definition of brighter? if someone comes up with a better brightness procedure.

Question 5: Write a function closer-color? that can be passed as color-comparator.

Here's a typical and correct closer-color? definition:

(define (closer-color? sample color1 color2)
  (<
    (+ (abs (- (get-red color1) (get-red sample)))
       (abs (- (get-blue color1) (get-blue sample)))
       (abs (- (get-green color1) (get-green sample))))
    (+ (abs (- (get-red color2) (get-red sample)))
       (abs (- (get-blue color2) (get-blue sample)))
       (abs (- (get-green color2) (get-green sample))))))

As with the first brighter? definition, though, there is lots of duplicated code so we should be able to do better.

First, observe that the two subexpressions that are the operands of the < application are both computing essentially the same thing: the closeness between two colors. So, we should define a procedure that abstracts this:

(define color-difference
   (lambda (colora colorb)
      (+ (abs (- (get-red colora) (get-red colorb)))
         (abs (- (get-blue colora) (get-blue colorb)))
         (abs (- (get-green colora) (get-green colorb))))))

Then, we can define closer-color? as:

(define (closer-color? sample color1 color2)
   (< (color-difference color1 sample) (color-difference color2 sample)))

One advantage of this approach is that it makes it easy to change the way we measure color closeness. For example, using square instead of abs produces a slightly better color-difference metric:

(define color-difference
   (lambda (colora colorb)
      (+ (square (- (get-red colora) (get-red colorb)))
         (square (- (get-blue colora) (get-blue colorb)))
         (square (- (get-green colora) (get-green colorb))))))

If we thought we might want to try other metrics, we could even abstract the color difference function like this:

(define generic-color-difference
   (lambda (cf)
      (lambda (colora colorb)
         (+ (cf (- (get-red colora) (get-red colorb)))
            (cf (- (get-blue colora) (get-blue colorb)))
            (cf (- (get-green colora) (get-green colorb)))))))

(define (closer-color? sample color1 color2)
   (< ((generic-color-difference square) color1 sample) ((generic-color-difference square) color2 sample)))

Using square here now has the drawback that if you make a commercially valuable photomosaic this way you might get sued by Robert Silvers! His patent includes the following C code to describe the patented RGB RMS method:

/* This is a variation of RGB RMS error. The final square-root has been eliminated to */
/* speed up the process. We can do this because we only care about relative error. */
/* HSV RMS error or other matching systems could be used here, as long as the goal of */
/* finding source images that are visually similar to the portion of the target image */
/* under consideration is met. */
for(i = 0; i > size; i++) {
rt = (int) ((unsigned char)rmas[i] - (unsigned
char)image->r[i]);
gt = (int) ((unsigned char)gmas[i] - (unsigned char)
image->g[i];
bt = (int) ((unsigned char)bmas[i] - (unsigned
char)image->b[i];
result += (rt*rt+gt*gt+bt*bt);
}

This is actually how the code is formatted in the patent! Unlike most documents, and especially, unlike the code you write for cs1120, patents are written deliberately to be hard to understand. Reformatting the code and removing some of the C cruft for dealing with data types, it is essentially:

   rt = rmas[i] - image->r[i];
   gt = gmas[i] - image->g[i];
   bt = bmas[i] - image->b[i];
   result += (rt*rt + gt*gt + bt*bt);

You are not expected to know the C programming language, of course, but might still be able to guess that this is computing the differences in the red, green, and blue components of the tile and image, storing them in the variables rt, gt, and bt, and then computing the result as the sum of the squares of the color component differences.

Print Friendly Print Get a PDF version of this webpage PDF

Leave a Reply Cancel reply

You must be logged in to post a comment.

cs1120 | RSS | Comments RSS | Book | Using These Materials | Login | Admin | Powered by Wordpress