Skip to content

Instantly share code, notes, and snippets.

@ejackson
Created June 22, 2010 17:08
Show Gist options
  • Save ejackson/448759 to your computer and use it in GitHub Desktop.
Save ejackson/448759 to your computer and use it in GitHub Desktop.
(ns
#^{:doc "New definitions for a timeseries type"}
ts
(:use clojure.test)
(:import (clojure.lang PersistentTreeSet)
(java.io Writer)))
;;----------------------------------------------------------------------------
(defprotocol Trim
"Trim a timeseries. There are performance advantages to having older+newer
rather than just trim in most implementations."
(trim-older [ts snip])
(trim-newer [ts snip]))
;;----------------------------------------------------------------------------
(defprotocol GettableData
"Kludge around my inability to figure out getting the right gensym"
(get-dataset [ts]))
;;----------------------------------------------------------------------------
(defn circular-conj
"Conj maintaining the datatype and count"
([max coll x]
(let [d (- (count coll) max)]
(if (<= d 0)
(conj coll x)
(recur max
(into (empty coll)
(drop (inc d) coll))
x))))
([max coll x & xs]
(if xs
(recur max (circular-conj max coll x) (first xs) (next xs))
(circular-conj max coll x))))
;;----------------------------------------------------------------------------
;; Idea is to write a macro that outputs the full deftype for Timeseries or
;; timeseries, but takes as an argument that datastructure which will become
;; the functions that we 'override' - in this case conj.
(defmacro create-timeseries-type [name cons-method]
`(deftype ~(symbol (str name)) [dataset#]
Object
(equals [this# o#]
(or (identical? this# o#)
(and (.isInstance ~(symbol (str name)) o#)
(= dataset# (.get-dataset o#)))))
(hashCode [this#]
(hash dataset#))
(toString [this#]
(str "(ts " (apply pr-str dataset#)")"))
;;--------------
clojure.lang.IPersistentCollection
(cons [this# a#]
(new ~(symbol (str name)) (~cons-method dataset# a#)))
(empty [this#]
(new ~name (-> dataset# empty)))
;; How does this differ from equals
(equiv [this# o#]
(.equals this# o#))
(seq [this#]
(seq dataset#))
;;--------------
clojure.lang.IPersistentStack
(peek [this#]
(-> dataset# rseq first))
(pop [this#]
(new ~name (disj dataset# (peek this#))))
;;--------------
clojure.lang.Sorted
(comparator [this#]
(.comparator dataset#))
(entryKey [this# entry#]
(.entryKey dataset# entry#))
(seq [this# ascending#]
(.seq dataset# ascending#))
(seqFrom [this# key# ascending#]
(.seqFrom dataset# key# ascending#))
;;--------------
Trim
(trim-older [this# snip#]
(new ~name (reduce disj dataset# (subseq this# < snip#))))
(trim-newer [this# snip#]
(new ~name (reduce disj dataset# (rsubseq this# > snip#))))
;;--------------
GettableData
(get-dataset [this#] dataset#)))
;;---------------------------------------------------------------------------
;; Extra API
;;---------------------------------------------------------------------------
;; Straight Timeseries
;; TODO: This recipe is seems like a candidate for a macro.
(create-timeseries-type
Timeseries
circular-conj)
(defn ts [& xs]
(Timeseries. (apply sorted-set xs)))
(defmethod print-method Timeseries [o, ^Writer w]
(#'clojure.core/print-sequential "(ts " #'clojure.core/pr-on " " ")" o w))
(defmethod print-dup Timeseries [o, ^Writer w]
(print-method o w))
;;---------------------------------------------------------------------------
;; Circular Timeseries
(def circular-series-length 5)
(create-timeseries-type
CircularTimeseries
(partial circular-conj circular-series-length))
(defn cts [& xs]
(CircularTimeseries. (apply sorted-set xs)))
(defmethod print-method CircularTimeseries [o, ^Writer w]
(#'clojure.core/print-sequential "(cts " #'clojure.core/pr-on " " ")" o w))
(defmethod print-dup CircularTimeseries [o, ^Writer w]
(print-method o w))
;;---------------------------------------------------------------------
;; Tests below
(deftest interface-test
(let [a (ts 1 2 3)
b (ts 1 2 3)
c (ts 1 2)
d (ts 1)
e (ts)
f (ts 2 3)
g (ts 3)]
(testing "Testing conj..."
(are [y] (= a y)
(conj c 3)
(conj e 1 2 3)))
(testing "Testing peek..."
(are [x y] (= x y)
3 (peek a)
2 (peek c)
nil (peek e)))
(testing "Testing subseq..."
(are [x y z] (= x (subseq a y z))
[1 2 3] > -2
[1 2 3] > 0
[2 3] > 1
[1 2 3] >= 1
nil > 3 ;; Technically this should be []
nil > 4
[1 2 3] < 4
[1 2 3] <= 3
[1 2] < 3
[] < 0))
(testing "Testing rsubseq..."
(are [x y z] (= x (rsubseq a y z))
[3 2 1] > -2
[3 2 1] > 0
[3 2] > 1
[3 2 1] >= 1
[] > 3
[] > 4
[3 2 1] < 4
[3 2 1] <= 3
[2 1] < 3
nil < 0)) ;; Technically this should be []
;; trim
(testing "Testing trim..."
(are [x y] (= (Timeseries. (apply sorted-set x)) (trim-older a y))
[1 2 3] 0
[1 2 3] 1
[2 3] 2
[3] 3
[] 4)
(are [x y] (= (Timeseries. (apply sorted-set x)) (trim-newer a y))
[] 0
[1] 1
[1 2] 2
[1 2 3] 3
[1 2 3] 4))))
(deftest internal-tests
(let [a (ts 1 2 3)
b (ts 1 2)
c (ts 1 2 3)
d (ts 1)
e (ts )]
(testing "Testing Object Interface..."
(are [x y] (= x y)
a a
a c)
(are [x y] (not (.equals x y))
a b
b a)
;; hashCode
(are [x y] (= (.hashCode x) (.hashCode y))
a a
a c)
;; toString
(is (= (.toString a) (.toString c))))
(testing "clojure.lang.IPersistentCollection..."
;; Check the basic functionality
(are [x y] (.equals x y)
a (.cons b 3)
e (.empty a)
(seq [1 2 3]) (seq a))
;; Type check these functions
(are [x] (= (type e) (type x))
(.cons b 3)
(.empty a)))
(testing "clojure.lang.IPersistentStack..."
;; Check the basic functionality
(are [x y] (= x y)
3 (.peek a)
b (.pop a)
d (-> a .pop .pop)
;; Empties and nils
nil (.peek e)
e (.pop e))
;; Type check these functions
(are [x] (= (type e) (type x))
(.pop a)))
(testing "Sorted..."
(are [x y] (= x y)
(.seq a true) (seq a)
(.seq a true) (seq [1 2 3])
(.seq a false) (seq [3 2 1])
(.seqFrom a 2 true) (seq [2 3])
(.seqFrom a 2 false) (seq [2 1])))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment