I'm learning Clojure and would like some advice on idiomatic usage. As part of a small statistics package, I have a function to calculate the mode of a set of data. (Background: The mode is the most common value in a set of data. There are almost a dozen published algorithms to calculate it. The one used here is from "Fundamentals of Biostatistics" 6th Ed by Bernard Rosner.)
(defn tally-map
" Create a map where the keys are all of the unique elements in the input
sequence and the values represent the number of times those elements
occur. Note that the keys may not be formatted as conventional Clojure
keys, i.e. a colon preceding a symbol."
[aseq]
(apply merge-with + (map (fn [x] {x 1}) aseq)))
(defn mode
" Calculate the mode. Rosner p. 13. The mode is problematic in that it may
not be unique and may not exist at all for a particular group of data.
If there is a single unique mode, it is returned. If there are multiple
modes, they are returned as a list. If there is no mode, that is all
elements are present in equal frequency, nil is returned."
[aseq]
(let [amap (tally-map aseq)
mx (apply max (vals amap))
k (keys amap)
f (fn [x] (not (nil? x)))
modes (filter f (map #(if (= mx (get amap %)) %) k))
]
(cond (= 1 (count modes)) (first modes)
(every? #(= mx %) (vals amap)) nil
:else modes)
)
)
There are a couple of things I have questions about:
- The argument. The function accepts a single sequence. Is it more idiomatic to accept a variable number of arguments like the addition function?
- Code smell. It seems like the "let" is a bit more complicated than it should be -- so many variable assignments. Have I missed any obvious (or not so obvious) uses of the language or library that would make this method more concise?
Thanks in advance for the help.