Skip to content

Commit

Permalink
Stop storing times-solved for problems in mongo.
Browse files Browse the repository at this point in the history
Instead, keep it in memory and update it when users
solve a new problem.
  • Loading branch information
amalloy committed Sep 19, 2011
1 parent 57b9382 commit 16ebe34
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 58 deletions.
13 changes: 4 additions & 9 deletions src/foreclojure/graphs.clj
Expand Up @@ -5,8 +5,7 @@
(:import [java.io ByteArrayInputStream (:import [java.io ByteArrayInputStream
ByteArrayOutputStream]) ByteArrayOutputStream])
(:use [compojure.core :only [defroutes GET]] (:use [compojure.core :only [defroutes GET]]
[foreclojure.utils :only [from-mongo]] [foreclojure.problems :only [solved-stats]]
[somnium.congomongo :only [fetch-one]]
[useful.utils :only [with-adjustments]])) [useful.utils :only [with-adjustments]]))


(defn un-group (defn un-group
Expand All @@ -18,13 +17,9 @@
(repeat count x)))) (repeat count x))))


(defn fetch-score-frequencies [problem-id] (defn fetch-score-frequencies [problem-id]
(into {} (-> @solved-stats
(for [[k v] (:scores (get problem-id)
(from-mongo (dissoc nil)))
(fetch-one :problems
:where {:_id problem-id}
:only [:scores])))]
[(Integer/parseInt (name k)), v])))


(defn make-problem-plot [id best curr] (defn make-problem-plot [id best curr]
(let [freqs (fetch-score-frequencies id) (let [freqs (fetch-score-frequencies id)
Expand Down
24 changes: 13 additions & 11 deletions src/foreclojure/mongo.clj
Expand Up @@ -2,7 +2,7 @@
(:use somnium.congomongo (:use somnium.congomongo
[foreclojure.data-set :only [load-problems]] [foreclojure.data-set :only [load-problems]]
[foreclojure.config :only [config]] [foreclojure.config :only [config]]
[foreclojure.problems :only [total-solved get-problem-list]] [foreclojure.problems :only [number-from-mongo-key solved-stats get-problem-list]]
[foreclojure.users :only [get-users]])) [foreclojure.users :only [get-users]]))


(defn connect-to-db [] (defn connect-to-db []
Expand Down Expand Up @@ -43,16 +43,18 @@
(defn reconcile-solved-count (defn reconcile-solved-count
"Overwrites the times-solved field in the problems collection based on data from the users collection. Should only be called on server startup since it isn't a safe operation. Also updates the total-solved agent." "Overwrites the times-solved field in the problems collection based on data from the users collection. Should only be called on server startup since it isn't a safe operation. Also updates the total-solved agent."
[] []
(let [total (->> (get-users) (let [+ (fnil + 0)
(mapcat :solved) [total scores]
(frequencies) (->> (fetch :users :only [:scores])
(reduce (fn [sum [id solved]] (mapcat :scores)
(update! :problems (frequencies)
{:_id id} (reduce (fn [[total scores] [[id score] times]]
{:$set {:times-solved solved}}) [(+ total times)
(+ sum solved)) (update-in scores
0))] [(number-from-mongo-key id) score]
(send total-solved (constantly total)))) + times)])
[0 {}]))]
(send solved-stats (constantly (assoc scores :total total)))))


(defn prepare-mongo [] (defn prepare-mongo []
(connect-to-db) (connect-to-db)
Expand Down
72 changes: 36 additions & 36 deletions src/foreclojure/problems.clj
Expand Up @@ -4,7 +4,7 @@
[clojure.string :as s] [clojure.string :as s]
[ring.util.response :as response]) [ring.util.response :as response])
(:import [org.apache.commons.mail EmailException]) (:import [org.apache.commons.mail EmailException])
(:use [foreclojure.utils :only [from-mongo get-user get-solved login-link *url* flash-msg flash-error def-page row-class approver? can-submit? send-email image-builder with-user]] (:use [foreclojure.utils :only [from-mongo get-user get-solved login-link *url* flash-msg flash-error def-page row-class approver? can-submit? send-email image-builder with-user maybe-update]]
[foreclojure.social :only [tweet-link gist!]] [foreclojure.social :only [tweet-link gist!]]
[foreclojure.feeds :only [create-feed]] [foreclojure.feeds :only [create-feed]]
[foreclojure.users :only [golfer? get-user-id disable-codebox?]] [foreclojure.users :only [golfer? get-user-id disable-codebox?]]
Expand All @@ -20,7 +20,7 @@
[compojure.core :only [defroutes GET POST]] [compojure.core :only [defroutes GET POST]]
[clojure.contrib.json :only [json-str]])) [clojure.contrib.json :only [json-str]]))


(def total-solved (agent 0)) (def solved-stats (agent {:total 0}))


(defn get-problem [x] (defn get-problem [x]
(from-mongo (from-mongo
Expand All @@ -31,7 +31,7 @@
([criteria] ([criteria]
(from-mongo (from-mongo
(fetch :problems (fetch :problems
:only [:_id :title :difficulty :tags :times-solved :user] :only [:_id :title :difficulty :tags :user]
:where criteria :where criteria
:sort {:_id 1})))) :sort {:_id 1}))))


Expand Down Expand Up @@ -80,6 +80,11 @@
[id] [id]
(keyword (str (int id)))) (keyword (str (int id))))


(defn number-from-mongo-key
"Turn a keyword like :4 into an integer"
[k]
(Integer. (name k)))

(defn trim-code [code] (defn trim-code [code]
(when code (.trim code))) (when code (.trim code)))


Expand All @@ -89,44 +94,39 @@
code))) code)))


(defn record-golf-score! [user-id problem-id score] (defn record-golf-score! [user-id problem-id score]
(let [user-score-key (keyword (str "scores." problem-id)) (when-let [{user-id :_id {old-score (keyword problem-id)} :scores :as user}
problem-score-key (keyword (str "scores." score)) (from-mongo
[problem-scores-key user-subkey] (map mongo-key-from-number (fetch-one :users
[score problem-id])] :where {:_id user-id}))]
(when-let [{:keys [_id scores] :as user} (when (golfer? user)
(from-mongo (session/session-put! :golf-chart
(fetch-one :users {:id problem-id
:where {:_id user-id}))] :score score
(let [old-score-real (get scores user-subkey) :best old-score}))
old-score-test (or old-score-real 1e6) (when (or (not old-score)
old-score-key (keyword (str "scores." old-score-real))] (> old-score score))
(when (golfer? user) (update! :users
(session/session-put! :golf-chart {:_id user-id}
{:id problem-id {:$set {(keyword (str "scores." problem-id)) score}})
:score score (send solved-stats (fn [scores]
:best old-score-real})) (let [inc (fnil inc 0),
(when (< score old-score-test) dec (fn [x]
(update! :problems (when (and x (> x 1))
{:_id problem-id, (dec x)))]
old-score-key {:$gt 0}} (maybe-update scores [problem-id]
{:$inc {old-score-key -1}}) #(-> %
(update! :problems (maybe-update [score] inc)
{:_id problem-id} (maybe-update [old-score] dec)))))))))
{:$inc {problem-score-key 1}})
(update! :users
{:_id _id}
{:$set {user-score-key score}}))))))


(defn store-completed-state! [username problem-id code] (defn store-completed-state! [username problem-id code]
(let [{user-id :_id} (fetch-one :users (let [{user-id :_id} (fetch-one :users
:where {:user username} :where {:user username}
:only [:_id]) :only [:_id])
current-time (java.util.Date.)] current-time (java.util.Date.)]
(when (not-any? #{problem-id} (get-solved username)) (when (not-any? #{problem-id} (get-solved username))
(update! :users {:_id user-id} {:$addToSet {:solved problem-id}}) (update! :users {:_id user-id} {:$addToSet {:solved problem-id}
(update! :problems {:_id problem-id} {:$inc {:times-solved 1}}) :$set {:last-solved-date current-time}})
(update! :users {:_id problem-id} {:$set {:last-solved-date current-time}}) (send solved-stats update-in [:total] inc))
(send total-solved inc))
(record-golf-score! user-id problem-id (code-length code)) (record-golf-score! user-id problem-id (code-length code))
(save-solution user-id problem-id code))) (save-solution user-id problem-id code)))


Expand Down Expand Up @@ -381,7 +381,7 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(let [solved (get-solved (session/session-get :user)) (let [solved (get-solved (session/session-get :user))
problems (get-problem-list)] problems (get-problem-list)]
(map-indexed (map-indexed
(fn [x {:keys [title difficulty times-solved tags user], id :_id}] (fn [x {:keys [title difficulty tags user], id :_id}]
[:tr (row-class x) [:tr (row-class x)
[:td.titlelink [:td.titlelink
[:a {:href (str "/problem/" id)} [:a {:href (str "/problem/" id)}
Expand All @@ -391,7 +391,7 @@ Return a map, {:message, :error, :url, :num-tests-passed}."
(s/join " " (map #(str "<span class='tag'>" % "</span>") (s/join " " (map #(str "<span class='tag'>" % "</span>")
tags))] tags))]
[:td.centered user] [:td.centered user]
[:td.centered (int times-solved)] [:td.centered (reduce + (vals (get @solved-stats id)))]
[:td.centered (checkbox-img (contains? solved id))]]) [:td.centered (checkbox-img (contains? solved id))]])
problems))])})) problems))])}))


Expand Down
4 changes: 2 additions & 2 deletions src/foreclojure/static.clj
@@ -1,6 +1,6 @@
(ns foreclojure.static (ns foreclojure.static
(:use [compojure.core :only [defroutes GET]] (:use [compojure.core :only [defroutes GET]]
[foreclojure.problems :only [total-solved]] [foreclojure.problems :only [solved-stats]]
[foreclojure.config :only [repo-url]] [foreclojure.config :only [repo-url]]
[foreclojure.utils :only [def-page]])) [foreclojure.utils :only [def-page]]))


Expand All @@ -17,7 +17,7 @@
:content :content
[:div#welcome [:div#welcome
[:div#totalcount [:div#totalcount
(.format df (deref total-solved)) " problems solved and counting!"] (.format df (:total @solved-stats)) " problems solved and counting!"]
[:div [:div
[:h3 "What is 4Clojure?"] [:h3 "What is 4Clojure?"]
[:p "4Clojure is a resource to help fledgling clojurians learn the language through interactive problems. The first few problems are easy enough that even someone with no prior experience should find the learning curve forgiving. See 'Help' for more information."]] [:p "4Clojure is a resource to help fledgling clojurians learn the language through interactive problems. The first few problems are easy enough that even someone with no prior experience should find the learning curve forgiving. See 'Help' for more information."]]
Expand Down
17 changes: 17 additions & 0 deletions src/foreclojure/utils.clj
Expand Up @@ -36,6 +36,23 @@
~fail-expr ~fail-expr
~body)) ~body))




(defn maybe-update
"Acts like clojure.core/update-in, except that if the value being assoc'd in
is nil, then instead the key is dissoc'd entirely."
([m ks f]
(let [[k & ks] ks
inner (get m k)
v (if ks
(maybe-update inner ks f)
(f inner))]
(if v
(assoc m k v)
(dissoc m k))))
([m ks f & args]
(maybe-update m ks #(apply f % args))))

(defn image-builder (defn image-builder
"Return a function for constructing an [:img] element from a keyword. "Return a function for constructing an [:img] element from a keyword.
Expand Down

0 comments on commit 16ebe34

Please sign in to comment.