0.6.0 x/reductions and extend behavior of x/for

This commit is contained in:
Christophe Grand 2016-12-02 16:27:59 -06:00
parent dfd401f647
commit 8d393365c3
4 changed files with 53 additions and 9 deletions

View file

@ -4,7 +4,7 @@ More transducers and reducing functions for Clojure!
[![Build Status](https://travis-ci.org/cgrand/xforms.png?branch=master)](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

View file

@ -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"

View file

@ -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

View file

@ -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
[]