7

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?

4

3 回答 3

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

您可以添加更多参数(它们将用逗号分隔),或者您也可以以某种方式处理冒号和 & 符号。

遵循 Svante 的建议,这里是这个函数的一个稍微改变的版本,它还以下列方式使用冒号和 & 符号:冒号使它在和之间改变prin1princ而 at-sign 使它递归地打印嵌套数组(它还可以更复杂地打印多维数组等......但时间有限,这就是它:

(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))))
于 2013-07-30T20:33:12.853 回答
3
  1. 我会使用coerce而不是loopvectors 转换为lists。
  2. 不会使用format+ coerceon vectors; 我会vector直接迭代。这将产生更易读(和更有效)的代码。
  3. format不支持s的原因vector可能是历史原因。
于 2013-07-30T20:08:50.440 回答
1

您可能正在寻找类似的东西:

(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

但我会听 sds 的回答,因为这肯定不是最有效和最易读的方式,而是直接在向量上迭代。

于 2013-07-31T08:26:03.720 回答