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 <ma...@xivilization.net> 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 clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to