0.6.0 x/reductions and extend behavior of x/for
This commit is contained in:
parent
dfd401f647
commit
8d393365c3
4 changed files with 53 additions and 9 deletions
|
|
@ -4,7 +4,7 @@ More transducers and reducing functions for Clojure!
|
|||
|
||||
[](https://travis-ci.org/cgrand/xforms)
|
||||
|
||||
Transducers: `reduce`, `into`, `last`, `count`, `avg`, `min`, `minimum`, `max`, `maximum`, `str`, `by-key`, `partition`, `for`, `multiplex`, `transjuxt`, `window` and `window-by-time`.
|
||||
Transducers: `reduce`, `into`, `last`, `count`, `avg`, `min`, `minimum`, `max`, `maximum`, `str`, `by-key`, `partition`, `reductions`, `for`, `multiplex`, `transjuxt`, `window` and `window-by-time`.
|
||||
|
||||
Reducing functions (in `net.cgrand.xforms.rfs`): `min`, `minimum`, `max`, `maximum`, `str`, `str!`, `avg`, `juxt` and `last`.
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ Transducing context: `transjuxt` (for performing several transductions in a sing
|
|||
Add this dependency to your project:
|
||||
|
||||
```clj
|
||||
[net.cgrand/xforms "0.5.1"]
|
||||
[net.cgrand/xforms "0.6.0"]
|
||||
```
|
||||
|
||||
```clj
|
||||
|
|
@ -199,6 +199,11 @@ Evaluation count : 24 in 6 samples of 4 calls.
|
|||
Execution time std-deviation : 364,932996 µs
|
||||
```
|
||||
|
||||
## Changelog
|
||||
### 0.6.0
|
||||
|
||||
* Added `x/reductions`
|
||||
* Now if the first collection expression in `x/for` is not a placeholder then `x/for` works like `x/for` but returns an eduction and performs all iterations using reduce.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject net.cgrand/xforms "0.5.1"
|
||||
(defproject net.cgrand/xforms "0.6.0"
|
||||
:description "Extra transducers for Clojure"
|
||||
#_#_:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
(ns net.cgrand.xforms
|
||||
"Extra transducers for Clojure"
|
||||
{:author "Christophe Grand"}
|
||||
(:refer-clojure :exclude [reduce into count for partition str last keys vals min max])
|
||||
(:refer-clojure :exclude [reduce reductions into count for partition str last keys vals min max])
|
||||
(:require [clojure.core :as clj]
|
||||
[net.cgrand.xforms.rfs :as rf]))
|
||||
|
||||
(defmacro for
|
||||
"Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer."
|
||||
"Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer.
|
||||
When the first expression is not % (or _) returns an eduction."
|
||||
[[binding %or_ & seq-exprs] body-expr]
|
||||
(assert (and (symbol? %or_) (#{"%" "_"} (name %or_)))
|
||||
"The second element of the comprehension vector must be % or _.")
|
||||
(let [rf (gensym 'rf)
|
||||
(if-not (and (symbol? %or_) (#{"%" "_"} (name %or_)))
|
||||
`(eduction (for [~binding ~'% ~@seq-exprs] ~body-expr) ~%or_)
|
||||
(let [rf (gensym 'rf)
|
||||
acc (gensym 'acc)
|
||||
pair? #(and (vector? %) (= 2 (clj/count %)))
|
||||
destructuring-pair? (every-pred pair?
|
||||
|
|
@ -47,7 +48,7 @@
|
|||
~(if (destructuring-pair? binding)
|
||||
`([~acc ~@(map #(vary-meta % dissoc :tag) binding)] ~body)
|
||||
`([~acc k# v#]
|
||||
(let [~binding (clojure.lang.MapEntry. k# v#)] ~body))))))))
|
||||
(let [~binding (clojure.lang.MapEntry. k# v#)] ~body)))))))))
|
||||
|
||||
(defprotocol KvRfable "Protocol for reducing fns that accept key and val as separate arguments."
|
||||
(some-kvrf [f] "Returns a kvrf or nil"))
|
||||
|
|
@ -310,6 +311,30 @@
|
|||
acc)
|
||||
acc))))))))
|
||||
|
||||
(defn reductions
|
||||
"Transient version of reductions. There's a difference in behavior when init is not provided: (f) is used.
|
||||
So x/reductions works like x/reduce or transduce, not like reduce and reductions when no init and 1-item input."
|
||||
([f] (reductions f (f)))
|
||||
([f init]
|
||||
(fn [rf]
|
||||
(let [prev (volatile! nil)]
|
||||
(vreset! prev prev) ; cheap sentinel to detect the first call, this is done to avoid having a 1-item delay
|
||||
(fn
|
||||
([] (rf)) ; no you can't emit init there since there's no guarantee that this arity is going to be called
|
||||
([acc] (if (identical? @prev prev)
|
||||
(rf (unreduced (rf acc init)))
|
||||
(rf acc)))
|
||||
([acc x]
|
||||
(if (identical? @prev prev)
|
||||
(let [acc (rf acc (vreset! prev init))]
|
||||
(if (reduced? acc)
|
||||
acc
|
||||
(recur acc x)))
|
||||
(let [curr (vswap! prev f x)]
|
||||
(if (reduced? curr)
|
||||
(ensure-reduced (rf acc @curr))
|
||||
(rf acc curr))))))))))
|
||||
|
||||
(def avg (reduce rf/avg))
|
||||
|
||||
(defn window
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@
|
|||
8 (range 16)))
|
||||
(is (trial (x/partition 3 (take 2))
|
||||
8 (range 2)))
|
||||
(is (trial (x/reductions conj [])
|
||||
8 (range 2)))
|
||||
(is (trial (x/reductions conj)
|
||||
8 (range 2)))
|
||||
(is (trial (x/into [])
|
||||
4 (range 16)))
|
||||
(is (trial (x/for [x % y (range x)] [x y])
|
||||
|
|
@ -68,6 +72,16 @@
|
|||
(is (trial (x/reduce +)
|
||||
4 (range 16)))))
|
||||
|
||||
(deftest reductions
|
||||
(is (= (into [] (x/reductions +) (range 10)) [0 0 1 3 6 10 15 21 28 36 45]))
|
||||
(is (= (into [] (x/reductions +) (range 0)) [0]))
|
||||
(is (= (into [] (x/reductions +) (range 1)) [0 0]))
|
||||
(is (= (into [] (x/reductions +) (range 2)) [0 0 1]))
|
||||
(is (= (into [] (comp (x/reductions +) (take 2)) (range)) [0 0]))
|
||||
(is (= (into [] (comp (x/reductions +) (take 3)) (range)) [0 0 1]))
|
||||
(is (= (into [] (comp (take 3) (x/reductions +)) (range)) [0 0 1 3]))
|
||||
(is (= (into [] (x/reductions (constantly (reduced 42)) 0) (range)) [0 42])))
|
||||
|
||||
(deftest window-by-time
|
||||
(is (= (into
|
||||
[]
|
||||
|
|
|
|||
Loading…
Reference in a new issue