cl-user> (custom-reverse '(1 2 3 4 5))
((((5 4) 3) 2) 1)
Yes, there's something wrong.
(defun custom-reverse (list-to-reverse)
(if (equal (cdr list-to-reverse) nil)
(car list-to-reverse)
(cons (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))))
The problem you have here is that you don't know exactly how lists are
represented in Lisp.
There's no list data type in lisp; see
http://www.informatimago.com/articles/usenet.html#Lisp-Paradoxes
Lists are represented by chains of cons cells.
Proper lists are either the symbol CL:NIL representing the empty list,
CL:NIL can be read or printed as () too; or a CONS cell where the CAR
contains a first element, and the CDR contains a proper list that is the
rest of the list.
Notice the types: proper list --> ( element . rest-of-the-list )
The elements can be of any type, but the rest-of-the-list must be of
type proper list! So it must be either CL:NIL (empty list = end of the
list), or another cons cell with an element and another proper list,
rest of the list.
The type signature of cons, when used to build a proper list would be:
(cons t proper-list) --> proper-list
Now you have to be careful because CONS cells can be used to build other
kind of data structures. For example, binary trees:
(cons binary-tree binary-tree) --> binary-tree
Dotted lists:
(cons t dotted-list) --> dotted-list
Circular lists:
(cons t circular-list) --> circular-list
and more.
But we are considering here only proper lists.
nil --> proper-list
(cons t proper-list) --> proper-list
Ok, now so what's the problem with your function:
(defun custom-reverse (list-to-reverse)
(if (equal (cdr list-to-reverse) nil)
(car list-to-reverse)
(cons (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))))
The type signature of your function should be:
(custom-reverse proper-list) --> proper-list
You give it a proper list, and it returns a proper list with the
elements in reverse order.
But let's see what it does.
If list-to-reverse is the empty list CL:NIL, also printed/read as (),
then the test is true and (car list-to-reverse) returns () which is a
proper list (right type) and the right result. The reverse of the empty
list is the empty list.
If list-to-reverse is a list containing one element, eg. (a) then the
test is true too, and (car list-to-reverse) returns the first element of
that list, ie. the symbol A. This is not a proper list. Here you have
a first bug.
cl-user> (custom-reverse '(a))
a
Let's correct it:
(defun custom-reverse (list-to-reverse)
(if (null (cdr list-to-reverse))
list-to-reverse
(cons (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))))
cl-user> (custom-reverse '(a))
(a)
cl-user> (custom-reverse '())
nil ; = ()
cl-user> (custom-reverse '(1 2 3 4 5))
(((((5) 4) 3) 2) 1)
We still have the main bug, but now the result is more consistent.
Let's consider the other case, when the list-to-reverse contains more
than 1 element.
(cons (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))
is called. custom-reverse returns a reversed proper list. (car list-to-reverse)
returns the first element. So that expression is something like:
(cons reversed/proper-list
(cons first/element nil))
(cons first/element nil) conses an element to a proper-list therefore it
returns a proper-list all right.
But while the type analysis of:
(cons reversed/proper-list head/proper-list)
doesn't reveal anything wrong: it takes an element which can be of any
type, reversed/proper-list is good, and a proper-list, and therefore
return a proper-list, you may still notice that the element we're
putting in the list is not one element from the original list, but a
reversed rest of the list. So here is the problem.
You have (b) and (a) and you want to get (b a).
(cons '(b) '(a)) --> ((b) a)
(cons '(e d c b) '(a)) --> ((e d c b) a)
so you can use cons directly to get (b a) or (e d c b a).
What you want indeed is to append the two lists:
(append proper-list proper-list) --> proper-list
(append (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))
(defun custom-reverse (list-to-reverse)
(if (null (cdr list-to-reverse))
list-to-reverse
(append (custom-reverse (cdr list-to-reverse))
(cons (car list-to-reverse) nil))))
cl-user> (mapcar 'custom-reverse '(() (1) (1 2) (1 2 3) (1 2 3 4 5 6 7)))
(nil (1) (2 1) (3 2 1) (7 6 5 4 3 2 1))
Now using APPEND in reverse is a bad thing, since APPEND is O(n), and
CUSTOM-REVERSE iterates on LIST-TO-REVERSE too, therefore it is O(n²).
Furthermore, APPEND copies all its arguments (but the last), so it will
also cons O(n) cells, and therefore CUSTOM-REVERSE will also cons O(n²)
cells, when the result only has n cells.
You could use NCONC, which is the non-consing version of APPEND, it
modifies all its arguments to concatenate them. So now you'd cons only
O(n) cells and being recursive, it also use O(n) stack space (so O(n)
space in total), but it is still be O(n²) in time complexity.
One thing with the above solution is that it uses only one variable:
list-to-reverse. If you try to imagine another algorithm to reverse a
list in O(n) time and O(n) space, it could be helpful to use more than
one variable. (This could be an additionnal parameter to a recursive
function, or this could be a local variable to an iterative function).
For more solutions have a look at:
http://www.informatimago.com/develop/lisp/l99/index.html
http://www.informatimago.com/develop/lisp/l99/p05.lisp
--
__Pascal Bourguignon__
http://www.informatimago.com/
A bad day in () is better than a good day in {}.