[comp.lang.lisp] Adding items to the end of a list.

plogan@mentor.com (Patrick Logan) (09/05/90)

I see a lot of LISP code building lists backwards and then reversing
them. I don't know if I've ever seen any C code doing that. Is it too
easy to write sloppy code in LISP? I don't think it is any more
difficult to write efficient list manipulations in Scheme than it is
in C.

Here is a procedure (in Scheme) that copies a list by adding items to
the end of a list and then returns that list. This isn't taught in the
Little LISPer, but it's not beyond the typical C programmer's
abilities.

(define (COPY-LIST lis)
  (let ((header (cons #f '()))) ; Ignore the #f, build the list in the cdr.
    (let loop ((tail header) (lis lis))
      (if (null? lis)
	  (cdr header)
	  (begin (set-cdr! tail (cons (car lis) '()))
		 (loop (cdr tail) (cdr lis)))))))

Here is an "object" that responds to two messages. For the cost of
building a closure of a few words in size, it abstracts the code for
adding items to the end of a list.

Messages:
((object 'EXTEND!) item) ....... Add item to the end of the list.
(object 'LIST) ................. Return the list.

Scheme->C -- 23feb90jfb -- Copyright 1989 Digital Equipment Corporation
> (define (MAKE-TAIL-LIST)
    (let* ((head (cons #f '())) ; Ignore the #f, use for tail-cons'ing.
           (tail head))
      (lambda (msg)
        (case msg
          ((extend!)
           (lambda (item)
             (let ((new-tail (cons item '())))
               (set-cdr! tail new-tail)
               (set! tail new-tail))))
          ((list) (cdr head))))))
MAKE-TAIL-LIST
> (define a (make-tail-list))
A
> (a 'list)
()
> ((a 'extend!) 1)
(1)
> ((a 'extend!) 2)
(2)
> ((a 'extend!) 3)
(3)
> (a 'list)
(1 2 3)
-- 
Patrick Logan, uunet!mntgfx!plogan
Mentor Graphics Corp. 8500 SW Creekside, Beaverton, Oregon 97005-7191

plogan@mentor.com (Patrick Logan) (09/05/90)

    I don't know exactly what the point of your message was. Was it meant to be a
    friendly recommendation to non-expert lisp programmers?

Yes. LISP has a reputation of being inefficient, this is historically
true but not so true of many current implementations. What is true
today is that many programmers, many who have used LISP extensively,
do very inefficient things in LISP. These are things the same
programmers would probably never do in C.

    Or what is a question of the sort `what do people do when they want to add
    to the end of a list'?

No, but that's a reasonable misinterpretation. Comments are welcome.

    ...functions like TCONC...without putting in your loop the code for keeping 
    track of the tail.

This is essentially what the "object" does in the second example in my
original message. It maintains the head and the tail abstractly, which
IMO is even better than using TCONC (which keeps the head in the car
and the tail in the cdr of a pair, in Franz?).
-- 
Patrick Logan, uunet!mntgfx!plogan
Mentor Graphics Corp. 8500 SW Creekside, Beaverton, Oregon 97005-7191

krulwich@ils.nwu.edu (Bruce Krulwich) (09/06/90)

plogan@mentor (Patrick Logan) writes:
>I see a lot of LISP code building lists backwards and then reversing
>them. I don't know if I've ever seen any C code doing that. Is it too
>easy to write sloppy code in LISP? 

It's more complicated than that.  In and of itself it's more efficient to
construct a list backwards, because a good compiler can use register
operations instead of memory operations (in T under Orbit, for example, this
makes a big difference).  In the cases when you don't have to do the
reversal (say, order doesn't matter, or you can keep it reversed for a
while) it's better to do it backwards.  Obviously, the reversal cost
probably outweighs the gains in register op's, so in general you're right if
you have to do the reversal, but it's not as simple as you say.


>Here is a procedure (in Scheme) that copies a list by adding items to
>the end of a list and then returns that list.
>
>(define (COPY-LIST lis)
>  (let ((header (cons #f '()))) ; Ignore the #f, build the list in the cdr.
>    (let loop ((tail header) (lis lis))
>      (if (null? lis)
>         (cdr header)
>         (begin (set-cdr! tail (cons (car lis) '()))
>                (loop (cdr tail) (cdr lis)))))))

You can make this a bit better (getting rid of the wasted HEADER CONS cell)
by special casing the (NULL? lis) case and then starting off header with the
copy of the first element:

(define (COPY-LIST lis)
  (if (null? lis)
      nil
    (let ((header (list (car lis))))
      (let loop ((tail header) (lis (cdr lis)))
        (if (null? lis)
            header
            (begin (set-cdr! tail (cons (car lis) '()))
                   (loop (cdr tail) (cdr lis))))))))


Bruce Krulwich
Institute for the Learning Sciences

 

briscoe-duke@cs.yale.edu (Duke Briscoe) (09/06/90)

In article <1990Sep5.011910.1177@mentor.com> Patrick Logan writes:
>I see a lot of LISP code building lists backwards and then reversing
>them. I don't know if I've ever seen any C code doing that. Is it too
>easy to write sloppy code in LISP? I don't think it is any more
>difficult to write efficient list manipulations in Scheme than it is
>in C.
>

I did some timings on compiled T code, and the difference between
copying a list using your copy-list function and using a function
(define (dcl lis) (reverse! (reverse lis))) was only 20%, the
important thing being that this is an algorithmic difference of a
small constant factor.  The reverse functions in T are coded in T, not
hand-coded assembly by the way.  If the operations used to build the
elements of the list are expensive (just copying the elements is the
least expensive thing I can think of) then the reverse! step is going
to make relatively less of a percentage difference.  I think in many
cases there isn't going to be much difference.  If someone is using
reverse instead of reverse! then that could be more serious because of
the extra consing.  I think the important thing is that the standard
functions be coded efficiently, but for most user applications a
person doesn't need to get involved with potentially tricky pointer
manipulations.

Duke

iam@Gang-of-Four.Stanford.EDU (Ian Mason) (09/06/90)

i am not sure i understand the point of the posting. 
it is just as easy to write a tail recursive version
of copy-list that conses onto the front, and the
trick of course generalizes to  the usual list
recursion. since i am not fluent in scheme here it is
in vanila lisp

(defun copylist (l)  
  (if (null l) 
      l 
      (let ((w (cons (car l) (cdr l))))(loop (cdr l) w w))))

(defun loop (x y z) 
   (if (null x) z (let ((w (cons (car x) (cdr x))))
                    (progn (rplacd y w)(loop (cdr x) w z)))))

a correctness proof can be found in "science of computer
programming 10 1988 177-210"


		-iam-		

jeff@aiai.ed.ac.uk (Jeff Dalton) (09/06/90)

In article <1990Sep5.011910.1177@mentor.com> plogan@mentor.com (Patrick Logan) writes:
>I see a lot of LISP code building lists backwards and then reversing
>them. I don't know if I've ever seen any C code doing that. Is it too
>easy to write sloppy code in LISP? I don't think it is any more
>difficult to write efficient list manipulations in Scheme than it is
>in C.

How soon they forget.  What do you think `tconc' (Interlisp,
Franz Lisp, etc.) was for?

For a more modern approach, look at Appendix B, "Generators and
Gatherers" in Guy Steele Jr.'s CLtL II.  (It's similar to your
object-oriented technique.)

-- Jeff

jjacobs@well.sf.ca.us (Jeffrey Jacobs) (09/07/90)

> tconc

Thanks!  I was about to post that, coupled with a righteous flame
about just how low the LISP community has sunk!

Jeffrey M. Jacobs

ConsArt Systems Inc, Technology & Management Consulting

P.O. Box 3016, Manhattan Beach, CA 90266

voice: (213)376-3802, E-Mail: 76702.456@COMPUSERVE.COM

lgm@cbnewsc.att.com (lawrence.g.mayka) (09/07/90)

In article <3372@skye.ed.ac.uk>, jeff@aiai.ed.ac.uk (Jeff Dalton) writes:
> In article <1990Sep5.011910.1177@mentor.com> plogan@mentor.com (Patrick Logan) writes:
> >I see a lot of LISP code building lists backwards and then reversing
> >them. I don't know if I've ever seen any C code doing that. Is it too
> >easy to write sloppy code in LISP? I don't think it is any more
> >difficult to write efficient list manipulations in Scheme than it is
> >in C.
> 
> How soon they forget.  What do you think `tconc' (Interlisp,
> Franz Lisp, etc.) was for?
> 
> For a more modern approach, look at Appendix B, "Generators and
> Gatherers" in Guy Steele Jr.'s CLtL II.  (It's similar to your
> object-oriented technique.)

The "Series" of Appendix A are often useful here too, as well as old
standbys like MAPCAR.  Otherwise, I use the COLLECT keyword of the new
expanded LOOP macro.


	Lawrence G. Mayka
	AT&T Bell Laboratories
	lgm@iexist.att.com

Standard disclaimer.

eliot@phoenix.Princeton.EDU (Eliot Handelman) (09/12/90)

In article <1990Sep5.155100.1264@mentor.com> plogan@mentor.com (Patrick Logan) writes:
;This is essentially what the "object" does in the second example in my
;original message. It maintains the head and the tail abstractly, which
;IMO is even better than using TCONC (which keeps the head in the car
;and the tail in the cdr of a pair, in Franz?).

No, it kept the whole list in the car and the last cons in the cdr.