comparison 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
comparison
equal deleted inserted replaced
9:35cf337adfcf 10:ef7dbbd6452c
1 ;; Accumulators
2
3 ;; by Konrad Hinsen
4 ;; last updated May 19, 2009
5
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.
12
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.
20
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]))
30
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)))
36
37 (defn add-items
38 "Add all elements of a collection coll to the accumulator acc."
39 [acc items]
40 (reduce add acc items))
41
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))))
47
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)))
58
59 (defmethod add ::accumulator
60 [a e]
61 ((get-method add (class a)) a e))
62
63 (defmethod combine ::accumulator
64 [& as]
65 (apply (get-method combine (class (first as))) as))
66
67 (defmethod ga/+ ::accumulator
68 [x y]
69 (combine x y))
70
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.")
76
77 (defmethod combine clojure.lang.IPersistentVector
78 [& vs]
79 (with-acc-tag (vec (apply concat vs))))
80
81 (defmethod add clojure.lang.IPersistentVector
82 [v e]
83 (with-acc-tag (conj v e)))
84
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.")
90
91 (defmethod combine clojure.lang.IPersistentList
92 [& vs]
93 (with-acc-tag (apply concat vs)))
94
95 (defmethod add clojure.lang.IPersistentList
96 [v e]
97 (with-acc-tag (conj v e)))
98
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.")
104
105 (defmethod combine clojure.lang.PersistentQueue
106 [& vs]
107 (add-items (first vs) (apply concat (rest vs))))
108
109 (defmethod add clojure.lang.PersistentQueue
110 [v e]
111 (with-acc-tag (conj v e)))
112
113 ;
114 ; Set accumulator
115 ;
116 (defvar empty-set (with-acc-tag #{})
117 "An empty set accumulator.")
118
119 (defmethod combine (class empty-set)
120 [& vs]
121 (with-acc-tag (apply clojure.set/union vs)))
122
123 (defmethod add (class empty-set)
124 [v e]
125 (with-acc-tag (conj v e)))
126
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.")
133
134 (defmethod combine java.lang.String
135 [& vs]
136 (apply str vs))
137
138 (defmethod add java.lang.String
139 [v e]
140 (str v e))
141
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.")
147
148 (defmethod combine clojure.lang.IPersistentMap
149 [& vs]
150 (with-acc-tag (apply merge vs)))
151
152 (defmethod add clojure.lang.IPersistentMap
153 [v e]
154 (with-acc-tag (conj v e)))
155
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#))))))
173
174 (defacc sum + 0
175 "An empty sum accumulator. Only numbers can be added.")
176
177 (defacc product * 1
178 "An empty sum accumulator. Only numbers can be added.")
179
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.
184
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.")
190
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.")
196
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))))
204
205 (derive ::min-max ::accumulator)
206
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.")
210
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)))
216
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)))
224
225 ;
226 ; Mean and variance accumulator
227 ;
228 (deftype ::mean-variance mean-variance)
229
230 (derive ::mean-variance ::accumulator)
231
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.")
235
236 (defmethod combine ::mean-variance
237 ([mv]
238 mv)
239
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})))
251
252 ([mv1 mv2 & mvs]
253 (reduce combine (combine mv1 mv2) mvs)))
254
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})))
263
264 ;
265 ; Counter accumulator
266 ;
267 (deftype ::counter counter)
268
269 (derive ::counter ::accumulator)
270
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.")
274
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)))
281
282 (defmethod add ::counter
283 [v e]
284 (assoc v e (inc (get v e 0))))
285
286 ;
287 ; Counter accumulator with total count
288 ;
289 (deftype ::counter-with-total counter-with-total)
290 (derive ::counter-with-total ::counter)
291
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.")
297
298 (defmethod add ::counter-with-total
299 [v e]
300 (assoc v e (inc (get v e 0))
301 :total (inc (:total v))))
302
303 ;
304 ; Accumulator n-tuple
305 ;
306 (deftype ::tuple acc-tuple)
307
308 (derive ::tuple ::accumulator)
309
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)))
317
318 (defmethod combine ::tuple
319 [& vs]
320 (acc-tuple (vec (map combine vs))))
321
322 (defmethod add ::tuple
323 [v e]
324 (acc-tuple (vec (map add v e))))