view src/clojure/contrib/accumulators.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line source
1 ;; Accumulators
3 ;; by Konrad Hinsen
4 ;; last updated May 19, 2009
6 ;; This module defines various accumulators (list, vector, map,
7 ;; sum, product, counter, and combinations thereof) with a common
8 ;; interface defined by the multimethods add and combine.
9 ;; For each accumulator type, its empty value is defined in this module.
10 ;; Applications typically use this as a starting value and add data
11 ;; using the add multimethod.
13 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use
14 ;; and distribution terms for this software are covered by the Eclipse
15 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
16 ;; which can be found in the file epl-v10.html at the root of this
17 ;; distribution. By using this software in any fashion, you are
18 ;; agreeing to be bound by the terms of this license. You must not
19 ;; remove this notice, or any other, from this software.
21 (ns
22 ^{:author "Konrad Hinsen"
23 :doc "A generic accumulator interface and implementations of various
24 accumulators."}
25 clojure.contrib.accumulators
26 (:refer-clojure :exclude (deftype))
27 (:use [clojure.contrib.types :only (deftype)])
28 (:use [clojure.contrib.def :only (defvar defvar- defmacro-)])
29 (:require [clojure.contrib.generic.arithmetic :as ga]))
31 (defmulti add
32 "Add item to the accumulator acc. The exact meaning of adding an
33 an item depends on the type of the accumulator."
34 {:arglists '([acc item])}
35 (fn [acc item] (type acc)))
37 (defn add-items
38 "Add all elements of a collection coll to the accumulator acc."
39 [acc items]
40 (reduce add acc items))
42 (defmulti combine
43 "Combine the values of the accumulators acc1 and acc2 into a
44 single accumulator of the same type."
45 {:arglists '([& accs])}
46 (fn [& accs] (type (first accs))))
48 ;
49 ; An ::accumulator type tag is attached to tbe built-in types
50 ; when used as accumulators, and new types are derived from it.
51 ; Multimethods add and combine for ::accumulator sub-dispatch on class.
52 ; We also define generic addition as the combine operation.
53 ;
54 (let [meta-map {:type ::accumulator}]
55 (defn- with-acc-tag
56 [x]
57 (with-meta x meta-map)))
59 (defmethod add ::accumulator
60 [a e]
61 ((get-method add (class a)) a e))
63 (defmethod combine ::accumulator
64 [& as]
65 (apply (get-method combine (class (first as))) as))
67 (defmethod ga/+ ::accumulator
68 [x y]
69 (combine x y))
71 ;
72 ; Vector accumulator
73 ;
74 (defvar empty-vector (with-acc-tag [])
75 "An empty vector accumulator. Adding an item appends it at the end.")
77 (defmethod combine clojure.lang.IPersistentVector
78 [& vs]
79 (with-acc-tag (vec (apply concat vs))))
81 (defmethod add clojure.lang.IPersistentVector
82 [v e]
83 (with-acc-tag (conj v e)))
85 ;
86 ; List accumulator
87 ;
88 (defvar empty-list (with-acc-tag '())
89 "An empty list accumulator. Adding an item appends it at the beginning.")
91 (defmethod combine clojure.lang.IPersistentList
92 [& vs]
93 (with-acc-tag (apply concat vs)))
95 (defmethod add clojure.lang.IPersistentList
96 [v e]
97 (with-acc-tag (conj v e)))
99 ;
100 ; Queue accumulator
101 ;
102 (defvar empty-queue (with-acc-tag clojure.lang.PersistentQueue/EMPTY)
103 "An empty queue accumulator. Adding an item appends it at the end.")
105 (defmethod combine clojure.lang.PersistentQueue
106 [& vs]
107 (add-items (first vs) (apply concat (rest vs))))
109 (defmethod add clojure.lang.PersistentQueue
110 [v e]
111 (with-acc-tag (conj v e)))
113 ;
114 ; Set accumulator
115 ;
116 (defvar empty-set (with-acc-tag #{})
117 "An empty set accumulator.")
119 (defmethod combine (class empty-set)
120 [& vs]
121 (with-acc-tag (apply clojure.set/union vs)))
123 (defmethod add (class empty-set)
124 [v e]
125 (with-acc-tag (conj v e)))
127 ;
128 ; String accumulator
129 ;
130 (defvar empty-string ""
131 "An empty string accumulator. Adding an item (string or character)
132 appends it at the end.")
134 (defmethod combine java.lang.String
135 [& vs]
136 (apply str vs))
138 (defmethod add java.lang.String
139 [v e]
140 (str v e))
142 ;
143 ; Map accumulator
144 ;
145 (defvar empty-map (with-acc-tag {})
146 "An empty map accumulator. Items to be added must be [key value] pairs.")
148 (defmethod combine clojure.lang.IPersistentMap
149 [& vs]
150 (with-acc-tag (apply merge vs)))
152 (defmethod add clojure.lang.IPersistentMap
153 [v e]
154 (with-acc-tag (conj v e)))
156 ;
157 ; Numerical accumulators: sum, product, minimum, maximum
158 ;
159 (defmacro- defacc
160 [name op empty doc-string]
161 (let [type-tag (keyword (str *ns*) (str name))
162 empty-symbol (symbol (str "empty-" name))]
163 `(let [op# ~op]
164 (deftype ~type-tag ~name
165 (fn [~'x] {:value ~'x})
166 (fn [~'x] (list (:value ~'x))))
167 (derive ~type-tag ::accumulator)
168 (defvar ~empty-symbol (~name ~empty) ~doc-string)
169 (defmethod combine ~type-tag [& vs#]
170 (~name (apply op# (map :value vs#))))
171 (defmethod add ~type-tag [v# e#]
172 (~name (op# (:value v#) e#))))))
174 (defacc sum + 0
175 "An empty sum accumulator. Only numbers can be added.")
177 (defacc product * 1
178 "An empty sum accumulator. Only numbers can be added.")
180 ; The empty maximum accumulator should have value -infinity.
181 ; This is represented by nil and taken into account in an
182 ; adapted max function. In the minimum accumulator, nil is
183 ; similarly used to represent +infinity.
185 (defacc maximum (fn [& xs]
186 (when-let [xs (seq (filter identity xs))]
187 (apply max xs)))
188 nil
189 "An empty maximum accumulator. Only numbers can be added.")
191 (defacc minimum (fn [& xs]
192 (when-let [xs (seq (filter identity xs))]
193 (apply min xs)))
194 nil
195 "An empty minimum accumulator. Only numbers can be added.")
197 ;
198 ; Numeric min-max accumulator
199 ; (combination of minimum and maximum)
200 ;
201 (deftype ::min-max min-max
202 (fn [min max] {:min min :max max})
203 (fn [mm] (list (:min mm) (:max mm))))
205 (derive ::min-max ::accumulator)
207 (defvar empty-min-max (min-max nil nil)
208 "An empty min-max accumulator, combining minimum and maximum.
209 Only numbers can be added.")
211 (defmethod combine ::min-max
212 [& vs]
213 (let [total-min (apply min (map :min vs))
214 total-max (apply max (map :max vs))]
215 (min-max total-min total-max)))
217 (defmethod add ::min-max
218 [v e]
219 (let [min-v (:min v)
220 max-v (:max v)
221 new-min (if (nil? min-v) e (min min-v e))
222 new-max (if (nil? max-v) e (max max-v e))]
223 (min-max new-min new-max)))
225 ;
226 ; Mean and variance accumulator
227 ;
228 (deftype ::mean-variance mean-variance)
230 (derive ::mean-variance ::accumulator)
232 (defvar empty-mean-variance (mean-variance {:n 0 :mean 0 :variance 0})
233 "An empty mean-variance accumulator, combining sample mean and
234 sample variance. Only numbers can be added.")
236 (defmethod combine ::mean-variance
237 ([mv]
238 mv)
240 ([mv1 mv2]
241 (let [{n1 :n mean1 :mean var1 :variance} mv1
242 {n2 :n mean2 :mean var2 :variance} mv2
243 n (+ n1 n2)
244 mean (/ (+ (* n1 mean1) (* n2 mean2)) n)
245 sq #(* % %)
246 c (+ (* n1 (sq (- mean mean1))) (* n2 (sq (- mean mean2))))
247 var (if (< n 2)
248 0
249 (/ (+ c (* (dec n1) var1) (* (dec n2) var2)) (dec n)))]
250 (mean-variance {:n n :mean mean :variance var})))
252 ([mv1 mv2 & mvs]
253 (reduce combine (combine mv1 mv2) mvs)))
255 (defmethod add ::mean-variance
256 [mv x]
257 (let [{n :n mean :mean var :variance} mv
258 n1 (inc n)
259 d (- x mean)
260 new-mean (+ mean (/ d n1))
261 new-var (if (zero? n) 0 (/ (+ (* (dec n) var) (* d (- x new-mean))) n))]
262 (mean-variance {:n n1 :mean new-mean :variance new-var})))
264 ;
265 ; Counter accumulator
266 ;
267 (deftype ::counter counter)
269 (derive ::counter ::accumulator)
271 (defvar empty-counter (counter {})
272 "An empty counter accumulator. Its value is a map that stores for
273 every item the number of times it was added.")
275 (defmethod combine ::counter
276 [v & vs]
277 (letfn [(add-item [cntr [item n]]
278 (assoc cntr item (+ n (get cntr item 0))))
279 (add-two [c1 c2] (reduce add-item c1 c2))]
280 (reduce add-two v vs)))
282 (defmethod add ::counter
283 [v e]
284 (assoc v e (inc (get v e 0))))
286 ;
287 ; Counter accumulator with total count
288 ;
289 (deftype ::counter-with-total counter-with-total)
290 (derive ::counter-with-total ::counter)
292 (defvar empty-counter-with-total
293 (counter-with-total {:total 0})
294 "An empty counter-with-total accumulator. It works like the counter
295 accumulator, except that the total number of items added is stored as the
296 value of the key :total.")
298 (defmethod add ::counter-with-total
299 [v e]
300 (assoc v e (inc (get v e 0))
301 :total (inc (:total v))))
303 ;
304 ; Accumulator n-tuple
305 ;
306 (deftype ::tuple acc-tuple)
308 (derive ::tuple ::accumulator)
310 (defn empty-tuple
311 "Returns an accumulator tuple with the supplied empty-accumulators
312 as its value. Accumulator tuples consist of several accumulators that
313 work in parallel. Added items must be sequences whose number of elements
314 matches the number of sub-accumulators."
315 [empty-accumulators]
316 (acc-tuple (into [] empty-accumulators)))
318 (defmethod combine ::tuple
319 [& vs]
320 (acc-tuple (vec (map combine vs))))
322 (defmethod add ::tuple
323 [v e]
324 (acc-tuple (vec (map add v e))))