aboutsummaryrefslogtreecommitdiff
path: root/modules/accumulators/src/main/clojure
diff options
context:
space:
mode:
authorStuart Sierra <mail@stuartsierra.com>2010-08-07 16:41:53 -0400
committerStuart Sierra <mail@stuartsierra.com>2010-08-07 16:41:53 -0400
commita6a92b9b3d2bfd9a56e1e5e9cfba706d1aeeaae5 (patch)
treef1f3da9887dc2dc557df3282b0bcbd4d701ec593 /modules/accumulators/src/main/clojure
parente7930c85290f77815cdb00a60604feedfa2d0194 (diff)
Split all namespaces into sub-modules.
* Examples and tests have not been copied over. * Clojure test/compile phases are commented out in parent POM. * May require installing parent POM before full build.
Diffstat (limited to 'modules/accumulators/src/main/clojure')
-rw-r--r--modules/accumulators/src/main/clojure/clojure/contrib/accumulators.clj324
1 files changed, 324 insertions, 0 deletions
diff --git a/modules/accumulators/src/main/clojure/clojure/contrib/accumulators.clj b/modules/accumulators/src/main/clojure/clojure/contrib/accumulators.clj
new file mode 100644
index 00000000..55073e33
--- /dev/null
+++ b/modules/accumulators/src/main/clojure/clojure/contrib/accumulators.clj
@@ -0,0 +1,324 @@
+;; Accumulators
+
+;; by Konrad Hinsen
+;; last updated May 19, 2009
+
+;; This module defines various accumulators (list, vector, map,
+;; sum, product, counter, and combinations thereof) with a common
+;; interface defined by the multimethods add and combine.
+;; For each accumulator type, its empty value is defined in this module.
+;; Applications typically use this as a starting value and add data
+;; using the add multimethod.
+
+;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+(ns
+ ^{:author "Konrad Hinsen"
+ :doc "A generic accumulator interface and implementations of various
+ accumulators."}
+ clojure.contrib.accumulators
+ (:refer-clojure :exclude (deftype))
+ (:use [clojure.contrib.types :only (deftype)])
+ (:use [clojure.contrib.def :only (defvar defvar- defmacro-)])
+ (:require [clojure.contrib.generic.arithmetic :as ga]))
+
+(defmulti add
+ "Add item to the accumulator acc. The exact meaning of adding an
+ an item depends on the type of the accumulator."
+ {:arglists '([acc item])}
+ (fn [acc item] (type acc)))
+
+(defn add-items
+ "Add all elements of a collection coll to the accumulator acc."
+ [acc items]
+ (reduce add acc items))
+
+(defmulti combine
+ "Combine the values of the accumulators acc1 and acc2 into a
+ single accumulator of the same type."
+ {:arglists '([& accs])}
+ (fn [& accs] (type (first accs))))
+
+;
+; An ::accumulator type tag is attached to tbe built-in types
+; when used as accumulators, and new types are derived from it.
+; Multimethods add and combine for ::accumulator sub-dispatch on class.
+; We also define generic addition as the combine operation.
+;
+(let [meta-map {:type ::accumulator}]
+ (defn- with-acc-tag
+ [x]
+ (with-meta x meta-map)))
+
+(defmethod add ::accumulator
+ [a e]
+ ((get-method add (class a)) a e))
+
+(defmethod combine ::accumulator
+ [& as]
+ (apply (get-method combine (class (first as))) as))
+
+(defmethod ga/+ ::accumulator
+ [x y]
+ (combine x y))
+
+;
+; Vector accumulator
+;
+(defvar empty-vector (with-acc-tag [])
+ "An empty vector accumulator. Adding an item appends it at the end.")
+
+(defmethod combine clojure.lang.IPersistentVector
+ [& vs]
+ (with-acc-tag (vec (apply concat vs))))
+
+(defmethod add clojure.lang.IPersistentVector
+ [v e]
+ (with-acc-tag (conj v e)))
+
+;
+; List accumulator
+;
+(defvar empty-list (with-acc-tag '())
+ "An empty list accumulator. Adding an item appends it at the beginning.")
+
+(defmethod combine clojure.lang.IPersistentList
+ [& vs]
+ (with-acc-tag (apply concat vs)))
+
+(defmethod add clojure.lang.IPersistentList
+ [v e]
+ (with-acc-tag (conj v e)))
+
+;
+; Queue accumulator
+;
+(defvar empty-queue (with-acc-tag clojure.lang.PersistentQueue/EMPTY)
+ "An empty queue accumulator. Adding an item appends it at the end.")
+
+(defmethod combine clojure.lang.PersistentQueue
+ [& vs]
+ (add-items (first vs) (apply concat (rest vs))))
+
+(defmethod add clojure.lang.PersistentQueue
+ [v e]
+ (with-acc-tag (conj v e)))
+
+;
+; Set accumulator
+;
+(defvar empty-set (with-acc-tag #{})
+ "An empty set accumulator.")
+
+(defmethod combine (class empty-set)
+ [& vs]
+ (with-acc-tag (apply clojure.set/union vs)))
+
+(defmethod add (class empty-set)
+ [v e]
+ (with-acc-tag (conj v e)))
+
+;
+; String accumulator
+;
+(defvar empty-string ""
+ "An empty string accumulator. Adding an item (string or character)
+ appends it at the end.")
+
+(defmethod combine java.lang.String
+ [& vs]
+ (apply str vs))
+
+(defmethod add java.lang.String
+ [v e]
+ (str v e))
+
+;
+; Map accumulator
+;
+(defvar empty-map (with-acc-tag {})
+ "An empty map accumulator. Items to be added must be [key value] pairs.")
+
+(defmethod combine clojure.lang.IPersistentMap
+ [& vs]
+ (with-acc-tag (apply merge vs)))
+
+(defmethod add clojure.lang.IPersistentMap
+ [v e]
+ (with-acc-tag (conj v e)))
+
+;
+; Numerical accumulators: sum, product, minimum, maximum
+;
+(defmacro- defacc
+ [name op empty doc-string]
+ (let [type-tag (keyword (str *ns*) (str name))
+ empty-symbol (symbol (str "empty-" name))]
+ `(let [op# ~op]
+ (deftype ~type-tag ~name
+ (fn [~'x] {:value ~'x})
+ (fn [~'x] (list (:value ~'x))))
+ (derive ~type-tag ::accumulator)
+ (defvar ~empty-symbol (~name ~empty) ~doc-string)
+ (defmethod combine ~type-tag [& vs#]
+ (~name (apply op# (map :value vs#))))
+ (defmethod add ~type-tag [v# e#]
+ (~name (op# (:value v#) e#))))))
+
+(defacc sum + 0
+ "An empty sum accumulator. Only numbers can be added.")
+
+(defacc product * 1
+ "An empty sum accumulator. Only numbers can be added.")
+
+; The empty maximum accumulator should have value -infinity.
+; This is represented by nil and taken into account in an
+; adapted max function. In the minimum accumulator, nil is
+; similarly used to represent +infinity.
+
+(defacc maximum (fn [& xs]
+ (when-let [xs (seq (filter identity xs))]
+ (apply max xs)))
+ nil
+ "An empty maximum accumulator. Only numbers can be added.")
+
+(defacc minimum (fn [& xs]
+ (when-let [xs (seq (filter identity xs))]
+ (apply min xs)))
+ nil
+ "An empty minimum accumulator. Only numbers can be added.")
+
+;
+; Numeric min-max accumulator
+; (combination of minimum and maximum)
+;
+(deftype ::min-max min-max
+ (fn [min max] {:min min :max max})
+ (fn [mm] (list (:min mm) (:max mm))))
+
+(derive ::min-max ::accumulator)
+
+(defvar empty-min-max (min-max nil nil)
+ "An empty min-max accumulator, combining minimum and maximum.
+ Only numbers can be added.")
+
+(defmethod combine ::min-max
+ [& vs]
+ (let [total-min (apply min (map :min vs))
+ total-max (apply max (map :max vs))]
+ (min-max total-min total-max)))
+
+(defmethod add ::min-max
+ [v e]
+ (let [min-v (:min v)
+ max-v (:max v)
+ new-min (if (nil? min-v) e (min min-v e))
+ new-max (if (nil? max-v) e (max max-v e))]
+ (min-max new-min new-max)))
+
+;
+; Mean and variance accumulator
+;
+(deftype ::mean-variance mean-variance)
+
+(derive ::mean-variance ::accumulator)
+
+(defvar empty-mean-variance (mean-variance {:n 0 :mean 0 :variance 0})
+ "An empty mean-variance accumulator, combining sample mean and
+ sample variance. Only numbers can be added.")
+
+(defmethod combine ::mean-variance
+ ([mv]
+ mv)
+
+ ([mv1 mv2]
+ (let [{n1 :n mean1 :mean var1 :variance} mv1
+ {n2 :n mean2 :mean var2 :variance} mv2
+ n (+ n1 n2)
+ mean (/ (+ (* n1 mean1) (* n2 mean2)) n)
+ sq #(* % %)
+ c (+ (* n1 (sq (- mean mean1))) (* n2 (sq (- mean mean2))))
+ var (if (< n 2)
+ 0
+ (/ (+ c (* (dec n1) var1) (* (dec n2) var2)) (dec n)))]
+ (mean-variance {:n n :mean mean :variance var})))
+
+ ([mv1 mv2 & mvs]
+ (reduce combine (combine mv1 mv2) mvs)))
+
+(defmethod add ::mean-variance
+ [mv x]
+ (let [{n :n mean :mean var :variance} mv
+ n1 (inc n)
+ d (- x mean)
+ new-mean (+ mean (/ d n1))
+ new-var (if (zero? n) 0 (/ (+ (* (dec n) var) (* d (- x new-mean))) n))]
+ (mean-variance {:n n1 :mean new-mean :variance new-var})))
+
+;
+; Counter accumulator
+;
+(deftype ::counter counter)
+
+(derive ::counter ::accumulator)
+
+(defvar empty-counter (counter {})
+ "An empty counter accumulator. Its value is a map that stores for
+ every item the number of times it was added.")
+
+(defmethod combine ::counter
+ [v & vs]
+ (letfn [(add-item [cntr [item n]]
+ (assoc cntr item (+ n (get cntr item 0))))
+ (add-two [c1 c2] (reduce add-item c1 c2))]
+ (reduce add-two v vs)))
+
+(defmethod add ::counter
+ [v e]
+ (assoc v e (inc (get v e 0))))
+
+;
+; Counter accumulator with total count
+;
+(deftype ::counter-with-total counter-with-total)
+(derive ::counter-with-total ::counter)
+
+(defvar empty-counter-with-total
+ (counter-with-total {:total 0})
+ "An empty counter-with-total accumulator. It works like the counter
+ accumulator, except that the total number of items added is stored as the
+ value of the key :total.")
+
+(defmethod add ::counter-with-total
+ [v e]
+ (assoc v e (inc (get v e 0))
+ :total (inc (:total v))))
+
+;
+; Accumulator n-tuple
+;
+(deftype ::tuple acc-tuple)
+
+(derive ::tuple ::accumulator)
+
+(defn empty-tuple
+ "Returns an accumulator tuple with the supplied empty-accumulators
+ as its value. Accumulator tuples consist of several accumulators that
+ work in parallel. Added items must be sequences whose number of elements
+ matches the number of sub-accumulators."
+ [empty-accumulators]
+ (acc-tuple (into [] empty-accumulators)))
+
+(defmethod combine ::tuple
+ [& vs]
+ (acc-tuple (vec (map combine vs))))
+
+(defmethod add ::tuple
+ [v e]
+ (acc-tuple (vec (map add v e))))