I am busy with Structure and Interpretation of Computer Programs exercise 2.18. Here we have to define a procedure reverse to reverse a list. It should do the following:
(reverse (list 1 4 9 16 25))
;; => (25 16 9 4 1)
I came up with the following definition:
(define (reverse list)
(if (null? list)
list
(cons (reverse (cdr list)) (car list))))
;; => (mcons (mcons (mcons (mcons (mcons '() 25) 16) 9) 4) 1).
Then in a solution In found something similar as follows:
(define (reverse items)
(if (null? (cdr items))
items
(append (reverse (cdr items))
(cons (car items) nil))))
;; => (mcons 25 (mcons 16 (mcons 9 (mcons 4 (mcons 1 '()))))).
There's a difference between append
and cons
here where I cannot put my finger on.
My question: what is the difference and why are the results not displayed as (25 16 9 4 1)
?
Short answer: the first version of reverse
is incorrectly building an improper list, the second version is inefficiently building a proper list. We can do better as long as we understand the difference between append
and cons
.
append
concatenates two lists. If we use it for just adding an element at the end of one list, we'll be doing a lot more of work than needed: we'll have to traverse the whole list each time just to put one last element (see: Schlemiel the Painter's algorithm). Hence a reverse
implementation that uses append
can be as bad as O(n^2)
in complexity.
On the other hand, cons
adds a single element to the head of a list, for an O(n)
complexity in the implementation of reverse
. In general, in Scheme you should try to avoid using append
to build a new output list, always prefer cons
. Now let's see what's being returned by your algorithm using cons
:
(reverse '(1 2 3 4 5))
=> '(((((() . 5) . 4) . 3) . 2) . 1)
Why is that? because to build a proper list with cons
the second argument must be another proper list, but you're passing a single element. A correct implementation requires an accumulator parameter - and incidentally, this is more efficient, because it uses tail recursion, a concept that you should already be familiar with, as it was introduced in section 1.2 of the book. Try this:
(define (reverse lst acc)
(if (null? lst)
acc
(reverse (cdr lst) (cons (car lst) acc))))
(reverse '(1 2 3 4 5) '())
=> '(5 4 3 2 1)
For the last part of the question: the list is being displayed with mcons
(the m
means that the cons
cells are mutable) because of the language you're using, try switching to #lang racket
, which uses immutable cons
cells by default, and will get printed as you expect.