Comp 210 Lab 3: let, structures

(Computers Can be Slow)

This lab
  • A Note on Formatting
  • Hand-Evaluating a Complex Function
  • Using Let
  • Using Structures
  • Shared Data
  • The UNIX Corner

  • A Note on Formatting

    Formatting your code does matter. While Dr. Scheme may not pay any attention to spaces and returns, human beings do. Adding spaces and carriage returns in the "right" amount makes your code much more understandable to people reading your code. In the short term, human beings are grading your homework. In the long term, real programs are never finished: the author or other programmers go back and modify the code, so it is important and easy to indent your program legibly.

    Some particular points:

  • Preceding every open parenthesis must be a space, an open parenthesis, or the beginning of a line. No exceptions. (By "parenthesis", here I include both round-paren and square-bracket.)
  • Dr Scheme will indent lines for you, but you have to decide where to have one line stop and the next start. If a line is getting too wide, start a new one at a convenient place. For instance:
    ... (cond [(null? argument)  (some-function other-argument)]
              [(complicated-expression (car argument)) (complicated-function argument other-argument)]
              [else (another-complicated-function (cdr argument))])
    
    is easier to read when written:
    ... (cond [(null? argument)
               (some-function other-argument)]
              [(complicated-expression (car argument))
               (complicated-function argument other-argument)]
              [else
               (another-complicated-function (cdr argument))])
    
    Note how a square-bracket is used to help let your eye know where you are in the code.
  • Go ahead and have your last line of code end with a whole passel of piled-up parentheses. Scheme uses parentheses differently than languages like C or Pascal, so it is okay to indent them differently than those other languages.
  • The bottom line is, follow the examples in the book and lecture, and (after a while) your own aesthetic sense, and you should end up with well-formatted code.

    Hand-Evaluating a Complex Function

    Starting in class last Friday, we saw some functions that return not just a simple argument (a number), but instead calculated a complex value (e.g., a list). While this might seem different, it's really not hard at all: we just use cons to build our return value:

    (define between
      (lambda (low high)
        (if (> low high)
            null
            (cons low (between (+ low 1) high)))))
    
    What are the major steps in evaluating (between 19 23)? They should be similar to the product we did in last week's lab, but instead of having a bunch of * pile up during evaluation, you'll have cons out front. (For * we had a "base case" of 1. What's the "base case" for cons?) (The answer is here.) Another example of a function for you to evaluate is here.

    Censoring Your Code

    The following code can be had by requiring the library Projects/lab3-library.ss in the class account.
    ;; (strongest-coalition-1  popularities)
    ;; popularities is a list of numbers: popularity ratings of politicians
    ;; Return the strength of the most popular coalition,
    ;;  where a coalition's strength is as follows:
    ;;   Think of people joining a group, one by one.
    ;;      If they're more popular than
    ;;      any existing coalition, they found their own new coalition.
    ;;      Otherwise they join the existing most popular coalition, adding
    ;;      their popularity.
    ;;
    (define strongest-coalition-1
      (lambda (popularities)
        (cond [(null? popularities) 0]
              [else (if (>= (car popularities)
                            (strongest-coalition-1 (cdr popularities)))
                        (car popularities)
                        (+ (car popularities)
                           (strongest-coalition-1 (cdr popularities))))])))
    
    
    ;; save myself some typing!
    ;;
    (define sc1 strongest-coalition-1)
    
    The library also provides a couple of specific lists of varying sizes: short1,short2,med1,med2,long1,long2. These are just placeholders; just evaluate the names to see their values of these lists.

    Now evaluate (sc1 short1) and (sc1 short2). Are these the results you expected?

    Let's test our function on a couple of medium-sized lists, m1 m2. Look at what these lists are, then pass them to strongest-coalition. What happened?

    You can use the function (bad-list n) to make a list with n items that will cause sc1 some difficulty. Exactly how long a list do you need to before you have to wait too long for an answer?

    Note that our code contained some repeated code: in order to compute (strongest-coalition popularities), we may compute the value of (strongest-coalition (cdr popularities)) two times. How can we avoid re-computing an answer?

    The answer is let, a new Scheme statement (covered in Chapter 5 (postscript)). The let statement allows you to create new, temporary placeholders, and give them a value. The value of those new placeholders can then be used repeatedly. Here's a simple example:

         (let ([G 6.670e-11]      ;; gravitation constant, in N m^2/kg^2
               [M1 1.989e30]      ;; mass of sun in kg
               [M2 5.965e24]      ;; mass of earth in kg
               [r  1.496e10])     ;; mean earth-sun dist in m
           (/ (* G M1 M2 ) (* r r)))  ;; gravitational force earth/sun, in newtons
    
    This shows one simple use of let: to make a calculation clearer by name sub-computations. The placeholders M1, etc, have their values only inside the resultExp. For instance, try evaluating the above expression directly in Dr. Scheme. After that, evaluate M1, and you should get the ol' "reference to unbound identifier: M1" error.

    In general, the syntax of the let statement is:

            (let  ([ph1 exp1]
                   [ph2 exp2]
                   ...      
                   [phn expn])
                 resultExp)
    
    where: ph1 through phn are place-holders (variable names, if you prefer), and exp1 through expn are expressions, whose value is associated with the placeholders. What is the value returned by the entire let statement? It's the value of the expression resultExp, which may contain references to the placeholders ph1, etc.

    In writing your own lets, notice how one set of parentheses is around each placeholder/expression pair, and another set of parentheses is around the list of these pairs. (I use square-parentheses to help distinguish them, but square and round parentheses are equivalent to Dr. Scheme.) Also, be aware that the expression exp2 can not use the placeholder ph1! Of course, resultExpcan itself be a let statement (evil grin).

    A more interesting use of let is to reduce redundant computation. Let's try "censoring" our code for strongest-coalition-1, and only compute once the intermediate value (strongest-coalition (cdr popularities)). We'll store this value inside a let using a local placeholder mob-pop.

    (define strongest-coalition-2
      (lambda (popularities)
        (cond [(null? popularities) 0]
              [else (let ([mob-pop (strongest-coalition-2 (cdr popularities))])
                         (if (>= (car popularities) mob-pop)
                             (car popularities)
                             (+ (car popularities) mob-pop)))])))
    
    (define sc2 strongest-coalition-2)
    
    Now try (sc2 med1) and (sc2 med2) again, or apply sc2 to the long lists.

    Self-test on let:
    Write the function solve-quadratic, which takes three numbers a,b,c and returns a list of the real roots of ax^2+bx+c=0 (that is, what are all the real values of x that make this statement true?). You can do it as you like, though my let statement happened to use three placeholders: discriminant, denominator, and negB. If you want to get fancy, make sure your function works for degenerate cases like a=0, etc.


    Structures

    Raising the Language Level

    In order to use define-structure, we must raise the language level that we are using. From the Language menu in Dr. Scheme, select "Intermediate". Congratulations!

    Structures for Family Trees

    We've seen lots of examples, where we take apart structures with awkward combinations like (car (cdr (cdr ...))) and such. Really, what we'd like, is some way to bundle together related values, and also give nice names to functions which extract items from these bundles. Structures are just the ticket; they are discussed in Chapter 6 (postscript). Whenever we have data structures which contain (say) exactly three elements, a structure is the way to go. As an example, consider the by-now familiar data description from lecture:
    A Family Tree is
  • 'unknown, or
  • (list mom name dad),
    where mom,dad are family trees, and name is a symbol.
  • In this second case, call it a "branch", we always have exactly three elements, so let's make our own structure for this:
    (define-structure (branch mom name dad))
    
    What does our data description look like now? Similar to before:
    A Family Tree is now
  • 'unknown, or
  • (make-branch mom name dad),
    where mom,dad are Family Trees, and name is a symbol.
  • Okay, great, we've taken a short word "list" and replaced it with a long name "define-structure". Sounds like a loss. However, this statement creates some new Scheme functions for us automagically:
  • make-branch to make such a structure,
  • functions branch-mom, branch-name, and cwp-dad, to extract the desired data from a structure so created, and
  • branch? which takes an object, and returns a Boolean value, depending on whether the object was created with make-branch.
  • An example would help, about now. Try typing in the following.

    (define a-tree      (make-branch 'unknown 'gabi 'unknown))
    (branch-name a-tree)
    (branch-dad a-tree)
    (branch? a-tree)
    
    So using structures, nothing has really changed, except that instead of having to use ugly statements like (car (cdr (cdr a-tree))), you can use the more informative and less error-prone (branch-dad a-tree).

    Exercise: What does a template look like now, for Family Trees?
    Using these new structures, write the function snobby which takes a Family Tree, and returns #t if 'Lady-Pfefferplump occurs in the tree, and #f otherwise.

    You can also browse an application of Complex Web Pages using Structures.


    Sharing Data

             Gabi    Helmut
              |         |
              +---------+
                  |
              +----------+
              |          |
    Hunko  Chantelle   Pierre  Lady-Pfefferplump
    |        |           |        |
    +--------+           +--------+
       |                      |
     Bambam                Pebbles
       |                      |
       +----------------------+
                 |
              Godzillette
    
    If you wanted to represent this tree by writing one humongous list, you'd notice that you make the family tree for Pierre's parents once, and later you separately make the family tree for Chantelle's parents, getting some duplication even though they are the same people. We will talk later about how Scheme actually stores data; for now observe that the following lets you define the above tree without defining parts of the tree twice. Cut-and-past the following code into Dr. Scheme (after you've done a define-structure for branch as described above).
    (define gabi      (make-branch 'unknown 'gabi 'unknown))
    (define helmut    (make-branch 'unknown 'helmut 'unknown))
    (define pierre    (make-branch gabi 'pierre helmut))
    (define chantelle (make-branch gabi 'chantelle helmut))
    (define pebbles   (make-branch
                           (make-branch 'unknown 'Lady-Pfefferplump 'unknown)
                           'pebbles
                           pierre))
    (define bambam    (make-branch chantelle 
                           'bambam
                           (make-branch 'unknown 'Hunko 'unknown)))
    (define godzillette  (make-branch pebbles 'godzillette bambam))
    
    (What is the (huge) difference between gabi and 'gabi?) When you evaluate the place-holder godzillette, who is related to Gabi and Helmut in two different ways, what is printed?

    The UNIX corner

  • quota -v
    The command quota -v tells you how much space you are currently using, and how much you're allowed to use total. Your quota is probably 4500; this is in kilobytes. In addition to your quota is your limit, or ``hard quota''. You can actually surpass your quota a little bit, for a little while; you can never exceed your limit. If you go above your quota (but under your limit), you have seven days to remove some files and go back under your quota. (If this is the situation, you'll be warned when you log on how much of those seven days are remaining.) If your seven days expires, or if you are at your hard quota, then you will not be allowed to write any files until you are under quota.

    If you need your quota raised (and have good reason), you can fill out an electronic form available from the Owlnet home page.

    Note that while 4.5MB is usually plenty of space for all the text files you care to read and write, images can quickly take up large amounts of space. Moreover, Netscape will silently write images you view to your directory (see Netscape--Caching in the Lab 1 handout). If you are having quota problems, the first place to look for files to delete is in the directory .netscape/.cache/ inside your home directory. You can limit how much space Netscape will use for caching by going to Netscape's Option menu and selecting ``Network Preferences'', and changing the size of the disk cache.

  • du
    This "Disk Usage" command shows a list of your directories, and how much disk space each one takes. It's handy for targeting sizable directories. See man du for some options.
  • grep
    This handy command looks inside a file for a particular pattern. Suppose you want to find all occurrences of define inside your program file, called (say) hw2.ss. Try grep define hw2.ss. Some handy flags to grep are grep -i which makes searching case-independent, and grep -v which prints out all lines not containing the indicated pattern. You can also give grep more than one file to look through: grep define hw2.ss hw3.ss.
  • Wildcard Expansion
    The UNIX shell interprets a few characters specially. ? expands to any character to match a filename, and * similarly expands to any sequence of zero or more characters. For example, *.ss expands to become all filenames ending in the three characters .ss. prog* expands to become all filenames starting with prog; you can think of more variations yourself.

    For instance, if you have several scheme source files in the same directory, you could say grep define *.ss. If you you had your various programs in different subdirectories, like hw1/, hw2/, etc., then you could try grep define hw?/*.ss or even grep define */*.ss.


  • Back to Comp 210 Home