Hi Marek,
Here's my tweaked version:
(ns karma
(:use [clojure.contrib.duck-streams :only (read-lines)])
(:use [clojure.contrib.generic.functor :only (fmap)]))
(def allowed-nickname "[A-z]{1,16}")
(def upvote-regexp (re-pattern (format "(%s)\\+\\+" allowed-
nickname)))
(def downvote-regexp (re-pattern (format "(%s)\\-\\-" allowed-
nickname)))
(defn get-votes [regexp line]
(let [nicks (map second (re-seq regexp line))]
(frequencies nicks)))
(defn get-histogram [line]
(let [upvotes (get-votes upvote-regexp line)
downvotes (fmap - (get-votes downvote-regexp line))]
(merge-with + upvotes downvotes)))
(defn -main [& args]
(let [file-name (ffirst args)
histograms (map get-histogram (read-lines file-name))
histogram (apply merge-with + histograms)
non-zero-histogram (remove (comp zero? second) histogram)
sorted-by-karma (reverse (sort-by second non-zero-histogram))]
(doseq [[nick karma] sorted-by-karma]
(println nick karma))))
(-main *command-line-args*)
The only non-trivial change is in the way the histogram is created.
Instead of "updating" an accumulator map while processing each line, I
create a mini-histogram for each line and then merge them together at
the end.
'get-votes' is a lot like 'extract-nicks', but leverages the built-in
'frequencies' fn to build a histogram of the upvotes/downvotes in a
line, depending on the regexp passed in:
karma=> (get-votes upvote-regexp "c++ d++ b++ c--")
{"c" 1, "d" 1, "b" 1}
karma=> (get-votes downvote-regexp "c++ d++ b++ c--")
{"c" 1}
Given a line, 'get-histogram' uses 'get-votes' to get a histogram of
the upvotes and a histogram of the downvotes. It then uses 'fmap' to
convert the downvotes into negative numbers:
karma=> (fmap - {"c" 1})
{"c" -1}
'get-histogram' then uses 'merge-with' to combine the two histograms
into a histogram for the line:
karma=> (merge-with + {"c" 1, "d" 1, "b" 1} {"c" -1})
{"c" 0, "d" 1, "b" 1}
In '-main', 'merge-with' is used again to combine all the histograms
into a single histogram. After that, the code is essentially
identical to the original, except for a few small tweaks:
- (first (first ...)) can be shortened into (ffirst ...)
- In cases like removing nicks with 0 karma, instead of (filter (not
pred) ...), I prefer (remove pred ...); I find it a little easier to
follow that way.
- To sort the nicks by karma in descending order, instead of sorting
by the negation of the karma, I used (reverse (sort-by ...)); again,
just a subjective thing, makes the intent more clear to me.
- In the final 'doseq', (first item) and (second item) can be replaced
by destructuring.
Hope you find this useful :)
On Dec 21, 6:38 pm, Marek Kubica <[email protected]> wrote:
> Hi,
>
> I wrote a small log file analyzer for IRC logs. We use nickname++ and
> nickname-- to track the "karma", so after trying to write it in
> JavaScript (failed due to to the fact that Gjs/Seed are unmature yet),
> Factor (failed because I am just too stupid to understand it), Guile
> (failed because I ran into encoding/regex-problems), so I gave Clojure
> a try.
>
> I really liked the immutable data stuctures, so I tried to avoid
> mutation.
>
> So uhm, this is my first Clojure program. I tend to learn a lot from
> code reviews and would be happy if someone could take a look at it and
> tell me what to improve and how to write more idiomatic code.
>
> Code is here, and reproduced below:
> <https://github.com/Leonidas-from-XIV/karmawhore/blob/68ff6b681c5862fa...>
>
> Thanks in advance!
>
> regards,
> Marek
>
> (ns net.xivilization.karmawhore
> (:gen-class)
> (:use [clojure.contrib.duck-streams :only (read-lines)]))
>
> (def allowed-nickname "[A-z]{1,16}")
> (def nick-plus (re-pattern (format "(%s)\\+\\+" allowed-nickname)))
> (def nick-minus (re-pattern (format "(%s)\\-\\-" allowed-nickname)))
>
> (defn extract-nicks [regexp line]
> (map second (re-seq regexp line)))
>
> (defn modify-karma [op h nick]
> (let [current-value (h nick)]
> (if (nil? current-value) (assoc h nick (op 1))
> (assoc h nick (op current-value 1)))))
>
> (def increase-karma (partial modify-karma +))
> (def decrease-karma (partial modify-karma -))
>
> (defn process-line [acc line]
> (let [nicks-add (extract-nicks nick-plus line)
> nicks-sub (extract-nicks nick-minus line)
> after-add (reduce increase-karma acc nicks-add)]
> (reduce decrease-karma after-add nicks-sub)))
>
> (defn -main [& args]
> (let [file-name (first (first args))
> histogram (reduce process-line (hash-map) (read-lines
> file-name)) nonzero? (comp not zero? second)
> histogram (filter nonzero? histogram)
> sorted-by-karma (sort-by #(- (second %)) histogram)]
> (doseq [item sorted-by-karma]
> (printf "%s %d\n" (first item) (second item)))))
>
> (-main *command-line-args*)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en