If you are lost, talk with us [experience suggests that some of you are]

 

TOPIC: list structures

[NOTE: today, some non-O(1) primitives!]

Lists are incredibly useful objects.

- have first, second, etc. elements.

 

Here's the abstraction:

CONSTRUCTOR:

SELECTORS/ACCESSORS:

CONTRACT:

(head (cons x l)) = x

(tail (cons x l)) = l

(null? l) = #t IFF l is the empty-list, which is written '() and prints as ()

>> Yes, that is an unmatched single quote, [which we will see more in recitation]. Lots more coming.

Note that we don't say what (head '()) and (tail '()) are

What a spec doesn't say is very important! [Cf Microsoft, Apple, etc.]


A list in Scheme is built from a set of pairs. (A list is a pair such that the tail is a list! Or the empty list…)

The list (1 2) = (cons 1 (cons 2 ‘())

IMPORTANT FACTS:

There are THREE ways that cons/head/tail are used:

* "Proper" lists

* pairs

* structures combining the two [example in section]

Some examples:

(define a (cons 1 2))

THIS IS NOT A PROPER LIST, because its tail is not a list.

(tail a) is 2

(define b (cons 1 (cons 2 (cons 3 '()))))

THIS IS A PROPER LIST:

() is a proper list

so (cons 3 '()) is

...

so b is, too.

>> Draw box-and-pointer diagram.

IMPORTANT FACT: [useful on prelims!]

* Cons always builds exactly ONE box.

Note that in a box and pointer diagram, a pointer points to an ENTIRE box, not (e.g. the front half). A box like:

[B points to 3 element list]

b will print as (1 2 3)

Note that this looks a whole lot like Scheme expressions.

* It's intentional

* We'll be writing Scheme programs which manipulate Scheme expressions. We'll be doing this in PS#3 and PS#6.

* This is why we teach in a language with uniform syntax like Scheme (and not, e.g., in Java )

 


There are lots of useful operations on lists.

LIST is a procedure which puts its arguments into a proper list

(list + 27 (+ 2 1))

--> ({add} {27} {3})

LIST isn't a special form, so all its arguments are evaluated!

[It is kind of funky, in that it takes any number of arguments.

Note: on exams you will never be asked to write such a function!]

 

We're going to be building Scheme code. What if we want to build the list (+ 27 3)

?

We want to TURN OFF the evaluator temporarily.

This isn't just an issue with programming languages.

Compare:

"Say your name"

with

"Say 'your name'"

We use a form of QUOTATION to tell the interpreter, "don't eval this".

For symbols, 'STUFF

means, "just return something that prints like STUFF as a value"

+ ----> {add}

'+ ---> + <<-- The <symbol> +

x ----> depends

'x ---> x <<-- The <symbol> x

(+ 1 2) ---> 3

'(+ 1 2) ---> a list of three elements, which prints as (+ 1 2)

This helps our immediate problem:

(list '+ 1 2) ---> (+ 1 2)

Note:

(let ((a 3))
   (list '+ a 2))
==>
(+ 3 2)

[list isn't a special form!]

The elements of a list can be ANYTHING:

IMPORTANT FACT: [useful on prelims!]

* LIST called with N arguments builds exactly N boxes

(list '* (list '+ 1 2) 3) looks like this:

>> Draw the boxes

It prints as

(* (+ 1 2) 3)

It's a list of THREE ELEMENTS,

-- Second element is itself a LIST OF THREE ELEMENTS.


Let's do some useful things with lists.

Computing the length (number of elements) is one of the most basic operations on a list. This is a Scheme primitive, but lets see how it can be implemented:

(define length
  (lambda (l)
    (if (null? l)
	0
	(+ 1 (length (tail l))))))

>> What kind of process? Recursive! <<

[Can you write a tail-recursive version?]

[How would you prove it correct? We'll get to that in a moment]

(length '(1 2 3)) == 3

(length '(1 (2 3) 4)) == 3

* Just the number of elements in the top-level list

* count how many tail's there are 'til we get to the empty list.

NOTE:

* This is a non- O(1) primitive! ... What is its order of growth?

CAVEAT EMPTOR!

A Scheme (non-arithmetic) primitive is constant time if either:

 

 

Very often, you want to apply a function to each element of a list and return a new list as a result.

(map f list) applies f to every element of list, returns result

VERY POWERFUL

This is a Scheme primitive, but lets see how to implement it:

(define map
  (lambda (f l)
    (if (null? l)
	'()
	(cons (f (head l))
	      (map f (tail l))))))

* go down the list taking successive tail's

* cons up -- or build -- the resulting list.

 

Like length, map operates element-by-element, not on the primitive elements:

(map length '(1 (2 3) (4 5 6) 7)) ==> (1 2 3 1)

NOTE: map is *also* a non- O(1) primitive. What is its order of

growth? [Trick question…]

NOTE: map in Scheme can walk down several lists simultaneously.

(map + '(1 2 3) '(5 6 7)) ==> (6 8 10)

 

(define copy
  (lambda (l)
    (map (lambda (x) x) l)))

(copy '(1 2 3)) ==> (1 2 3), but it's a different list with different boxes.


eq? is a version of equality that tells if two pairs are the same box.

* It'll be important later on.

* equal? tests "do these print the same way?"

(eq? l l) ---> always true.

(eq? l (copy l)) ---> always false. prints the same, but different list.

(equal? l (copy l)) --> always true.

 


One way to think about lists and recursive list manipulation is using induction and the substitution model.

How could we go about showing that, for all lists l, (copy l) = l?

But it's a different kind of induction.

 

The length of () is 0

The length of (x) is 1

etc.

Proof by induction: (each step has a reason)

INDUCTION ON: n, length of l

STATEMENT: (copy l) = l for any list l of length n

BASE n=0:

Why?? Because (null? ()) ==> #t

INDUCTION:

Assume that for any list l of length n, (copy l) = l

We must show that for any list l' of length n+1, (copy l') = l'.

Using the substitution model, we know that:

(null? l') --> #f because the length of l' is n+1 > 0

The length (tail l') is length l' - 1, which is n

So, (copy (tail l')) = (tail l') *by the induction hypothesis*

[CLEARLY STATED]

Thus

(copy l') = (cons (head l') (copy (tail l')))

= (cons (head l') (tail l')) <<< by above reasoning

= l' <<< contract for cons

 

However, the induction on (length l) was a bit of a kludge.

Recall that induction was supposed to be usable on any inductively defined set:

Nat was defined by {0} and succ(x)=x+1

The set of lists is defined by '() and (cons x l) for any expression x and list l.

The corresponding inductive proofs

- Pick an arbitrary l'

- Assume that the hypothesis holds for l' [Induction hypothesis]

- Show that it holds for (cons s l') for every s

This is called STRUCTURAL INDUCTION:

- Induct on the structure of lists.

 


In proofs, we'll write (cons s l) as s.l

Let's revisit our proof of copy by induction on lists:

INDUCTION ON: l

STATEMENT: (copy l) = l

BASIS: l = ().

(copy ()) = () by the substitution model, because (null? ()) = #t

INDUCTION: Assume (copy l') = l'

Must show (copy s.l') = s.l' for all s.

(null? s.l') = #f by definition, it contains at least s.

(copy s.l') = (cons (head s.l') (copy (tail s.l'))) <<< subst model

= (cons s (copy l')) <<< list contract

= (cons s l') <<< induction hypothesis!

= s.l' <<< definition