• No results found

3.6 Some Other Useful Data Types

3.6.3 Lists Again

Suppose we want to make a list of symbols whose print names are the English words for the rst ve integers. We could do it using quoting, of course, like this:

Scheme>(define firstfive '(one two three four five)) #void

Scheme>firstfive

(one two three four five)

We don't have to quote each symbol individually. Within a quote expression, everything is

assumed to be literal data, not expressions to evaluate.

We could also do it by calling listto construct the list, and handing it each of the ve symbols

as literals. To do that, we have to quote them, so that Scheme won't think we're referring to variables named one,two, etc.

Scheme>(define firstfive (list 'one 'two 'three 'four 'five)) #void

Scheme>firstfive

(one two three four five)

Since listis a procedure, its argument expressions are evaluated. We use aquotearound each

expression, so that it will return a pointer to the appropriate symbol, rather than the value of the variable by the same name.

This works whether or not there is a variable by that name, because names of symbols and names of variables are completely dierent things.

For example, even after evaluating the above expressions, attempting to evaluate the expression

fourwill be an error, unless we've dened a variable namedfour. The existence of a symbol with

3.6.3.1 Heterogeneous Lists

Since Scheme is dynamically typed, we can put any kind of object in a list. So far, we've made a list of integers and a list of symbols. We can also make a list of dierent kinds of things, such as a list of integers, symbols, and lists.

Scheme>(define mixed5 '(one 2 (three and a) "four" 5)) #void

Scheme>mixed5

(one 2 (three and a) "four" 5)

Here we've constructed a mixed list whose rst element is a symbol, the second is an integer, the third is a list of symbols, the fourth is a string, and the fth is another integer. (The technical term for a mixed list is a \heterogeneous list.")

We can draw it like this:

+---+ mixed5 | +--+-->+---+---+ +---+---+ +---+---+ +---+---+ +---+---+ +---+ | + | +-+->| + | +-+->| + | +-+->| + | +-+->| + | * | +-+-+---+ +-+-+---+ +-+-+---+ +-+-+---+ +-+-+---+ | | | | | \|/ \|/ | \|/ \|/ one 2 | "four" 5 | \|/ +---+---+ +---+---+ +---+---+ | + | +-+->| + | +-+->| + | * | +-+-+---+ +-+-+---+ +-+-+---+ | | | \|/ \|/ \|/ three and a

Notice that we draw the symbols (one, three, and, and a) as simple sequences of characters.

This is just a drawing convention. They're really objects, like pairs are. We draw strings similarly, but with double quotes around them. Don't be fooled|these are objects on the heap, too. We just draw them this way to keep the picture from getting cluttered up.

3.6.3.2 Operations on Lists

We've already seen two list-processing procedures that you'll use a lot, carand cdr. car takes

a pointer to a pair, and extracts the value of its rst (car) eld. cdrtakes a pointer to a pair and

returns the value of its second (cdr) eld.

(It might have been better if car had been called first and cdr had been called rest, since

that's more suggestive of how they're used: a pointer to the rst item in a list, and a pointer to the pair that heads the rest of the list.)

Given our list stored in mixed5, we can extract parts of the list usingcar andcdr. Scheme>(car mixed5)

one

Scheme>(cdr mixed5)

(2 (three and a) "four" five)

By using car and cdr multiple times, we can extract things beyond the rst element. For

example, taking thecdr of thecdr of a list skips the rst two elements, and returns the rest: Scheme>(cdr (cdr mixed5))

((three and a) "four" 5)

Taking the car of that list (that is, the car of thecdrof the cdr) returns the rst item in that

list:

Scheme>(car (cdr (cdr mixed5))) (three and a)

We can keep doing this, for example taking the second element of that sublist by taking the car of its cdr.

Scheme>(car (cdr (car (cdr (cdr mixed5))))) and

This starts to get tedious and confusing|too many nestings of procedures that do too little at each step|so Scheme provides a handful of procedures that do two list operations at a whack. The two most important ones arecadrand cddr.

cadrtakes the carof the cdr, which gives you the second item in the list. cddrtakes thecdr

of the cdr, skipping the rst two pairs in a list and returning the rest of the list.

This lets us do the same thing we did above, a little more concisely and readably:

Scheme>(cadr (car (cddr mixed5))) and

With a little practice, it's not hard to read a few nested expressions like this. In this example, taking the cddrof mixed5 skips down the list two places, giving us the list that starts with the

sublist we want. Then taking thecarof that gives us the sublist itself o the front of that list, and

taking thecadrof that gives us the second item in the sublist.

Of course, even if Scheme didn't provide cadrand cdr, you could write them yourself in terms

of carand cdr:

(define (cadr x) (car (cdr x))) (define (cddr x)

(cdr (cdr x)))

Scheme actually provides predened list operations for all combinations of up to four car's and cdr's. For example, cadadrtakes the cadr of the cadr. (The naming scheme is that the pattern

of a's and d's reects the equivalent nesting of calls tocar andcdr.)

You probably won't want to bother with most of those, because the names aren't very intuitive. Two procedures that are worth knowing are list-refand list-tail.

(list-reflist n)extracts the nth element of a listlist, which is equivalent to n-1 applications

of cdrfollowed bycar. For example, (list-ref '(a b c d e) 3) is equivalent to(car (cdr (cdr '(a b c d e)))), and returnsd.)

In eect, you can index into a list as though it were an array, using list-ref. (Of course, the

access time for an element of a list is linear in the index of the element. If you need constant-time access, you can use vectors, i.e., one-dimensional arrays.) Notice that the numbering is zero-based, which is why (list-ref lis 3) returns the fourth element of a lis. This is consistent with the

(list-taillist n)skips the rst n elements of a list and returns a pointer to the rest, which is

equivalent to repeated applications ofcdr. (This is standard R4RS Scheme, but not IEEE Scheme.

If your Scheme doesn't provide list-tail, you can easily write your own.)

These two procedures can make it much clearer what you're doing when you extract elements from nested lists. Suppose that we have a list foo, which is a triply-nested list|a list of lists of

lists, and we want to extract the second element of the bottom-level list that is the third element of the middle-level list that is the fourth element of the outermost list.

We could write (car (cdr (car (cdr (cdr (car (cdr (cdr (cdr foo))))))))), but that's

pretty hard to read. If we use cadr,caddr, and cadddr, we can make it somewhat more readable

by using one function call at each level of structure: (cadr (caddr (cadddr foo))). But it's still

clearer to write (list-ref (list-ref (list-ref foo 4) 3) 2)

or (indented)

(list-ref (list-ref (list-ref foo 4) 3)

2)

list-ref and list-tailare much more convenient than things like caddr when the indexes

into a list vary at run time. For example, we might use an index variablei(or some other expression

that returns an integer) to pick out the ith member of a list: (list-ref foo i). Writing this with carand cdrwould require writing a loop or recursion to perform n-1 cdr's and acar.

================================================================== This is the end of Hunk N

At this point, you should go back to the previous chapter and read Hunk O before returning here and continuing this tutorial. ==================================================================

(Go BACK to read Hunk O, which starts at Section 2.9 Tail Recursion], page 79.)