Subject: Re: Format puzzle
From: rpw3@rpw3.org (Rob Warnock)
Date: Mon, 19 Nov 2007 06:06:08 -0600
Newsgroups: comp.lang.lisp
Message-ID: <8qadnQO6_eyt4dzanZ2dnUVZ_gCdnZ2d@speakeasy.net>
Tobias C. Rittweiler <tcr@freebits.de.invalid> wrote:
+---------------
| Ron Garret <rNOSPAMon@flownet.com> writes:
| > Here's a function that does what the Python string.join function does:
| > (defun join (l &optional (delim ""))
| >   (format nil (format nil "~~{~~A~~#[~~:;~A~~]~~}" delim) l))
| > Is there a way to do this with only a single call to format?
| 
| If you don't mind a consing solution, what about
|   (format nil "~:{~A~A~}" (loop for word in list 
|                                 collect (list word delim)))
+---------------

Your solution appends a spurious final "delim", e.g.:

    > (let ((list '(a b c))
	    (delim #\,))
	(format nil "~:{~A~A~}" (loop for word in list
				      collect (list word delim))))

    "A,B,C,"
    >

But you can fix that with a bit of uglification:

    > (let ((list '(a b c))
	    (delim #\,))
	(format nil "~:{~A~^~@[~A~]~}"
		    (loop for tail on list
		      collect (list (first tail)
				    (when (rest tail) delim)))))

    "A,B,C"
    >

which can be simplified considerably by changing the LOOP iterator
from FOR...IN to FOR...ON, to allow lookahead, and *not* using sublists:

    > (let ((list '(a b c))
	    (delim #\,))
	(format nil "~{~A~^~A~}" (loop for tail on list
				   collect (first tail)
				   when (rest tail)
				     collect delim)))

    "A,B,C"
    >

But if you're going to allow LOOP, then why use FORMAT at all?!?
Since FORMAT probably uses WITH-OUTPUT-TO-STRING "under the hood",
just do so directly:

    > (let ((list '(a b c))
	    (delim #\,))
	(with-output-to-string (s)
	  (loop for tail on list
	    do (princ (first tail) s)
	       (when (rest tail)
		 (princ delim s)))))

    "A,B,C"
    >

Those still awake will note that Joshua Taylor suggested this two days ago
in <news:deaba798-6a05-4ce5-a573-10ed2de2722c@41g2000hsh.googlegroups.com>,
except he used DO & WRITE-STRING and the above uses LOOP & PRINC.


-Rob

p.s. To *really* beat a dead horse, we can merge this with the
"so something different the first time" thread [remember that?
"Subject: Multiple arguments to mapcar?"], switch back to FOR...IN,
rearrange things a bit, and get:

    > (let ((list '(a b c))
	    (delim #\,))
	(with-output-to-string (s)
	  (loop for word in list
		and first = t then nil
	    do (unless first
		 (princ delim s))
	       (princ word s))))

    "A,B,C"
    >

p.p.s. It must be really late here, or I never
could have even *considered* this version:  ;-}  ;-}

    > (let ((list '(a b c))
	    (delim #\,))
	(with-output-to-string (s)
	  (loop for word in list
		and maybe-delim = "" then delim
	    do (princ maybe-delim s)
	       (princ word s))))

    "A,B,C"
    >

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607