Lists in Lisp and Scheme • Lists are Lisp’s fundamental data structures, Lists in Lisp but there are others – Arrays, characters, strings, etc. – has moved on from being and Scheme merely a LISt Processor • However, to understand Lisp and Scheme you must understand lists – common funcons on them a – how to build other useful data structures with them

Lisp Lists In the beginning was the (or pair) • What cons really does is combines two objects into a • Lists in Lisp and its descendants are very two-part object called a cons in Lisp and a pair in Scheme simple linked lists • Conceptually, a cons is a pair of pointers -- the first is – Represented as a linear chain of nodes the car, and the second is the cdr • Each node has a (pointer to) a value (car of • provide a convenient representaon for pairs list) and a pointer to the next node (cdr of list) of any type • The two halves of a cons can point to any kind of – Last node ‘s cdr pointer is to null object, including conses • Lists are immutable in Scheme • This is the mechanism for building lists • Typical access paern is to traverse the list • (pair? ‘(1 2) ) => #t from its head processing each node a null

1 Pairs Box and pointer notaon a Common notation: use diagonal line in • Lists in Lisp and Scheme are defined as (a) cdr part of a cons cell pairs for a pointer to null a a null • Any non empty list can be considered as a pair of the first element and the rest of A one element list (a) the list (a b c) • We use one half of a cons cell to point to

the first element of the list, and the other a b c to point to the rest of the list (which is either another cons or nil) A list of three elements (a b c)

Z What sort of list is this? Pair?

• The funcon pair? returns true if its argument is a cons cell a d • The equivalent funcon in CL is consp • So list? could be defined: (define (list? x) (or (null? x) (pair? x))) b c • Since everything that is not a pair is an 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 (ii) a list of two elements, (a (b c) d) (define (atom? x) (not (pair? x))) b & c and (iii) the atom d. > (car (cdr z)) ??

2 Equality Equal? • Each me you call  (define L1 (cons 'a null)) • Do two lists have the same elements? cons, Scheme  L1 allocates a new (A) • Scheme provides a predicate equal? that is like cons cell from  (define L2 (cons 'a null))) Java’s equal method memory with room  L2 • Eq? returns true iff its arguments are the same for two pointers  (A)  (eq? L1 L2) object, and • If we call cons twice  #f • Equal?, more or less, returns true if its with the same args,  (equal? L1 L2) arguments would print the same. we get two values  #t that look the same,  (and (eq? (car L1)(car L2)) > (equal? L1 L2) but are disnct (eq? (cdr L1)(cdr L2))) #t objects  #t • Note: (eq? x y) implies (equal? x y)

Equal? Use trace to see how it works > (require racket/trace) >(myequal? (c) (c)) (define (myequal? x y) > (trace myequal?) > (myequal? c c) ; this is ~ how equal? could be defined > (myequal? '(a b c) '(a b c)) < #t (cond ((number? x) (= x y)) >(myequal? (a b c) (a b c)) >(myequal? () ()) > (myequal? a a) <#t ((not (pair? x)) (eq? x y)) < #t #t ((not (pair? y)) #f) >(myequal? (b c) (b c)) ((myequal (car x) (car y)) > (myequal? b b) < #t (myequal? (cdr x) (cdr y))) (#t #f))) • Trace is a debugging package showing what args a user- defined function is called with and what it returns • The require function loads the package if needed

3 Does Lisp have pointers? Variables point to their values • A secret to understanding Lisp is to realize that > (define x ‘(a b)) variables have values in the same way that lists environment > x VAR VALUE have elements (a b) … a b • As pairs have pointers to their elements, > (define y x) x variables have pointers to their values y • Scheme maintains a data structure (a b) … represenng the mapping of variables to their y current values. …

Variables point to their values Does Scheme have pointers?

> (define x ‘(a b)) environment • The locaon in memory associated with the > x VAR VALUE variable x does not contain the list itself, but a a b (a b) … pointer to it. > (define y x) x • When we assign the same value to y, Scheme y copies the pointer, not the list. … 1 2 (a b) • Therefore, what would the value of > (set! y ‘(1 2) y > (eq? x y) > y … (1 2) be, #t or #f?

4 Length is a simple funcon on Lists Building Lists

• The built-in funcon length takes a list and • list-copy takes a list and returns a copy of it returns the number of its top-level elements • The new list has the same elements, but • Here’s how we could implement it contained in new pairs > (set! x ‘(a b c)) (define (length L) (a b c) (if (null? L) 0 (+ 1 (length (cdr L)))) > (set! y (list-copy x)) • As typical in dynamically typed languages (a b c) (e.g., Python), we do minimal type checking • Spend a few minutes to draw a box diagram – The underlying interpreter does it for us of x and y to show where the pointers point – Get run-me error if we apply length to a non-list

Copy-list Append • append returns the >(append ‘(a b) ‘(c d)) • List-copy is a Lisp built-in (as copy-list) that concatenaon of (a b c d) any number of lists could be defined in Scheme as: > (append ‘((a)(b)) ‘(((c)))) • Append copies its (define (list-copy s) arguments except ((a) (b) ((c))) (if (pair? s) the last > (append ‘(a b) ‘(c d) ‘(e)) (cons (list-copy (car s)) – If not, it would have (a b c d e) >(append ‘(a b) ‘()) (list-copy (cdr s))) to modify the lists – Such side effects (a b) s)) are undesirable in >(append ‘(a b)) • Given a non-atomic s-expression, it makes and funconal (a b) returns a complete copy (e.g., not just the top- languages >(append) level spine) ()

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

Visualizing Append List access funcons > (load "append2.ss") environment > (define L1 '(1 2)) • To find the element at a given posion in a list VAR VALUE > (define L2 '(a b)) use the funcon list-ref (nth in CL) … a b > (define L3 > (list-ref ‘(a b c) 0) (append2 L1 L2)) L2 > L3 a (1 2 a b) L1 • To find the nth cdr, use list-tail (nthcdr in CL) > L1 L3 1 2 (1 2) > (list-tail ‘(a b c) 2) > L2 … (c) (a b) • Both funcons are zero indexed > (eq? (cdr (cdr L3) L2) Append2 copies the top level of its #f first list argument, L1

6 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) > (list-ref L 2) (a b c d) (cond ((< n 0) (error...)) c > (list-tail L 2) ((not (pair? l)) (error...)) > (list-ref L 0) (c d) ((= n 0) (car l)) a > (list-tail L 4) (#t (mylist-ref (cdr l) (- n 1))))) > (list-ref L -1) () (define (mylist-tail l n) list-ref: expects type (list-tail L 5) 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 d) ((not (pair? l)) (error...)) > (list-ref L 4) ((= n 0) (cdr l)) list-ref: index 4 too large for list: (a b c d) (#t (mylist-ref (cdr l) (- n 1)))))

Accessing lists Member • Scheme’s last returns the last element in a list • Member returns true, but instead of simply > (define (last l) returning t, its returns the part of the list (if (null? (cdr l)) beginning with the object it was looking for. (car l) > (member ‘b ‘(a b c)) (last (cdr l)))) (b c) (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 funcon a string of up to four as or ds. – E.g., cadr, caddr, cddr, cdadr, …

7 Defining member Memf • If we want to find an element sasfying an (define (member X L) arbitrary predicate we use the funcon (cond ((null? L) #f) memf: > (memf odd? ‘(2 3 4)) ((equal? X (car L)) L) (3 4) (#t (member X (cdr L))))) • Which could be defined like: (define (memf f l) (cond ((null? l) #f) ((f (car l)) l) (#t (memf f (cdr l)))))

Doed pairs and lists Doed pairs and lists • Lists built by calling list are known as proper lists; • A pair that isn’t a proper list they always end with a pointer to null is called a doed pair A proper list is either the empty list, or a pair whose cdr is a proper list Remember that a doed pair a b • Pairs aren’t just for building lists, if you need a isn’t really a list at all, It’s a just (a . b) structure with two fields, you can use a pair a two part data structure • Use car to get the 1st field and cdr for the 2nd • Doted pairs and lists that end with a doed pair > (define the_pair (cons ‘a ‘b)) are not used very oen (a . b) • • Because this pair is not a proper list, it’s displayed If you produce one for 331 code, you’ve probably in dot notaon made an error In dot notaon the car and cdr of each pair are shown separated by a period

8 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 sll the most used data atructure in Lisp and Scheme – Simple, elegant, less is more • Recursion is the natural way to process lists

9