I'm fairly new to Clojure and looking to improve my use of the proper idioms, and make my code more readable. Here's the problem:
Read from standard input and produce the count of each word to standard output, sorted by word, with a single space between the word and the count. Sort the output by word. Words are A-Z only, for simplicity, and case does not matter (lowercase everything).
My first pass is:
(let [counts (apply merge-with + (map #(frequencies (re-seq #"[a-z']+" (.toLowerCase %))) (line-seq (java.io.BufferedReader. *in*))))] (doseq [[word count] (into (sorted-map) counts)] (println word count)))
It gives the correct output:
$ echo 'One one two five > eight ONE FIVE > > four OnE two ' > test $ java -cp ~/apps/clojure/clojure-1.7.0.jar clojure.main word_count.clj < test eight 1 five 2 four 1 one 4 two 2
but I have concerns about the code.
- Is it too dense? Or is it idiomatic enough? I'm not trying to code golf; this is just how I thought of the problem (I have experience in ML and Lisp.)
- I believe the code stays properly within the realm of lazy sequences as much as possible, but would appreciate a Clojure expert to verify or point out where I might be prematurely realizing a lazy sequence, which would make the script unsuitable for large files.
(into (sorted-map) counts)the proper idiom for iterating through a map sorted by key?
- I find
(.toLowerCase s)easier than Clojure's native
lower-casebecause the latter is from a different namespace? Am I anti-Clojure to think that way? Is it part of Clojure culture to prefer the Clojure function to the Java method or does it not matter?
- Would it be preferable to rewrite the mapped function as
#(->> % .toLowerCase (re-seq #"[a-z']+") frequencies)instead of nesting the calls as I did so? On the one hand,
->>results in fewer parens, but on the other hand might that be a little harder to read? Or is the difference between