Is there a format directive to iterate over vectors in Common Lisp?
Asked Answered
L

3

8

Common Lisp supports an plethora of formatting directives. However, I couldn't find a handy directive for my problem. Basically, I'd like to print a grid of numbers.

Using a list the following works nicely:

(format t "~{~A|~A|~A~%~^-----~%~}" '(1 2 3 4 5 6 7 8 9))

1|2|3
-----
4|5|6
-----
7|8|9
NIL

I was unable find a similar construct to iterate over vectors. CLtL2 states clearly that ~{...~} expects a list as argument. I tried using a vector anyway, but my Clisp rightly exclaimed about the wrong argument type. As a workaround I convert the my vector into a throw-away list using the almighty loop.

(let ((lst (loop for e across '#(1 2 3 4 5 6 7 8 9) collecting e)))
   (format t "~{~A|~A|~A~%~^-----~%~}" lst))

1|2|3
-----
4|5|6
-----
7|8|9
NIL

This works, but it strikes me as a clumsy makeshift solution. I'd rather not create tons of temporary lists only for format. Is there a way to iterate vectors directly?

Out of curiosity, is there a reason why format shouldn't support sequences?

Loy answered 30/7, 2013 at 19:43 Comment(1)
You can always add your own with (format t "~/func-name/" something)Roca
R
7
(defun pprint-array (stream array
                     &optional colon amp (delimiter #\Space))
  (declare (ignore colon amp))
  (loop
     :with first-time = t
     :for x :across array
     :unless first-time :do (format stream "~C" delimiter) :end
     :do (format stream "~S" x)
     (setf first-time nil)))

(format t "~' :@/pprint-array/" #(1 2 3 4)) ; 1 2 3 4

You can add more arguments (they would be separated by comma), or you could also handle somehow colon and ampersand.

Following Svante's advise here's a somewhat altered version of this function, it also utilizes the colon and ampersand in the following way: colon makes it alter between prin1 and princ and at-sign makes it print nested arrays recursively (it could be further sophisticated to also print multidimensional arrays etc... but having limited time here's what it is:

(defun pprint-array (stream array
                     &optional colon amp
                       (delimiter #\Space) (line #\Newline))
  (if amp (loop
             :with first-time = t
             :for a :across array
             :unless first-time
             :do (when line (write-char line stream)) :end
             :if (or (typep a 'array) (typep a 'vector))
             :do (pprint-array stream a colon amp delimiter line)
             :else
             :do (if colon (prin1 a stream) (princ a stream)) :end
             :do (setf first-time nil))
      (loop
         :with first-time = t
         :for x :across array
         :unless first-time
         :do (when delimiter (write-char delimiter stream)) :end
         :do (if colon (prin1 x stream) (princ x stream))
         (setf first-time nil))))
Roca answered 30/7, 2013 at 20:33 Comment(1)
I believe (no hard data, just some experiences here and there) that instead of using a format call with a format string that consists of a single instruction, using write with appropriate parameters is significantly more performant. This would likely matter here, because it happens in a tight loop over each character of a string.Cleocleobulus
P
3
  1. I would use coerce instead of loop to convert vectors to lists.
  2. I would not use format+coerce on vectors; I would iterate on the vector directly. This would produce more readable (and more efficient) code.
  3. The reason for format not to support vectors is probably historical.
Pontonier answered 30/7, 2013 at 20:8 Comment(0)
D
1

You are probably are looking for something like:

(format t "~{~A|~A|~A~%~^-----~%~}" (coerce #(1 2 3 4 5 6 7 8 9)
                                            'list))
1|2|3
-----
4|5|6
-----
7|8|9
NIL

But I would insted listen to sds' answer, since this is certainly not the most efficient and readable way and iterate on the vector directly.

Disrelish answered 31/7, 2013 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.