Newsgroups: comp.lang.lisp
Path: cantaloupe.srv.cs.cmu.edu!bb3.andrew.cmu.edu!news.sei.cmu.edu!cis.ohio-state.edu!math.ohio-state.edu!cs.utexas.edu!swrinde!pipex!uknet!festival!edcogsci!jeff
From: jeff@aiai.ed.ac.uk (Jeff Dalton)
Subject: Re: Format directive for lists wanted.
Message-ID: <CzDoDn.4zn@cogsci.ed.ac.uk>
Sender: usenet@cogsci.ed.ac.uk (C News Software)
Nntp-Posting-Host: cara.aiai.ed.ac.uk
Organization: AIAI, University of Edinburgh, Scotland
References: <ALB.94Nov11195921@ipvaim.unipv.it> <3a47gi$muq@tools.near.net>
Date: Wed, 16 Nov 1994 20:46:35 GMT
Lines: 74

In article <3a47gi$muq@tools.near.net> barmar@nic.near.net (Barry Margolin) writes:
>In article <ALB.94Nov11195921@ipvaim.unipv.it> alb@ipvaim.unipv.it (Alberto Riva) writes:
>>A very simple question: what is the best way to print a list such as:
>>(one two three)
>>in this way:
>>"(one, two, three)" 
>
>You should be able to adapt the example at the top of CLtL2 p. 602 for
>this, resulting in:
>
>"(~(~#[~;~A~;~:;~@{~#[~;~]~A~^, ~}~]~))"

Um, how about

  (format t "(~{~S~^, ~})" '(one two three))

Explanation:

  ~{ ... ~}  iterates over the elts of (one two three)

  ~^   exits the iteration when the number left is zero

Now, here's a question for you all:

Suppose I have something like ((3 days) (2 hours) (5 minutes))
and I want to make a string "3 day, 2 hours, 5 minutes".

No complications like saying "and 5 minutes" for the last
item instead of just "5 minutes".

Now, ~:{ ...  iterates over a list of sublists, and it looks
like ~:^ can be used to exit in a manner analogous to the simpler
case above.  Here's an example in Lucid CL.`:

> (format nil "~:{~A ~A~:^, ~}" '((5 hours) (6 minutes)))
"5 HOURS, 6 MINUTES"

In my interpretation of CLtL II, Lucid Cl is correct, but guess
what?  AKCL gave "5 HOURS", because of a different interpretation
of when to exit.  So did XP format and (yes, I tried this) Franz
Lisp (the MacLisp-like one, not Franz Inc's CL).  

The exact interpretation was evidently (to judge from CLtL II) the
subject of a cleanup, so perhaps it's not surprising that there's
disagreement.

Now the question: if ~:^ can't be relied on, what's the next best
alternative?  What I came up with on the spot is embodied in the
following function:

(defun seconds->description (seconds)
  ;; Returns something like "3 days, 10 minutes".
  (let (days hrs mins secs items)
    (multiple-value-setq (mins secs) (floor seconds 60))
    (multiple-value-setq (hrs mins) (floor mins 60))
    (multiple-value-setq (days hrs) (floor hrs 24))
    (flet ((record (n singular plural)
	     (cond ((= n 1) (push `(1 " " ,singular ", ") items))
		   ((> n 1) (push `(,n " " ,plural ", ") items)))))
      (record days "day" "days")
      (record hrs "hour" "hours")
      (record mins "minute" "minutes")
      (record secs "second" "seconds")
      ;; /\/: You might think we'd do something with ~:{ and ~:^ --
      ;; but there are problems in at least AKCL and the XP format
      ;; that runs in AKCL.
      (format nil "~{~A~#,1^~}" (apply #'append (nreverse items))))))

-- jeff





