<<

Lists in Lisp and Scheme Lisp Lists • Lists are Lisp’s fundamental data structures, • Lists in Lisp and its descendants are very but there are others Lists in Lisp simple linked lists – Arrays, characters, strings, etc. – Represented as a linear chain of nodes – has moved on from being • Each node has a (pointer to) a value (car of merely a LISt Processor and Scheme list) and a pointer to the next node (cdr of list) • However, to understand Lisp and Scheme – Last node’s cdr pointer is to null you must understand lists • Lists are immutable in Scheme – common functions on them • Typical access pattern is to traverse the list a – how to build other useful data structures from its head processing each node with them

In the beginning was the (or pair) Pairs Box and pointer notation • What cons really does is combines two objects into a a Common notation: two-part object called a cons in Lisp and a pair in use diagonal line in • Lists in Lisp and Scheme are defined as (a) cdr part of a cons Scheme cell for a pointer to • Conceptually, a cons is a pair of pointers -- the first is pairs null a a null the car, and the second is the cdr • Any non empty list can be considered as a • provide a convenient representation for pairs A one element list (a) of any type pair of the first element and the rest of • the list The two halves of a cons can point to any kind of (a b c) object, including conses • We use one half of a cons cell to point to • This is the mechanism for building lists the first element of the list, and the other • (pair? ‘(1 2) ) => #t a b c to point to the rest of the list (which is a null either another cons or nil) A list of three elements (a b c)

1 What sort of list is this? Z Pair? Equality • Each time you call (define L1 (cons 'a null)) • The function pair? returns true if its cons, Scheme L1 allocates a new (A) argument is a cons cell (define L2 (cons 'a null))) a d cons cell from • The equivalent function in CL is consp memory with room L2 • So list? could be defined: for two pointers (A) (eq? L1 L2) (define (list? x) (or (null? x) (pair? x))) • If we call cons twice #f with the same args, b c • Since everything that is not a pair is an (equal? L1 L2) we get two values #t Z is a list with three > (define Z (list ‘a (list ‘b ‘c) ‘d)) atom, the predicate atom could be defined: elements: (i) the atom a, > Z that look the same, (and (eq? (car L1)(car L2)) (ii) a list of two elements, (a (b c) d) (define (atom? x) (not (pair? x))) but are distinct (eq? (cdr L1)(cdr L2))) b & c and (iii) the atom d. > (car (cdr z)) objects #t ??

Equal? Equal? Use trace to see how it works • Do two lists have the same elements? (define (myequal? x y) > (require racket/trace) >(myequal? (c) (c)) • Scheme provides a predicate equal? that is like > (trace myequal?) > (myequal? c c) Java’s equal method ; this is how equal? could be defined > ( myequal ? '(a b c) '(a b c)) < #t • eq? returns true iff its arguments are the same (cond ((and (number? x) (number? y))(= x y)) >(myequal? (a b c) (a b c)) >(myequal? () ()) > (myequal? a a) <#t object, and ((and (string? x) (string? y)) (string=? x y)) < #t #t • equal?, more or less, returns true if its ((not (pair? x)) (eq? x y)) >(myequal? (b c) (b c)) arguments would print the same. ((not (pair? y)) #f) > (myequal? b b) > (equal? L1 L2) ((myequal? (car x) (car y)) < #t #t (myequal? (cdr x) (cdr y))) • Trace is a debugging package showing what args a user- • Note: (eq? x y) implies (equal? x y) defined function is called with and what it returns (#t #f))) • The require function loads the package if needed

2 Does Lisp have pointers? Variables point to their values Does Scheme have pointers? • A secret to understanding Lisp is to realize that > (define x ‘(a b)) environment • variables have values in the same way that lists The location in memory associated with the > x VAR VALUE have elements variable x does not contain the list itself, but a (a b) … a b pointer to it. • As pairs have pointers to their elements, > (define y x) • variables have pointers to their values x When we assign the same value to y, Scheme y copies the pointer, not the list. • Scheme maintains a data structure … (a b) • Therefore, what would the value of representing the mapping of variables to their y > (eq? x y) current values. … be, #t or #f?

Variables point to their values Variables point to their values Length is a simple function on Lists

> (define x ‘(a b)) environment > (define x ‘(a b)) environment • The built-in function length takes a list and > x VAR VALUE > x VAR VALUE returns the number of its top-level elements (a b) … a b (a b) … a b • Here’s how we could implement it > (define y x) x > (define y x) x (define (length L) y y (if (null? L) 0 (+ 1 (length (cdr L)))) (a b) … (a b) … 1 2 • As typical in dynamically typed languages y > (set! y ‘(1 2)) y (e.g., Python), we do minimal type checking … > y … – The underlying interpreter does it for us (1 2) – Get run-time error if we apply length to a non-list

3 Building Lists Copy-list Append • append returns the • >(append ‘(a b) ‘(c d)) list-copy takes a list and returns a copy of it • List-copy is a Lisp built-in (as copy-list) that concatenation of any number of lists (a b c d) • The new list has the same elements, but could be defined in Scheme as: > (append ‘((a)(b)) ‘(((c)))) contained in new pairs • Append copies its (define (list-copy s) arguments except ((a) (b) ((c))) > (set! x ‘(a b c)) (if (pair? s) the last > (append ‘(a b) ‘(c d) ‘(e)) (a b c d e) (a b c) (cons (list-copy (car s)) –If not, it would have > (set! y (list-copy x)) to modify the lists >(append ‘(a b) ‘()) (list-copy (cdr s))) (a b) (a b c) –Such side effects s)) are undesirable in >(append ‘(a b)) • Spend a few minutes to draw a box diagram • Given a non-atomic s-expression, it makes and functional (a b) of x and y to show where the pointers point returns a complete copy (e.g., not just the top- languages >(append) level spine) ()

Append Visualizing Append Visualizing Append > (load "append2.ss") > (require racket/trace) > (load "append2.ss") environment • The two argument version of append could be > (define L1 '(1 2)) > (trace append2) > (define L1 '(1 2)) VAR VALUE > (append2 L1 L2) defined like this > (define L2 '(a b)) > (define L2 '(a b)) a b >(append2 (1 2) (a b)) … (define (append2 s1 s2) > (define L3 (append2 L1 L2)) > (define L3 > L3 > (append2 (2) (a b)) (append2 L1 L2)) L2 (if (null? s1) (1 2 a b) > >(append2 () (a b)) > L3 s2 > L1 < <(a b) (1 2 a b) L1 < (2 a b) (cons (car s1) (1 2) > L1 1 2 <(1 2 a b) L3 > L2 (1 2) (append2 (cdr s1) s2)))) (1 2 a b) (a b) > L2 … • Notice how it ends up copying the top level list (a b) Append does not modify its arguments. It makes structure of its first argument > (eq? (cdr (cdr L3) L2) Append2 copies the top level of its copies of all of the lists save the last. #f first list argument, L1

4 List access functions List-ref and list-tail Defining Scheme’s list-ref & list-tail > (define L '(a b c d)) > (list-tail L 0) (define (mylist-ref l n) • To find the element at a given position in a list > (list-ref L 2) (a b c d) (cond ((< n 0) (error...)) use the function list-ref (nth in CL) c > (list-tail L 2) ((not (pair? l)) (error...)) > (list-ref ‘(a b c) 0) > (list-ref L 0) (c d) ((= n 0) (car l)) a a > (list-tail L 4) (#t (mylist-ref (cdr l) (- n 1))))) • To find the nth cdr, use list-tail (nthcdr in CL) > (list-ref L -1) () (define (mylist-tail l n) list-ref: expects type (list-tail L 5) > (list-tail ‘(a b c) 2) exact integer> as 2nd arg, given: -1; (cond ((< n 0) (error...)) other arguments were: (a b c d) list-tail: index 5 too large for list: (a b (c) c d) ((not (pair? l)) (error...)) > (list-ref L 4) • Both functions are zero indexed ((= n 0) l) list-ref: index 4 too large for list: (a b c d) (#t (mylist-tail (cdr l) (- n 1)))))

Accessing lists Member Recall: defining member • Scheme’s last returns the last element in a list • Member returns true, but instead of simply (define (member X L) > (define (last l) returning t, its returns the part of the list (if (null? (cdr l)) beginning with the object it was looking for. (cond ((null? L) #f) (car l) > (member ‘b ‘(a b c)) ((equal? X (car L)) L) (last (cdr l)))) (b c) (#t (member X (cdr L))))) (last ‘(a b c)) • member compares objects using equal? c • Note: in CL, last returns the last cons cell (aka pair) • There are versions that use eq? and eqv? • We also have: first, second, third, and CxR, where x is And that take an arbitrary function a string of up to four as or ds. –E.g., cadr, caddr, cddr, cdadr, …

5 Memf Dotted pairs and lists Dotted pairs and lists • If we want to find an element satisfying an • Lists built by calling list are known as proper lists; • A pair that isn’t a proper list arbitrary predicate we use the function they always end with a pointer to null is called a dotted pair memf: A proper list is either the empty list, or a pair whose cdr Remember that a dotted pair a b > (memf odd? ‘(2 3 4)) is a proper list isn’t really a list at all, It’s a just (a . b) (3 4) • Pairs aren’t just for building lists, if you need a structure with two fields, you can use a pair a two part data structure • Which could be defined like: • Use car to get the 1st field and cdr for the 2nd • Doted pairs and lists that end with a dotted pair (define (memf f l) > (define the_pair (cons ‘a ‘b)) are not used very often (cond ((null? l) #f) (a . b) • Because this pair is not a proper list, it’s displayed • If you produce one for 331 code, you’ve probably ((f (car l)) l) in dot notation made an error (#t (memf f (cdr l))))) In dot notation the car and cdr of each pair are shown separated by a period

Conclusion

• Simple linked lists were the only data structure in early Lisps – From them you can build most other data structures though efficiency may be low • Its still the most used data structure in Lisp and Scheme – Simple, elegant, less is more • Recursion is the natural way to process lists

6