This is a read-only archive!

Printing a nicely formatted plaintext table of data in Clojure

I use this fairly often while data-munging, when I want to quickly view a list of hash-maps of data in a simple table format. Usually this is coming out of database. Maybe someone will find it useful.

(defn table
  "Given a seq of hash-maps, prints a plaintext table of the values of the hash-maps.
  If passed a list of keys, displays only those keys.  Otherwise displays all the
  keys in the first hash-map in the seq."
  ([xs]
     (table xs (keys (first xs))))
  ([xs ks]
     (when (seq xs)
       (let [f (fn [old-widths x]
                 (reduce (fn [new-widths k]
                           (let [length (inc (count (str (k x))))]
                             (if (> length (k new-widths 0))
                               (assoc new-widths k length)
                               new-widths)))
                         old-widths ks))
             widths (reduce f {} (conj xs (zipmap ks ks)))
             total-width (reduce + (vals widths))
             format-string (str "~{"
                                (reduce #(str %1 "~" (%2 widths) "A") "" ks)
                                "~}~%")]
         (cl-format true format-string (map str ks))
         (cl-format true "~{~A~}~%" (repeat total-width \-))
         (doseq [x xs]
           (cl-format true format-string (map x ks)))))))

Then you can do this:

user> (def data [{:name "Brian" :job "Code monkey" :age 29}
                 {:name "Bob" :job "Janitor" :age 97}
                 {:name "Johnny McLongname" :job "None" :age 3}])
#'user/data
user> (table data)
:name             :job        :age 
-----------------------------------
Brian             Code monkey 29   
Bob               Janitor     97   
Johnny McLongname None        3    
nil
user> (table data [:age :name])
:age :name             
-----------------------
29   Brian             
97   Bob               
3    Johnny McLongname 
nil
April 16, 2010 @ 4:49 AM PDT
Cateogory: Programming
Tags: Clojure

2 Comments

Lee
Quoth Lee on April 16, 2010 @ 2:14 PM PDT

This is neat, I'd totally use this if it were part of clojure.contrib.pprint

Lauri
Quoth Lauri on April 16, 2010 @ 8:25 PM PDT

Thanks, this is very nice!

In order to try it in repl I had to import cl-format function from clojure.contrib.pprint: (use '[clojure.contrib.pprint :only (cl-format)])