Lecture 13 Slides - Linked Lists

1 of 34

Lecture 13

Recursive Data Structures

2 of 34

Logistics and Week Ahead

For some - Ethics Module 5 - Identity

  • Monday - Recursive Data Structures
    • Tuesday - Ex 4 Due (Ex 5 Posted) - Late Penalty Waiver open till Monday 11:59pm
  • Wednesday - Trees! (Pre-Recorded + MQ10) + Tutorial 6
  • Friday - Binary Search Trees (MQ 11)

Reminders:

  • Quiz 1 Grades Available - Median was 89!
  • If you're missing class you should spend time to catch-up.
  • Take 10 seconds each week to check your grades on Canvas.
  • I do not receive notifications for Canvas comments on assignments.
  • New policy: if you have trouble submitting to Canvas, you should submit to me as an email attachment (NOT as a link). I will use the timestamp of when the email arrives as its submission time.

3 of 34

A quick aside: quoting lists

  • Double-quotes let you type a string as a constant in your code

  • "this is a test string"
    • Means a string
    • Whose characters are t,h,i,s, space, i, s, etc.
  • A single quote lets you type a list as a constant
  • '(1 2 3 4)
    • Means a list
    • Whose elements are: 1,2,3, & 4

  • Note that we don't need a close quote because the parens tell Racket where the list ends

Note: I'm going to use quoting to simplify the following slides, it's never required or necessary to quote.

Warning: this works with CONSTANTS but provides strange behavior with more complex expressions.

4 of 34

A new function: cons

; cons : T (listof T) -> "(listof T)"

(cons element list)

  • Returns a list that
    • Starts with element
    • And follows with all the elements of list
  • Essentially the opposite of rest
    • Rest removes an element from the beginning
    • Cons adds one to the beginning
  • While we often use rest to destroy lists one element at a time, we can use cons to build lists one element at a time!

> (cons 1 (list 2 3))'(1 2 3)

> (cons 1� (cons 2

(list 3 4 5)))

'(1 2 3 4 5)

> (cons 1� (cons 2� (cons 3 '())))'(1 2 3)

5 of 34

Implementing map using cons

(define (my-map f a-list)

(if (empty? a-list)

'() ; remember '() means the empty list

(cons (f (first a-list))

(my-map f (rest a-list)))))

(check-expect (my-map - '(1 2 3 4 5))

(map - '(1 2 3 4 5)))

  • Recursion time! Each call
    • Computes one element
    • Recurses to compute the rest of the problem
    • Joins them using cons

6 of 34

Running it - abbreviated substitution model

(my-map - '(1 2 3))

  • (if (empty? '(1 2 3)) ...)
  • (cons (- (first '(1 2 3)))

(my-map -

(rest '(1 2 3))))

  • (cons (- 1)

(my-map -

(rest '(1 2 3))))

  • (cons -1

(my-map -

(rest '(1 2 3))))

  • (cons -1

(my-map -

'(2 3)))

  • (cons -1

(if (empty? '(2 3)) ...))

  • (cons -1

(cons (- (first '(2 3)))

(my-map -

(rest '(2 3)))))

  • (cons -1

(cons (- 2)

(my-map -

(rest '(2 3)))))

  • (cons -1

(cons -2

(my-map -

(rest '(2 3)))))

  • (cons -1

(cons -2

(my-map - '(3))))

  • (cons -1

(cons -2

(if (empty? '(3)) ...)))

7 of 34

Running it - abbreviated substitution model

  • (cons -1

(cons -2

(cons (- (first '(3)))

(my-map

-

(rest '(3))))))

  • (cons -1

(cons -2

(cons (- 3)

(my-map -

(rest '(3))))))

  • (cons -1

(cons -2

(cons -3

(my-map -

'()))))

  • (cons -1

(cons -2

(cons -3

(if (empty? '())

'()

...))))

  • (cons -1

(cons -2

(cons -3 '())))

  • (cons -1

(cons -2

'(-3)))

  • (cons -1

'(-2 -3))

  • '(-1 -2 -3)

8 of 34

Wait, isn’t all that copying of lists inefficient?

9 of 34

No: cons doesn’t copy anything

10 of 34

Idea: linking instead of copying

  • Instead of recopying the list
  • cons can just return a “list” containing
    • The one element it’s adding
    • And a note saying "the rest of the list is over here"
  • When rest sees one of these lists
    • It just returns the "rest of the list over here" part
    • So again, nothing is copied

11 of 34

Lists and web pages

  • When you read a long news article on the web, it’s usually more than one page
    • Each page has a link to the next page
  • When you give someone a link to the article
    • You’re really giving them a link to the first page

Page 1

Page 2

Page 3

Page 4

Link to article

12 of 34

Lists and web pages

  • Cons is like something that makes a new page that links to an old page
  • We can give someone a link to a longer article by giving them the link to the new page
  • But we can also still give them the original list of pages by giving them the old link

Page 1

Page 2

Page 3

Page 4

Cons’ed

page

Link to old article

Link to extended article

13 of 34

Growing a list without copying

  • When you use cons, it doesn’t recopy the list
  • It returns a new pair object
    • Holds just the new element
    • Along with a link to the list it’s extending
  • So the old list object is a part of the new list

> (cons 1 '(2 3))

'(1 2 3)

first

rest

1

'(2 3)

14 of 34

But wait…

'(1 2 3 4) is the same as (cons 1 (cons 2 (cons 3 (cons 4 '()))))

  • If we use cons to build a list
    • Then there are no “real” lists
    • Just additions to lists
    • Or rather, additions to additions to additions to additions to the list
    • And the empty list at the end
  • That’s okay; it works!
  • This is an example of an inductively defined data structure. It itself is recursive!

15 of 34

Minimalism

  • Scheme and Racket are so minimalist, they don't even have lists as a native data type
  • They’re built out of a simpler data structure the cons cell or a pair

16 of 34

Pairs

(define-struct pair(first rest))

; remember those are attributes

; NOT our fave functions!

  • Pairs are essentially structs
  • Two fields
    • One holds a list element
    • The other holds the link to the rest of the list.

first

rest

1

'(2 3)

17 of 34

Pairs

  • Cons cells are essentially two-element struct objects called pairs
    • They just name their constructors and accessors differently than the normal Racket defaults
  • You could implement them yourself if they weren’t already built in to the Intermediate Student language

(define-struct pair(first rest))

; note those are property names

; NOT the functions!

(define (cons first rest)� (make-pair first rest))

(define (first list)� (pair-first list))

(define (rest list)� (pair-rest list))

18 of 34

Racket lists are built out of pairs

  • In Racket, lists are really just big chains of pairs
  • The list is represented by the first pair in the chain
    • Holds the first element of the list
    • And the next pair
    • Which represents rest of the list
  • Cons really just makes a new pair that extends the list
  • The end of the list is marked by having the rest be the special empty-list object, called '().

> (define foo (list 1 2 3))

> (first foo)

1

> (rest foo)

'(2 3)

>

19 of 34

A list

'(1 2 3 4)

20 of 34

first

rest

Is really a pair

1

'(2 3 4)

21 of 34

first

rest

I mean a pair chained with another pair

1

first

rest

2

'(3 4)

22 of 34

first

rest

Make that three pairs

1

first

rest

2

first

rest

3

'(4)

23 of 34

first

rest

well, really four…

1

first

rest

2

first

rest

3

first

rest

4

'()

24 of 34

Building lists out of pairs

This means that when you “pass a list” to a function

  • You're really just passing the first pair to the function
  • And that pair links to the rest of the list

(define foo

(cons 1

(cons 2

(cons 3 '()))))

1

2

3

'()

foo

25 of 34

Growing lists using cons

  • This structure of building lists from pairs is call a linked list
  • Linked lists can be grown easily by adding new pairs
  • Different lists can share pairs
    • That’s how we’re able to implement cons and rest without copying data
  • This is called structure sharing: different lists can share pairs at the end

> (cons 0 foo)

'(0 1 2 3)

1

2

3

'()

foo

0

cons returns this

26 of 34

Lists are recursive data structures

A list is always either:

    • The empty list: '()
    • A pair of an element and a list: (cons something list)

  • By this definition, the following are all lists
    • '() by point 1
    • (cons 1 '()) = '(1), by point 2
    • (cons 2 (cons 1 '()) = '(2 1), also by point 2
    • (cons 3 (cons 2 (cons 1 '())) = '(3 2 1), also by point 2

  • This is called an inductive or recursive definition

27 of 34

Lists are recursive data structures

  • This gives us an easy way of writing recursions on lists
    • Check if it’s the empty list or a pair
    • Write the solution for the empty list
    • Write the solution for pairs
      • This will often involve recursing
  • Doesn’t always work, but it’s a nice starting point

(define (func list)

(if (empty? list)

answer-for-empty-list

answer-for-pair))

28 of 34

Takeaways

  • cons'ing
    • You can use cons to add a new element to the beginning of a list
    • This means we can build lists as part of recursive algorithms! (demos)

Advanced Ideas

  • Racket builds lists out of pairs
    • Which means cons and rest are efficient (no copying necessary)
  • Data structures are built as networks of linked objects
  • Structure sharing: two “different” lists can link to the same pairs
  • Linked lists: Racket builds lists out of linked pairs
    • And so cons and rest are efficient

29 of 34

Cookbook for Building Lists Recursively

  • Most recursive functions that build lists look something like this

(define (func args …)�(if simple-case? '()� (cons …figure out a new element to add…� (func …args to build the rest of the list…))))

  • Notice that the growing of the list happens after the recursive call

30 of 34

Building a list recursively

(define (func args …)�(if simple-case? '()� (cons …new element…� (func …args…))))

  • Growing of the list happens after the recursive call

(define (my-map f list)�(if (empty? list)� '()� (cons (f (first list))� (map f (rest list)))))

31 of 34

What about iterative recursion when building these?

32 of 34

Building a list iteratively

  • If we want to do iterative recursion we need to modify our template a bit

(define (func args … accumulator)�(if simple-case?accumulator� (func …args for rest of list… (cons … new element to add…accumulator))))

  • Growing of the list happens before the recursive call
    • Specifically, in the arguments for the recursive call
    • And the result of the recursive call is returned without further processing
    • And so the recursive call doesn't need to remember everything (new stack frame)
    • And so it's iterative

33 of 34

An iterative version of map

(define (loop func in out)

(if (empty? in)out� (loop func

(rest in)� (cons (func (first in))

out)))

(define (map func list)

(loop func list '())))

  • To make map iterative, we need to add an extra argument to it (the out parameter), that accumulates the results of the loop
  • The easiest way to do that is to make a helper function for map and leave map's arguments alone

(define (func args … accumulator)�(if simple-case?accumulator� (func …args… (cons …new element…accumulator))))

34 of 34

Sometimes iterative recursion is a bad idea

  • Remember that
    • Normal recursion grows the list after recursing
    • Iterative recursion grows the list before recursing - BIG LIST BIG PROBLEMS in some languages
  • So they grow the lists in opposite orders