init
This commit is contained in:
commit
09953646eb
3 changed files with 139 additions and 0 deletions
44
README.md
Normal file
44
README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# xforms
|
||||
|
||||
More transducers and reduciing functions for Clojure!
|
||||
|
||||
## Usage
|
||||
|
||||
```clj
|
||||
=> (require '[net.cgrand.xforms :as x])
|
||||
```
|
||||
|
||||
`str` and `str!` are two reducing functions to build Strings and StringBuilders in linear time.
|
||||
|
||||
```clj
|
||||
=> (quick-bench (reduce str (range 256)))
|
||||
Execution time mean : 58,714946 µs
|
||||
=> (quick-bench (reduce x/str (range 256)))
|
||||
Execution time mean : 11,609631 µs
|
||||
```
|
||||
|
||||
`by-key` and `reduce` are two new transducers. Here is an example usage:
|
||||
|
||||
```clj
|
||||
;; reimplementing group-by
|
||||
(defn my-group-by [kfn coll]
|
||||
(into {} (by-key kfn (reduce conj)) coll))
|
||||
|
||||
;; let's go transient!
|
||||
(defn my-group-by [kfn coll]
|
||||
(into {} (by-key kfn (reduce (completing conj! persistent!))) coll))
|
||||
|
||||
=> (quick-bench (group-by odd? (range 256)))
|
||||
Execution time mean : 29,356531 µs
|
||||
=> (quick-bench (my-group-by odd? (range 256)))
|
||||
Execution time mean : 20,604297 µs
|
||||
```
|
||||
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2015 Christophe Grand
|
||||
|
||||
Distributed under the Eclipse Public License either version 1.0 or (at
|
||||
your option) any later version.
|
||||
6
project.clj
Normal file
6
project.clj
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(defproject net.cgrand/xforms "0.1.0-SNAPSHOT"
|
||||
:description "Extra transducers for Clojure"
|
||||
#_#_:url "http://example.com/FIXME"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.7.0"]])
|
||||
89
src/net/cgrand/xforms.clj
Normal file
89
src/net/cgrand/xforms.clj
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
(ns net.cgrand.xforms
|
||||
"Extra transducers for Clojure"
|
||||
{:author "Christophe Grand"}
|
||||
(:refer-clojure :exclude [reduce for partition str])
|
||||
(:require [clojure.core :as clj]))
|
||||
|
||||
(defmacro for
|
||||
"Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer."
|
||||
[[binding %or_ & seq-exprs] body]
|
||||
(assert (and (symbol? %or_) (#{"%" "_"} (name %or_)))
|
||||
"The second element of the comprehension vector must be % or _.")
|
||||
(let [rf (gensym 'rf)
|
||||
acc (gensym 'acc)
|
||||
body
|
||||
(clj/reduce (fn [body [expr binding]]
|
||||
(case binding
|
||||
:let `(let ~expr ~body)
|
||||
:when `(if ~expr ~body ~acc)
|
||||
:while `(if ~expr ~body (reduced ~acc))
|
||||
`(reduce (fn [~acc ~binding] ~body) ~acc ~expr)))
|
||||
`(~rf ~acc ~body)
|
||||
(clj/partition 2 (rseq (vec seq-exprs))))]
|
||||
`(fn [~rf]
|
||||
(fn
|
||||
([] (~rf))
|
||||
([~acc] (~rf ~acc))
|
||||
([~acc ~binding] ~body)))))
|
||||
|
||||
(defn reduce
|
||||
"A transducer that reduces a collection to a 1-item collection consisting of only the reduced result."
|
||||
([f]
|
||||
(fn [rf]
|
||||
(let [vacc (volatile! (f))]
|
||||
(fn
|
||||
([] (rf))
|
||||
([acc] (rf (rf acc (f (unreduced @vacc)))))
|
||||
([acc x]
|
||||
(if (reduced? (vswap! vacc f x))
|
||||
(reduced acc)
|
||||
acc))))))
|
||||
([f init]
|
||||
(reduce (fn ([] init) ([acc] (f acc)) ([acc x] (f acc x))))))
|
||||
|
||||
(defmacro ^:private or-instance? [class x y]
|
||||
(let [xsym (gensym 'x_)]
|
||||
`(let [~xsym ~x]
|
||||
(if (instance? ~class ~xsym) ~(with-meta xsym {:tag class}) ~y))))
|
||||
|
||||
(defn str!
|
||||
"Like xforms/str but returns a StringBuilder."
|
||||
([] (StringBuilder.))
|
||||
([sb] (or-instance? StringBuilder sb (StringBuilder. (clj/str sb)))) ; the instance? checks are for compatibility with str in case of seeded reduce/transduce.
|
||||
([sb x] (.append (or-instance? StringBuilder sb (StringBuilder. (clj/str sb))) x)))
|
||||
|
||||
(def str
|
||||
"Reducing function to build strings in linear time. Acts as replacement for clojure.core/str in a reduce/transduce call."
|
||||
(completing str! clj/str))
|
||||
|
||||
;; for both map entries and vectors
|
||||
(defn- key' [kv] (nth kv 0))
|
||||
(defn- val' [kv] (nth kv 1))
|
||||
|
||||
(defn- noprf "The noop reducing function" ([acc] acc) ([acc _] acc))
|
||||
|
||||
(defn by-key
|
||||
"Returns a tranducer that applies the transducer xform independently for items partitioned by kfn.
|
||||
(This docstring is a hard nut to crack.)"
|
||||
([xform] (by-key key' val' vector xform))
|
||||
([kfn xform] (by-key kfn identity vector xform))
|
||||
([kfn vfn xform] (by-key kfn vfn vector xform))
|
||||
([kfn vfn pair xform]
|
||||
(fn [rf]
|
||||
(let [make-rf (if pair
|
||||
(fn [k] (fn ([acc] (rf acc)) ([acc v] (rf acc (pair k v)))))
|
||||
(constantly rf))
|
||||
m (volatile! (transient {}))]
|
||||
(fn self
|
||||
([] (rf))
|
||||
([acc] (clj/reduce (fn [acc krf] (krf acc)) acc (vals (persistent! @m))))
|
||||
([acc x]
|
||||
(let [k (kfn x)
|
||||
krf (or (get @m k) (doto (xform (make-rf x)) (->> (vswap! m assoc! k))))
|
||||
acc (krf acc (vfn x))]
|
||||
(when (reduced? acc)
|
||||
(vswap! m assoc! k noprf))
|
||||
(unreduced acc))))))))
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in a new issue