Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Format directive ~{ counting iterations

128 views
Skip to first unread message

proton

unread,
Jul 20, 2012, 4:42:58 AM7/20/12
to
Hi all,

I want to display a list with an iteration format directive which also displays the iteration count. Is this possible?

This is what I have:

(setq table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))

I want to display it like this:

1. Name: John, age: 13, color: blue.
2. Name: Mary, age: 16, color: green.
3. Name: Charles, age: 10, color: yellow.

So I could use (format t "~{~X. Name: ~a, age: ~a, color: ~a.~%~}" table)

The ~X directive is what I am looking for: to display the iteration number. Of course, I could use a loop, but I was wondering about the possibility of doing it with format.

Thanks a lot for any suggestions.

Pascal J. Bourguignon

unread,
Jul 20, 2012, 9:44:58 AM7/20/12
to
(format t "~{~A. Name: ~a, age: ~a, color: ~a.~%~}"
(mapcar (let ((i -1)) (lambda (x) (cons (incf i) x)))
table))

Inconvenient: you need to cons a new list.


Another solution:

(defvar *i* -1)

(defun cl-user::i (stream argument colon at &rest parameters)
"~W,I/i/ prints (incf *i* i) over W positions."
(declare (ignore argument colon at))
(destructuring-bind (&optional (width 0) (increment 1) &rest ignored) parameters
(declare (ignore ignored))
(format stream "~VD" width (incf *i* increment))))

(let ((table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
(*i* 0))
(format t "~:{~3/i/~:*. Name: ~a, age: ~a, color: ~a.~%~}" table))

1. Name: john, age: 13, color: blue.
2. Name: mary, age: 16, color: green.
3. Name: charles, age: 10, color: yellow.

(let ((table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
(*i* 0))
(format t "~:{~3,10/i/~:*. Name: ~:(~a~), age: ~a, color: ~a.~%~}"
table))
10. Name: John, age: 13, color: blue.
20. Name: Mary, age: 16, color: green.
30. Name: Charles, age: 10, color: yellow.

Notice in this solution we need to use ~:* to push back the argument
that is necessarily eaten by ~/i/. Also, the formating function must
either be in the CL-USER package, or you must qualify it:
~/my-package:i/.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

proton

unread,
Jul 20, 2012, 12:27:19 PM7/20/12
to
On Friday, July 20, 2012 3:44:58 PM UTC+2, pjb wrote:
> proton writes:
>
> > Hi all,
> >
> > I want to display a list with an iteration format directive which also
> > displays the iteration count. Is this possible?
> >
> > This is what I have:
> >
> > (setq table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
> >
> > I want to display it like this:
> >
> > 1. Name: John, age: 13, color: blue.
> > 2. Name: Mary, age: 16, color: green.
> > 3. Name: Charles, age: 10, color: yellow.
> >
> > So I could use (format t "~{~X. Name: ~a, age: ~a, color: ~a.~%~}"
> > table)
> >
> > The ~X directive is what I am looking for: to display the iteration
> > number. Of course, I could use a loop, but I was wondering about the
> > possibility of doing it with format.
>
> (format t "~{~A. Name: ~a, age: ~a, color: ~a.~%~}"
> (mapcar (let ((i -1)) (lambda (x) (cons (incf i) x)))
> table))
>
> Inconvenient: you need to cons a new list.
>
>
> Another solution:
>
> (defvar *i* -1)
>
> (defun cl-user::i (stream argument colon at &rest parameters)
> "~W,I/i/ prints (incf *i* i) over W positions."
> (declare (ignore argument colon at))
> (destructuring-bind (&optional (width 0) (increment 1) &rest ignored) parameters
> (declare (ignore ignored))
> (format stream "~VD" width (incf *i* increment))))
>
> (let ((table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
> (*i* 0))
> (format t "~:{~3/i/~:*. Name: ~a, age: ~a, color: ~a.~%~}" table))
>
> 1. Name: john, age: 13, color: blue.
> 2. Name: mary, age: 16, color: green.
> 3. Name: charles, age: 10, color: yellow.
>
> (let ((table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
> (*i* 0))
> (format t "~:{~3,10/i/~:*. Name: ~:(~a~), age: ~a, color: ~a.~%~}"
> table))
> 10. Name: John, age: 13, color: blue.
> 20. Name: Mary, age: 16, color: green.
> 30. Name: Charles, age: 10, color: yellow.
>
> Notice in this solution we need to use ~:* to push back the argument
> that is necessarily eaten by ~/i/. Also, the formating function must
> either be in the CL-USER package, or you must qualify it:
> ~/my-package:i/.
>
>
> --
> __Pascal Bourguignon__ http://www.informatimago.com/
> A bad day in () is better than a good day in {}.

Thank you Pascal! That's a really neat solution with the ~/ directive.
To solve the package problem for the variable *i*, do you think it would be a good idea to define it as let over lambda?:

(let ((*i* -1))
(defun i (stream argument colon at &rest parameters)
... ))

Cheers.


WJ

unread,
Jul 20, 2012, 12:38:44 PM7/20/12
to
proton wrote:

> I want to display a list with an iteration format directive which also displays the iteration count. Is this possible?
>
> This is what I have:
>
> (setq table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
>
> I want to display it like this:
>
> 1. Name: John, age: 13, color: blue.
> 2. Name: Mary, age: 16, color: green.
> 3. Name: Charles, age: 10, color: yellow.

Racket:

(define table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))

(for ([(x i) (in-indexed table)])
(apply printf "~a. Name: ~a, age: ~a, color: ~a\n" (add1 i) x))

==>

1. Name: John, age: 13, color: blue
2. Name: Mary, age: 16, color: green

Pascal J. Bourguignon

unread,
Jul 20, 2012, 1:17:00 PM7/20/12
to
proton <leosa...@gmail.com> writes:

> Thank you Pascal! That's a really neat solution with the ~/ directive.
> To solve the package problem for the variable *i*, do you think it would be a good idea to define it as let over lambda?:
>
> (let ((*i* -1))
> (defun i (stream argument colon at &amp;rest parameters)
> ... ))

No, the problem is not with *I*, but with I. Read clhs for ~/. It
interns the symbol in CL-USER by default if it is not qualified.

As for *I*, you could use a closure to hide it, but you would need a
function in the closure to reset it. Perhaps you could use colon or at
to reset it:

(let ((i 0))
(defun cl-user::i (stream argument colon at &rest parameters)
"
~:@/i/ sets the counter to the value of the argument - 1 (must be a number).
~C@/i/ sets the counter to C (default 0, so that the next ~/i/ prints 1).
~W,I/i/ prints (incf C I) over W positions (default for W = 0, default for I = 1).
"
(declare (ignore argument colon at))
(if at
(if colon
(setf i argument)
(destructuring-bind (&optional (counter 0) &rest ignored) parameters
(declare (ignore ignored))
(setf i counter)))
(destructuring-bind (&optional (width 0) (increment 1) &rest ignored) parameters
(declare (ignore ignored))
(format stream "~VD" width (incf i increment))))))


(format nil "~10@/i/~:*~{~%~3/i/~:*. ~A~}~%" '(one two three))
"
11. one
12. two
13. three
"

(format nil "~:@/i/~{~%~3,2/i/~:*. ~A~}~%" 42 '(one two three))
"
44. one
46. two
48. three
"

(format nil "~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~{~%~3,1/i/~:*. ~A~}~%"
'(one two three)
'(un deux trois))
"
1. one
2. two
3. three
4. un
5. deux
6. trois
"

(format nil "~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~%"
'(one two three)
'(un deux trois))
"
1. one
2. two
3. three
1. un
2. deux
3. trois
"

The advantage of ~0@/i/~:* is that it let you write complex format
control strings, where you may reset the counter several times, or not.
The inconvenient is that it may not be as clear as (let ((cl-user:*i*
0)) (format …)). But indeed, if we start writing format functions, we
may as well go 100% that way.

proton

unread,
Jul 20, 2012, 5:02:02 PM7/20/12
to
On Friday, July 20, 2012 7:17:00 PM UTC+2, pjb wrote:
> proton writes:
>
> &gt; Thank you Pascal! That&#39;s a really neat solution with the ~/ directive.
> &gt; To solve the package problem for the variable *i*, do you think it would be a good idea to define it as let over lambda?:
> &gt;
> &gt; (let ((*i* -1))
> &gt; (defun i (stream argument colon at &amp;amp;rest parameters)
> &gt; ... ))
>
> No, the problem is not with *I*, but with I. Read clhs for ~/. It
> interns the symbol in CL-USER by default if it is not qualified.
>
> As for *I*, you could use a closure to hide it, but you would need a
> function in the closure to reset it. Perhaps you could use colon or at
> to reset it:
>
> (let ((i 0))
> (defun cl-user::i (stream argument colon at &amp;rest parameters)
> &quot;
> ~:@/i/ sets the counter to the value of the argument - 1 (must be a number).
> ~C@/i/ sets the counter to C (default 0, so that the next ~/i/ prints 1).
> ~W,I/i/ prints (incf C I) over W positions (default for W = 0, default for I = 1).
> &quot;
> (declare (ignore argument colon at))
> (if at
> (if colon
> (setf i argument)
> (destructuring-bind (&amp;optional (counter 0) &amp;rest ignored) parameters
> (declare (ignore ignored))
> (setf i counter)))
> (destructuring-bind (&amp;optional (width 0) (increment 1) &amp;rest ignored) parameters
> (declare (ignore ignored))
> (format stream &quot;~VD&quot; width (incf i increment))))))
>
>
> (format nil &quot;~10@/i/~:*~{~%~3/i/~:*. ~A~}~%&quot; &#39;(one two three))
> &quot;
> 11. one
> 12. two
> 13. three
> &quot;
>
> (format nil &quot;~:@/i/~{~%~3,2/i/~:*. ~A~}~%&quot; 42 &#39;(one two three))
> &quot;
> 44. one
> 46. two
> 48. three
> &quot;
>
> (format nil &quot;~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~{~%~3,1/i/~:*. ~A~}~%&quot;
> &#39;(one two three)
> &#39;(un deux trois))
> &quot;
> 1. one
> 2. two
> 3. three
> 4. un
> 5. deux
> 6. trois
> &quot;
>
> (format nil &quot;~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~0@/i/~:*~{~%~3,1/i/~:*. ~A~}~%&quot;
> &#39;(one two three)
> &#39;(un deux trois))
> &quot;
> 1. one
> 2. two
> 3. three
> 1. un
> 2. deux
> 3. trois
> &quot;
>
> The advantage of ~0@/i/~:* is that it let you write complex format
> control strings, where you may reset the counter several times, or not.
> The inconvenient is that it may not be as clear as (let ((cl-user:*i*
> 0)) (format …)). But indeed, if we start writing format functions, we
> may as well go 100% that way.
>
> --
> __Pascal Bourguignon__ http://www.informatimago.com/
> A bad day in () is better than a good day in {}.

Thanks a lot again for your long answer, I really appreciate it. I almost got a stack overflow in my head (I still consider myself a newbie), but I've learned a lot and you've given me quite a few nice suggestions.
Thanks also to WJ for his reply. From the practical point of view that's a very logical way to go.

Manlio Perillo

unread,
Jul 26, 2012, 4:52:56 PM7/26/12
to
Il Fri, 20 Jul 2012 15:44:58 +0200, Pascal J. Bourguignon ha scritto:

> proton <leosa...@gmail.com> writes:
>
>> Hi all,
>>
>> I want to display a list with an iteration format directive which also
>> displays the iteration count. Is this possible?
>>
>> This is what I have:
>>
>> (setq table '((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
>>
>> I want to display it like this:
>>
>> 1. Name: John, age: 13, color: blue.
>> 2. Name: Mary, age: 16, color: green. 3. Name: Charles, age: 10, color:
>> yellow.
>>
>> So I could use (format t "~{~X. Name: ~a, age: ~a, color: ~a.~%~}"
>> table)
>>
>> The ~X directive is what I am looking for: to display the iteration
>> number. Of course, I could use a loop, but I was wondering about the
>> possibility of doing it with format.
>
> (format t "~{~A. Name: ~a, age: ~a, color: ~a.~%~}"
> (mapcar (let ((i -1)) (lambda (x) (cons (incf i) x)))
> table))
>

In Python there is a nice enumerate function:

>>> list(enumerate([1, 2, 3, 4], start=1))
[(1, 1), (2, 2), (3, 3), (4, 4)]

It returns (and takes) a generator, but in Common Lisp it should not be a
problem to define an enumerate function to work with sequences.

> [...]


Regards Manlio

Marco Antoniotti

unread,
Jul 27, 2012, 7:38:05 AM7/27/12
to
On Thursday, July 26, 2012 11:52:56 PM UTC+3, Manlio Perillo wrote:
> Il Fri, 20 Jul 2012 15:44:58 +0200, Pascal J. Bourguignon ha scritto:
>
> &gt; proton &lt;leosa...@gmail.com&gt; writes:
> &gt;
> &gt;&gt; Hi all,
> &gt;&gt;
> &gt;&gt; I want to display a list with an iteration format directive which also
> &gt;&gt; displays the iteration count. Is this possible?
> &gt;&gt;
> &gt;&gt; This is what I have:
> &gt;&gt;
> &gt;&gt; (setq table &#39;((John 13 blue) (Mary 16 green) (Charles 10 yellow)))
> &gt;&gt;
> &gt;&gt; I want to display it like this:
> &gt;&gt;
> &gt;&gt; 1. Name: John, age: 13, color: blue.
> &gt;&gt; 2. Name: Mary, age: 16, color: green. 3. Name: Charles, age: 10, color:
> &gt;&gt; yellow.
> &gt;&gt;
> &gt;&gt; So I could use (format t &quot;~{~X. Name: ~a, age: ~a, color: ~a.~%~}&quot;
> &gt;&gt; table)
> &gt;&gt;
> &gt;&gt; The ~X directive is what I am looking for: to display the iteration
> &gt;&gt; number. Of course, I could use a loop, but I was wondering about the
> &gt;&gt; possibility of doing it with format.
> &gt;
> &gt; (format t &quot;~{~A. Name: ~a, age: ~a, color: ~a.~%~}&quot;
> &gt; (mapcar (let ((i -1)) (lambda (x) (cons (incf i) x)))
> &gt; table))
> &gt;
>
> In Python there is a nice enumerate function:
>
> &gt;&gt;&gt; list(enumerate([1, 2, 3, 4], start=1))
> [(1, 1), (2, 2), (3, 3), (4, 4)]
>
> It returns (and takes) a generator, but in Common Lisp it should not be a
> problem to define an enumerate function to work with sequences.
>
> &gt; [...]

Of course it is not difficult. It has been done a long time ago and it is a Quicklisp away. Look for CL-ENUMERATIONS :)

Cheers
--
MA

Mark Tarver

unread,
Aug 2, 2012, 7:13:47 AM8/2/12
to
In Shen

(define tabulate
  L -> (tabh L 1))

(define tabh
  [] _ -> []
  [[Name Age Colour] | L] N -> (do (output "~A. ~A, ~A, ~A.~%" N Name
Age Colour)
                                   (tabh L (+ N 1))))

The Qi solution is the same; if you type the above program into Qi at
compiler speed 3 (max speed, minimum readability) you get this CL
output which if you substitute 'FORMAT T' for 'output' '(ERROR
"tabh")' for '(qi::f_error 'tabh)', runs under virgin CL.

(DEFUN tabulate (V1) (tabh V1 1))

(DEFUN tabh (V4 V5)
 (BLOCK NIL
  (IF (NULL V4) (RETURN NIL)
   (TAGBODY
    (IF (CONSP V4)
     (LET ((Car13 (CAR V4)))
      (IF (CONSP Car13)
       (LET ((Cdr12 (CDR Car13)))
        (IF (CONSP Cdr12)
         (LET ((Cdr11 (CDR Cdr12)))
          (IF (CONSP Cdr11)
           (IF (NULL (CDR Cdr11))
            (RETURN
             (PROGN
              (output "~A. ~A, ~A, ~A.~%" V5 (CAR Car13) (CAR Cdr12)
               (CAR Cdr11))
              (tabh (CDR V4) (1+ V5))))
            (GO tag6))
           (GO tag6)))
         (GO tag6)))
       (GO tag6))))
    tag6 (RETURN (qi::f_error 'tabh))))))

Mark
0 new messages