Reputation: 11875
So I have lists, looking like this:
((24 . 23) (9 . 6) ... )
and want to custom format the output to something looking like this:
"24/23 9/6 ..."
I tried:
(defun show-pair (ostream pair col-used atsign-used)
(declare (ignore col-used atsign-used))
(format ostream "~d/~d" (first pair) (second pair)))
(let ((x '( 1 . 2))) (format nil "~{~/show-pair/~^~}" (list x)))
as a simple warming up exercise to show a list with only 1 pair. But when trying this in the emacs slime repl, I get the error
The value 2 is not of type LIST [Condition of type TYPE-ERROR]
Which, of course is confusing as ~/show-pair/
was expected to handle one entry in the list, which is is the pair, passing the pair to show-pair
. But it appears, something else is actually happening.
Upvotes: 1
Views: 161
Reputation: 9865
If you want to do it with format
- the problem is to access first and second element of the alist using format
directives. I didn't found how I could access them inside a format
directive.
However, in such regularly formed structures like an alist, one could flatten the list first and then let format
-looping directive consume two elements per looping - then one consumes the pair.
Since the famous :alexandria
library doesn't count as dependency in Common Lisp world, one could directly use alexandria:flatten
:
(defparameter *al* '((24 . 23) (9 . 6)))
(ql:quickload :alexandria) ;; import alexandria library
(format nil "~{~a/~a~^ ~}" (alexandria:flatten *al*))
;; => "24/23 9/6"
nil
return as string~{ ~}
loop over the list~a/~a
the fraction~^
empty space between the elements but not after last elementflatten
by the way without :alexandria
-"dependency" would be in this case:
(defun flatten (l)
(cond ((null l) nil)
((atom l) (list l))
(t (append (flatten (car l)) (flatten (cdr l))))))
Upvotes: 2
Reputation: 145
Using hints from Redefinition of the print-object method for conses..., you could end up with something like this:
CL-USER> (let ((std-function (pprint-dispatch 'cons)))
(unwind-protect
(progn (set-pprint-dispatch
'cons
(lambda (s o) (format s "~d/~d" (car o) (cdr o))))
(format t "~{~a~%~}" '((23 . 24) (5 . 9))))
(set-pprint-dispatch 'cons std-function))
(format t "~{~a~%~}" '((23 . 24) (5 . 9))))
23/24
5/9
(23 . 24)
(5 . 9)
NIL
CL-USER>
Hiding the bookkeeping;
(defmacro with-fractional-conses (&body body)
(let ((std-function (gensym "std-function")))
`(let ((,std-function (pprint-dispatch 'cons)))
(unwind-protect
(progn (set-pprint-dispatch
'cons
(lambda (s o) (format s "~d/~d"
(car o)
(cdr o))))
,@body)
(set-pprint-dispatch 'cons ,std-function)))))
CL-USER> (with-fractional-conses
(format t "~{~a~%~}"
'((23 . 24) (5 . 9))))
23/24
5/9
NIL
CL-USER>
Upvotes: 1
Reputation:
It is fairly seldom a good idea to use ~/.../
in format
in my experience. It's probably much better to simply turn the list you have into the list you need and then process that directly. So, for instance:
> (format t "~&~:{~D/~D~:^, ~}~%"
(mapcar (lambda (p)
(list (car p) (cdr p)))
'((1 . 2) (3 . 4))))
1/2, 3/4
nil
Or if you want to use loop
:
> (format t "~&~:{~D/~D~:^, ~}.~%"
(loop for (n . d) in '((1 . 2) (3 . 4))
collect (list n d)))
1/2, 3/4.
nil
The cost (and storage) associated with converting the list is likely to be absolutely tiny compared with the I/O cost of printing it.
Upvotes: 1
Reputation: 11875
While waiting for feedback, I found out, where the problem is coming from:
So far, I considered (second x)
to behave exactly like (cdr x)
but for tagged values, this assumption is wrong. If I change in show-pairs
above (in the question) accordingly, it all works.
So, it is not a formatting problem at all, nor any surprises with how ~/foo~/
works.
(defun show-pair (ostream pair col-used atsign-used)
(declare (ignore col-used atsign-used))
(format ostream "~d/~d" (first pair) (cdr pair))) ;; second -> cdr fixes the problem
Upvotes: 1