5

I am having some troubles regarding the lisp format function. I have the following list:

((X X X)(X X X X X X)(X X X X X X X X X)) 

and I need to print it in the following format:

X  X  X
XX XX XX
XXXXXXXXX

Any thoughts on how to achieve this? The format function is kinda confusing and the HyperSpec documentation doesn't seem to do anything for me. Thanks.

6
  • 2
    It's not clear from your question exactly what your input and output formats are. Why are the elements in the first line spaced out more than the others? Are the input elements literal 'X symbols? etc. Commented Nov 10, 2011 at 18:36
  • Is this a hard-coded list structure? i.e. The X symbols can change, but you'll have three lists (or use destructuring bind to acheive the same)? If that's the case, are all X symbols going to be one character in width? Commented Nov 10, 2011 at 19:16
  • The number of lists within the main list is variable, and always has 3 more elements than the previous one. Commented Nov 10, 2011 at 20:55
  • @pirezas: so you don't need any formatting, spaces, etc., but just to print each next row at new line? Commented Nov 10, 2011 at 21:20
  • I need the spaces, for instance, at every row I need to print something like: X + Space*(NumRows-CurrentRowNumber) Commented Nov 10, 2011 at 21:33

2 Answers 2

3

Like every tool format has its limitations and it's not suited for such problems very well. Probably the best you can get with plain format without resorting to black magic tricks with ~? or ~/, that you or anyone else probably won't understand in the future, is this code:

CL-USER> (format t "~{~{~A ~}~%~}"
                 '((X X X) (X X X X X X) (X X X X X X X X X)))
X X X 
X X X X X X 
X X X X X X X X X 

If you want to get your sophisticated output structure, try to do some pre-processing. Like, if the format of the list is hard-coded, you can use this:

(format t "~{~{~6A~} ~%~}"
          (mapcar (lambda (l)
                    (loop :for i :from 0 :to (1- (length l)) :by (/ (length l) 3)
                          :collect (format nil "~{~A ~}"
                                           (subseq l i (+ i (/ (length l) 3))))))
                  '((X X X) (X X X X X X) (X X X X X X X X X))))

Here we first collect the items of a list into same number of groups for each list, print them and this way get 3 lists with the same number of elements, which can then be processed by format.

You can find out more about format in the appropriate chapter of Peter Seibel's excelent Lisp book: http://gigamonkeys.com/book/a-few-format-recipes.html

EDIT

If you have a variable number of lists, with each one being twice bigger than the previous one, you'll also need to prepare the format string beforehand:

CL-USER> (defun format-custom-list (list)
           (format t (format nil "~~{~~{~~~DA~~} ~~%~~}" (* 2 (length list)))
                   (mapcar (lambda (l)
                             (let* ((len (length l))
                                    (len/3 (/ len 3)))
                               (loop :for i :from 0 :to (1- len) :by len/3 
                                     :collect (format nil "~{~A ~}"
                                                      (subseq l i (+ i len/3))))))
                           list)))
CL-USER> (format-custom-list '((X X X) (X X X X X X) (X X X X X X X X X)
                               (X X X X X X X X X X X X)))
X       X       X        
X X     X X     X X      
X X X   X X X   X X X    
X X X X X X X X X X X X  
NIL

(The trailing nil is the output of format, which isn't printed to the output stream t. If you want to get a string out of this function use nil as format's output stream.)

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks a lot this is almost what I need the only thing is that it's printing a NIL at the end, any thoughts on that?
I believe you try this code in REPL. NIL here is not the part of printing, but just returned value, so don't worry about it.
Thanks it works now. But regarding the NIL, even though it's the returned value, can't I make it go away? Or evaluate the list or something...
@pirezas: any form (function, macro, variable, expression) in Lisp always returns some value. You can't avoid printing returned value in REPL. However, you can change (format t ...) to (format nil ...) and thus return string instead of printing string and returning nil.
2

I'm assuming you want to print each list, inserting spaces to make elements fit max list length.

Though I believe it is possible to print this with nearly single format call, it is better to split printing into several functions:

(defun format-list (stream lst space-count)
  (let ((spaces (make-string 5 :initial-element #\Space))) ;; create string of spaces to insert
    (let ((fmt (concatenate 'string "~{~a" spaces "~}~%")) ;; create formatting string
      (format stream fmt lst)))))

(defvar full-list '((X X X)(X X X X X X)(X X X X X X X X X)))
(defvar max-list-length (max (mapcar length full-list)))  ;; find length 
(mapcar 
  #'(lambda (lst) (format-list t lst (/ (- max-list-length (length lst)) (length lst)))) 
  full-list)

UPD.

For X + Space * (NumRows - CurrentRowNumber) condition you can next function instead of 2 last lines in my original code (in functional style, you can also use loop instead of reduce to make it less functional and more CL-like):

(format-list-of-lists (lst)
  (let ((num-rows (length lst))) 
    (reduce #(lambda (cur-row sub-list) (format-list t sub-list (- num-rows cur-row)) (1+ cur-row)) 
            lst)))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.