ponipei
ponipei

Reputation: 23

Table printing a list of lists Common lisp

I wish to print this data in a table with the columns aligned. I tried with Format but the columns were not aligned. Does anyone know how to do it ? Thank you.

(("tiscali" 10000 2.31 0.84 -14700.0 "none")
 ("atlantia" 50 22.65 22.68 1.5 "none")
 ("bper-banca" 1000 1.59 2.01 423.0 "none")
 ("alerion-cleanpower" 30 44.14 36.45 -230.7   "none")
 ("tesmec" 10000 0.12 0.14 150.0 "none")
 ("cover-50" 120 8.95 9.6 78.0 "none")
 ("ovs" 1000 1.71 1.93 217.0 "none")
 ("credito-emiliano" 200 5.7 6.26 112.0 "none"))

I tried to align the columns wit the ~T directive, no way. Is there a piece of code that prints nicely table data?

Upvotes: 0

Views: 322

Answers (2)

coredump
coredump

Reputation: 38809

I have something like this in my personal code, that I reproduced here in a simplified way:

(defpackage :tabular (:use :cl))                                                                                                                                                                                                                                                
(in-package :tabular)                                                                                                                                                                                                                                                           
           

I have a function that turns any object into a list of values (a row), here the usage is for a list of values, so it is already in the correct shape.

(defgeneric columnize (object)                                                                                                                                                                                                                                                  
  (:documentation "Representation of object as a list of fields")                                                                                                                                                                                                               
  (:method ((o list)) o))                                                                                                                                                                                                                                                       
                                                                     

I also define a transpose method that works with lists of various sizes:

(defun transpose (lists)                                                                                                                                                                                                                                                        
  (when (notany #'null lists)                                                                                                                                                                                                                                                   
    (cons                                                                                                                                                                                                                                                                       
     (mapcar #'first lists)                                                                                                                                                                                                                                                     
     (transpose (mapcar #'cdr lists)))))                             

Here is your data, as defined by Chris:

(defparameter *data*                                                                                                                                                                                                                                                            
  '(("tiscali" 10000 2.31 0.84 -14700.0 "none")                                                                                                                                                                                                                                 
    ("atlantia" 50 22.65 22.68 1.5 "none")                                                                                                                                                                                                                                      
    ("bper-banca" 1000 1.59 2.01 423.0 "none")                                                                                                                                                                                                                                  
    ("alerion-cleanpower" 30 44.14 36.45 -230.7   "none")                                                                                                                                                                                                                       
    ("tesmec" 10000 0.12 0.14 150.0 "none")                                                                                                                                                                                                                                     
    ("cover-50" 120 8.95 9.6 78.0 "none")                                                                                                                                                                                                                                       
    ("ovs" 1000 1.71 1.93 217.0 "none")                                                                                                                                                                                                                                         
    ("credito-emiliano" 200 5.7 6.26 112.0 "none")))                                                                                                                                                                                                                            
                                   

And finally, a function that prints a list of objects in a tabular way. Basically, I convert all objects to list of values, convert them to string, and compute their size. This gives a matrix of size that I transpose to have a list of sizes for the same column: this is used to compute the width of each column, based on the maximum size of the actual data.

In practice, I allow also the generic function to add indicators like how to justify (left/right), etc.

(defun tabulate (stream objects)                                                                                                                                                                                                                                                
  (loop                                                                                                                                                                                                                                                                         
    for n from 0                                                                                                                                                                                                                                                                
    for o in objects                                                                                                                                                                                                                                                            
    for row = (mapcar #'princ-to-string (columnize o))                                                                                                                                                                                                                          
    collect row into rows                                                                                                                                                                                                                                                       
    collect (mapcar #'length row) into row-widths                                                                                                                                                                                                                               
    finally                                                                                                                                                                                                                                                                     
       (flet ((build-format-arguments (max-width row)                                                                                                                                                                                                                           
                (when (> max-width 0)                                                                                                                                                                                                                                           
                  (list max-width #\space row))))                                                                                                                                                                                                                               
         (loop                                                                                                                                                                                                                                                                  
           with number-width = (ceiling (log n 10))                                                                                                                                                                                                                             
           with col-widths = (transpose row-widths)                                                                                                                                                                                                                             
           with max-col-widths = (mapcar (lambda (s) (reduce #'max s)) col-widths)                                                                                                                                                                                              
           for index from 0                                                                                                                                                                                                                                                     
           for row in rows                                                                                                                                                                                                                                                      
           for entries = (mapcan #'build-format-arguments max-col-widths row)                                                                                                                                                                                                   
           do (format stream                                                                                                                                                                                                                                                    
                     "~v,'0d. ~{~v,,,va~^ ~}~%"                                                                                                                                                                                                                                 
                     number-width index entries)))))                                                                                                                                                                                                                            
                                                

For example:

(fresh-line)                                                                                                                                                                                                                                                                    
(tabulate *standard-output* *data*)                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                

Gives:

0. tiscali            10000 2.31  0.84  -14700.0 none                                                                                                                                                                                                                           
1. atlantia           50    22.65 22.68 1.5      none                                                                                                                                                                                                                           
2. bper-banca         1000  1.59  2.01  423.0    none                                                                                                                                                                                                                           
3. alerion-cleanpower 30    44.14 36.45 -230.7   none                                                                                                                                                                                                                           
4. tesmec             10000 0.12  0.14  150.0    none                                                                                                                                                                                                                           
5. cover-50           120   8.95  9.6   78.0     none                                                                                                                                                                                                                           
6. ovs                1000  1.71  1.93  217.0    none                                                                                                                                                                                                                           
7. credito-emiliano   200   5.7   6.26  112.0    none    

        

As you can see there is some adjustments that could be made to format floating points values so that they align on the dot, but this is already quite useful.

Upvotes: 0

Chris
Chris

Reputation: 36596

Let's break this down.

First, let's give your data a nice name:

(defparameter *data* 
    '(("tiscali" 10000 2.31 0.84 -14700.0 "none")
      ("atlantia" 50 22.65 22.68 1.5 "none")
      ("bper-banca" 1000 1.59 2.01 423.0 "none")
      ("alerion-cleanpower" 30 44.14 36.45 -230.7   "none")
      ("tesmec" 10000 0.12 0.14 150.0 "none")
      ("cover-50" 120 8.95 9.6 78.0 "none")
      ("ovs" 1000 1.71 1.93 217.0 "none")
      ("credito-emiliano" 200 5.7 6.26 112.0 "none")))

Now, come up with a way to print each line using format and destructuring-bind. Widths of various fields are hard-coded in.

(defun print-line (line)
    (destructuring-bind (a b c d e f) line
        (format T "~20a ~5d ~6,2f ~6,2f ~10,2f ~4a~%"  a b c d e f)))

Once you know you can print a line, you just need to do that for each line.

(mapcar 'print-line *data*)

Result:

tiscali              10000   2.31   0.84  -14700.00 none
atlantia                50  22.65  22.68       1.50 none
bper-banca            1000   1.59   2.01     423.00 none
alerion-cleanpower      30  44.14  36.45    -230.70 none
tesmec               10000   0.12   0.14     150.00 none
cover-50               120   8.95   9.60      78.00 none
ovs                   1000   1.71   1.93     217.00 none
credito-emiliano       200   5.70   6.26     112.00 none

Upvotes: 2

Related Questions