• No results found

3.7 Basic Programming Examples (Hunk P)

3.7.3 Copying Lists

There are two common senses of copying, shallow copying, and deep copying. A shallow copy makes a copy of one object, and the copy has pointers to the same objects that the original did.

A deep copy copies not only the top-level objects in a data structure, but the ones below that, and so on recursively, so that a whole new data structure is created.

For lists, which are made up of more than one object, it is often useful to copy the spine of the list, i.e., doing a deep copy along the cdr's only. We typically think of a list as being like a special

kind of object, even though it's really a sequence of pair objects. It's therefore natural to copy \just the list."

If we just want to do a shallow copy, we can dene pair-copyto copy a pair, without copying

anything else.

In these examples, I'll assume we only want to copy list structure|that is a connected set of pairs. Whenever we come to something that's not a pair, we stop copying and the copy shares structure with the original. (These aren't standard Scheme procedures.)

Here's a truly shallow copy, just copying a single pair:

(define (pair-copy pr)

(cons (car pr) (cdr pr)))

If we want to do a deep copy, we can use recursion to copy carorcdrvalues that are also pairs.

The following code forlists-deep-copyassumes that the structure to be copied is a tree of pairs.

(If there is any shared structure, it will be copied each time it is reached. If there's a directed cycle,

deep-copywill loop innitely.)

(define (lists-deep-copy thing) (if (pair? thing)

(cons (lists-deep-copy (car thing)) (lists-deep-copy (cdr thing))) thing))

To copy the spine of a list, we can use this

(define (list-copy lis) (cond ((pair? lis)

(cons (car lis)

(list-copy (cdr lis)))) ((null? lis)

'())))

need to discuss error checking, tastefulness...]

3.7.4

append

and

reverse

append takes any number of lists as arguments, and returns a list with all of their elements. reverse takes a list and returns a new list, with the same elements but in the opposite order.

Note that like most Scheme procedures, neither of these procedures is destructive|each creates a new list without modifying its argument.

3.7.4.1

append

Here's a simple two-argument version ofappend: (define (append2 lis1 lis2)

(if (null? lis1) lis2

(cons (car lis1)

(append2 (cdr lis1) lis2))))

Note that append2 copies its rst list argument, but the result simply shares a pointer to the

last list argument|the last list is not copied, so the result shares structure with that list. This is also true of the standard Scheme functionappend, which can take any number of lists as arguments.

The rst n-1 lists are copied, but the last is shared.

Suppose we have dened two lists, fooand bar, like this: (define foo '(x y z))

(define bar '(a b))

(define baz (append bar foo))

The result will be thatbazshares structure withfoo, but not withbar. Changes to the list via foowill also be visible via baz.

+---+ | | \|/ | +---+ +---+---+ +---+---+ +---+---+ | foo | *-+--->| * | *-+---->| * | *-+--->| * | * | | +---+ +-+-+---+ +-+-+---+ +-+-+---+ | | | | | \|/ \|/ \|/ | x y z | | | +---+ +---+---+ +---+---+ | bar | *-+--->| * | *-+---->| * | * | | +---+ +-+-+---+ +-+-+---+ | | | | \|/ \|/ | a b | /|\ /|\ | | | | +---+ +---+---+ +---+---+ | baz | *-+--->| * | *-+---->| * | *-+---+ +---+ +---+---+ +---+---+

give code for full-blown append, using dot notation for variable arity arg list? Do it later, fwd ref from here? ]

3.7.4.2

reverse

reverse returns a reversed copy of a list.

There's an easy (but slow) way to dene reverse in terms of append. We just take the rst

element o the list, reverse the rest of the list, and append the rst element to the end.

(define (reverse lis) (if (null? lis)

'()

(append (reverse (cdr lis)) (list (car lis)))))

Think about how this works. reverserecurses down the list, calling itself on thecdrof the list

at each recursive step, until the recursion stops at the end of the list. (This last call returns the empty list, which is the reverse of the empty list.) At each step, we use carto peel o one element

of the list, and hold onto it until the recursive call returns.

The reversed lists are handed back up through the returns, with the cars being slapped on the rear of the list at each return step. We end up constructing the new list back-to-front on the way up from the recursion.

There are two problems coding reverse this way, and later I'll show better versions. (They'll

still be recursive, and won't use loops or assignment.)

The rst problem is that each call to append takes time proportional to the length of the list

it's given. (Remember that append has to copy all of the elements in the rst list it's given.) We have to copy the \rest" of the list (using appendstarting at each pair in the list. On average, we

copy half the list at a given recursive step, so since we do this for every pair in the list, we have an n-squared algorithm.

Another problem is that we're doing things on the way back up from recursion, which turns out to be more expensive than doing things on the way down. As I'll explain in a later chapter, Scheme can do recursion very eciently if everything is done in a forward direction|it can optimize away all but one of the returns. (Luckily, this is easy to do.)

3.7.5

map

and

for-each

map and for-each are used to apply a procedure to elements of lists. They're good for coding

repetitive operations over sets of objects.

3.7.5.1

map

map takes a procedure and applies it to the elements of a list (or corresponding elements of a

set of lists), returning a list of results.

For example, if we want to double the elements of a list, we can use map and the double

Scheme>(map double '(1 2 3)) (2 4 6)

If the procedure we're calling takes more than one argument, we can pass two lists of arguments to map. For example, if we want to add corresponding elements of two lists, and get back a

corresponding list of their sums, we can do this:

Scheme>(map + '(1 2 3) '(4 5 6)) (5 79)

Right now, we'll just write a simplied version ofmap, which takes one list of values and applies

a one-argument procedure to them.

(define (map proc lis) (cond ((null? lis)

'())

((pair? lis)

(cons (proc (car lis))

(map proc (cdr lis))))))

Notice thatmapmay construct a list of results front-to-back, or back-to-front, depending on the

order of the evaluation of the arguments tocons. That is, it may apply the mapped procedure on

the way down during recursion, or on the way back up. (This is allowed by the Scheme standard| the order of the results in the resulting list corresponds to the ordering of the argument list(s), but the dynamic order of applications is not specied.)

3.7.5.2

for-each

Like map,for-eachapplies a procedure to each element of a list, or to corresponding elements

of a set of lists. Unlikemap,for-eachdiscards the values returned by all of the applications except

the last, and returns the last value. (The applications are also guaranteed to occur in front-to-back list order.) This is sort of like what abeginexpression does, except that the \subexpressions" are

not textually written out|they're applications of a rst-class procedure to list items.

Like begin, for-each is used to execute expressions in sequence, for eect rather than value,

except that the last value may be useful. give code ]

3.7.6

member

and

assoc

, and friends

The standard Scheme procedures member and assocare used for searching lists. I'll show how

they can be implemented in Scheme, even though every Scheme system includes them.

Each of these procedures has two alternative versions, which use dierent equality tests (eq?or eqv?) when searching for an item in list.

3.7.6.1

member

,

memq

, and

memv

member searches a list for an item, and returns the remainder of the list starting at the point

where that item is found. (That is, it returns the pair whosecarrefers to the item.) It returns#f

if the item is not in the list.

For example, (member 3 '(1 4 3 2)) returns (3 2), and (member 'foo '(bar baz quux)) re-

turns #f.

Lists are often used as sets, and member serves nicely as a test of set membership. If an item

is not found, it returns#f, and if it is, it returns a pair. Since a pair is a true value, the result of membercan be used like a boolean in a conditional.

Since member returns the \rest" of a list, starting with the point where the item is found, it can also be particularly useful with ordered lists, by skipping past all of the elements up to some desired point, and returning the rest.

(define (member thing lis) (if (pair? lis)

(if (equal? (car lis) thing) lis

(member thing (cdr lis)))))

Note thatmemberuses theequal?test (data structure equivalence) when searching. This makes

sense in situations where you want same-structured data structures to count as \the same." (For example, if you're searching a list of lists, and you want a sublist that has the same structure as the target list to count as \the same.") Note that if the elements of the list are circular data structures,

If you want to search for a particular object, you should use memq?, which is likememberexcept

that it uses the eq?test, and may be much faster.

If the list may include numbers, and you want copies of the same number to count as \the same", you should use memv.

3.7.6.2

assoc

,

assq

, and

assv

assoc is used to search a special kind of nested list called an association list. Association lists

are often used to represent small tables.

An association list is a list of lists. Each sublist represents an association between a key and a list of values. The car of the list is taken as the key eld, but the whole list of values is returned.

(Typically, an association list is used as a simple table to map keys to single values. In that case, you must remember to take the cadrof the sublist thatassocreturns.)

Some example uses:

Scheme>(assoc 'julie '((paul august 22) (julie feb 9) (veronique march 28))) (julie february 9)

Scheme>(assoc 'key2 '((key1 val1) (key2 val2) (key0 val0))) (key2 val2)

Scheme>(cadr (assoc 'key2 '((key1 val1) (key2 val2) (key0 val0)))) val2

Scheme>(assoc '(feb 9)

'(((aug 1) maggie phil) ((feb 9) jim heloise) ((jan 6) declan))) ((feb 9) jim heloise)

(define (assoc thing alist) (cond ((pair? alist))

(if (equal? (car (car alist)) thing) (car alist)

(assoc thing (cdr alist))) (#t #f)))

Like member, assocuses the equal?test when searching a list. This is what you want if (and

only if) you want same-structured data structures to count as \the same."

assqis likeassoc, but uses theeq?test. This is the most commonly-used routine for searching

association lists, because symbols are commonly used as keys for association lists. (The nameassq

suggests \associate using the eq?test.")

If the keys may be numbers, assv? should probably be used instead. It considers = numbers

the same, but otherwise tests object identity, like eq?. (The name assvsuggests \associate using

theeqv?test.")

3.7.7 Procedure Specialization, Composition, and Currying