1

I've got a lazy sequence that, when sent to println, shows like this:

(((red dog) (purple cat)) ((green mouse) (yellow bird)))

Note that this is the result of reading a csv and trimming all "cell" values, hence (a) the fact that it's lazy at the time I want to print it and (b) in the future innermost lists might be more than 2 strings because more columns are added.

I'm trying to juse clojure.pprint/print-table to print this in a two-column table. I'm having a pretty hard time because print-table seems to want a map with data.

Here's a repro:

;; Mock the lazy data retrieved from a csv file
(defn get-lazy-data []
  (lazy-seq '('('("red" "dog") '("purple" "cat")) '('("green" "mouse") '("yellow" "bird")))))

(defn -main []
  (let [data       (get-lazy-data)]
      (println "Starting...")
      (println data)
      (println "Continuing...")
      (print-table data)
      (println "Finished!")))

This gives an error:

Exception in thread "main" java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.util.Map$Entry

I've tried various options:

  • (print-table (apply hash-map data)) gives same exception
  • (print-table (zipmap data)) tells me to provide another argument for the keys, but I want a solution that doesn't rely on specifying the number of columns beforehand
  • comprehending and adapting the answer to "Clojure printing lazy sequence", which would be a duplicate of my question were it not that both the question and answer seem so much more complex that I don't know how to translate that solution to my own scenario

Basically I know I have an XY-problem but now I want answers to both questions:

  • X: How do I pretty print a lazy sequence of pairs of pairs of strings as a table on the console?
  • Y: How can I convert a lazy sequence to a map (where e.g. keys are the indexes)?
Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • `(apply hash-map (flatten data))` => `{purple cat, yellow bird, green mouse, red dog}` – leetwinski Apr 06 '18 at 13:13
  • But would that still work if the innermost lists have more than 2 items? (I might need to clarify in my question that might happen - my data comes from a CSV with _currently_ 2 columns, that will be more later.) – Jeroen Apr 06 '18 at 13:19
  • the first one was obviously wrong. the following would work for any number of values, indexing them by integers `(print-table (map #(zipmap (range) %) (apply concat data)))` – leetwinski Apr 06 '18 at 13:26

2 Answers2

3

How do I pretty print a lazy sequence of pairs of pairs of strings as a table on the console?

The fact that your "rows" seem to be grouped in pairs is odd, assuming you want a two-column table of color/animal, so we can remove the extra grouping with mapcat identity then zipmap those pairs with the desired map keywords:

(def my-list
  '(((red dog) (purple cat)) ((green mouse) (yellow bird))))
(def de-tupled (mapcat identity my-list))
(map #(zipmap [:color :animal] %) de-tupled)
=> ({:color red, :animal dog} {:color purple, :animal cat} {:color green, :animal mouse} {:color yellow, :animal bird})

(clojure.pprint/print-table *1)
| :color | :animal |
|--------+---------|
|    red |     dog |
| purple |     cat |
|  green |   mouse |
| yellow |    bird |

It's not clear from the question, but it seems like you want to support an arbitrary number of "columns" which kinda precludes having fixed names for them. In that case you can do something like this:

(def my-list ;; added third mood "column"
  '(((red dog happy) (purple cat sad)) ((green mouse happy) (yellow bird sad))))
(def de-tupled (apply concat my-list))
(clojure.pprint/print-table (map #(zipmap (range) %) de-tupled))
|      0 |     1 |     2 |
|--------+-------+-------|
|    red |   dog | happy |
| purple |   cat |   sad |
|  green | mouse | happy |
| yellow |  bird |   sad |

How can I convert a lazy sequence to a map (where e.g. keys are the indexes)?

(def my-list
  '(((red dog) (purple cat)) ((green mouse) (yellow bird))))
(zipmap (range) my-list)
=> {0 ((red dog) (purple cat)), 1 ((green mouse) (yellow bird))}
Taylor Wood
  • 15,886
  • 1
  • 20
  • 37
  • I realize now that I was unclear in my question (because I was apparently unclear to myself on what I wanted), but this answers my question as asked AFAICT and in any case the last bit helped me understand things enough to carry on. Thanks for your time! – Jeroen Apr 06 '18 at 14:01
0

A related point to your problem is how you print out your data. Clojure has two ways to print:

(dotest
  (println ["hello" "there" "everybody"])      ; #1
  (prn     ["hello" "there" "everybody"]))     ; #2

#1  => [hello there everybody]
#2  => ["hello" "there" "everybody"]

For strings the presence of quotes in #2 makes a huge difference in understanding what is happening. The prn function produces output that is machine-readable (like what you type in your source code). You really need that if you have strings involved in your data.

Look at the difference with symbols:

  (println ['hello 'there 'everybody])
  (prn     ['hello 'there 'everybody])

  ; doesn't matter if you quote the whole form or individual symbols    
  (println '[hello there everybody])
  (prn     '[hello there everybody])

all results are the same:

[hello there everybody]
[hello there everybody]
[hello there everybody]
[hello there everybody]

The point is that you need prn to tell the difference between symbols and strings when you print results. Note that the prn output format (with double-quotes) happens automatically if you use pprint:

(def data
  [["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]
   ["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]
   ["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]])

(clojure.pprint/pprint data) =>
  [["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]
   ["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]
   ["here" "we" "have" "a" "lot" "of" "strings" "in" "vectors"]]
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48