On Tuesday, September 25, 2012 5:03:36 PM UTC-4, informatimago wrote:
> "Yves S. Garret" <yoursurrogate
...@gmail.com> writes:
> > Hi, in my quest of becoming a better lisp programmer. I figured that I should try to make a reverse function. This is what I have now:
> > ================================================
> > (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))))
> > ================================================
> > Now, it doesn't work. At least not very well. The problem is what
> > should I return from the method? Also, and I could be wrong, but is
> > it possible to make a reverse method using car, cdr and cons?
> 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 {}.
Wow. Honestly, I haven't understood everything that you said, I'm on chapter 12 of PCL, so learning about lists as I go.
First of all, thank you very much into the effort that you put in writing this post. I appreciate it. I haven't understood everything and I will have to wait (try, fall on my face a couple of times) and learn more about how Lisp handles information. I accepted the fact that I'll go through this n00bish uncomfortable phase if I would like to gain any sort of mastery of Lisp.
Second of all, I love your website. I saw the 99 problems link and that's where I'll go to try stuff out and then see how you did things.
And everybody, thank you for your patience and input as I try to learn, quite possibly, the greatest programming language yet to be invented.