Skip to content

Commit

Permalink
Iterable & Reducible range for Long arugments
Browse files Browse the repository at this point in the history
Introduces a range deftype that is iterable, reducible, and seqable
when the arguments are all Longs.

Method paths that use range as a seq will defer to the original range
implementation. In this patch no attempt is made to preserve fast
iterability or reducibility after a seq operation.

eg: (rest (range)) uses the old seq impl.

The old seq impl has been moved into range-seq*, a private fn.

Since range is now implemented as a deftype, four existing Clojure core
usages have been moved to use a simple range1 private fn, defined simply
using sequence, take-while and iterate.

The four existing usages are:

maybe-min-hash
replace (when on vecs)
resultset-seq (old and dead)
core_deftype/build-positional-factory
  • Loading branch information
Ghadi Shayban committed Oct 26, 2014
1 parent 716a2b7 commit 906cd6e
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 29 deletions.
44 changes: 16 additions & 28 deletions src/clj/clojure/core.clj
Expand Up @@ -2876,31 +2876,17 @@
:static true}
[f x] (cons x (lazy-seq (iterate f (f x)))))

(defn range
"Returns a lazy seq of nums from start (inclusive) to end
(exclusive), by step, where start defaults to 0, step to 1, and end to
infinity. When step is equal to 0, returns an infinite sequence of
start. When start is equal to end, returns empty list."
{:added "1.0"
:static true}
([] (range 0 Double/POSITIVE_INFINITY 1))
([end] (range 0 end 1))
([start end] (range start end 1))
(defn ^:private
range1
([] (range1 0 Double/POSITIVE_INFINITY 1))
([end] (range1 0 end 1))
([start end] (range1 start end 1))
([start end step]
(lazy-seq
(let [b (chunk-buffer 32)
comp (cond (or (zero? step) (= start end)) not=
(pos? step) <
(neg? step) >)]
(loop [i start]
(if (and (< (count b) 32)
(comp i end))
(do
(chunk-append b i)
(recur (+ i step)))
(chunk-cons (chunk b)
(when (comp i end)
(range i end step)))))))))
(let [comp (cond (or (zero? step) (= start end)) not=
(pos? step) <
(neg? step) >)]
(sequence (take-while #(comp % end))
(iterate #(+ step %) start)))))

(defn merge
"Returns a map that consists of the rest of the maps conj-ed onto
Expand Down Expand Up @@ -4803,7 +4789,7 @@
(if-let [e (find smap (nth v i))]
(assoc v i (val e))
v))
coll (range (count coll)))
coll (range1 (count coll)))
(map #(if-let [e (find smap %)] (val e) %) coll))))

(defmacro dosync
Expand Down Expand Up @@ -5414,7 +5400,7 @@
{:added "1.0"}
[^java.sql.ResultSet rs]
(let [rsmeta (. rs (getMetaData))
idxs (range 1 (inc (. rsmeta (getColumnCount))))
idxs (range1 1 (inc (. rsmeta (getColumnCount))))
keys (map (comp keyword #(.toLowerCase ^String %))
(map (fn [i] (. rsmeta (getColumnLabel i))) idxs))
check-keys
Expand Down Expand Up @@ -6266,8 +6252,8 @@
(first
(filter (fn [[s m]]
(apply distinct? (map #(shift-mask s m %) hashes)))
(for [mask (map #(dec (bit-shift-left 1 %)) (range 1 (inc max-mask-bits)))
shift (range 0 31)]
(for [mask (map #(dec (bit-shift-left 1 %)) (range1 1 (inc max-mask-bits)))
shift (range1 0 31)]
[shift mask]))))

(defn- case-map
Expand Down Expand Up @@ -7392,3 +7378,5 @@
(catch Throwable t
(.printStackTrace t)
(throw t)))

(load "core_range")
2 changes: 1 addition & 1 deletion src/clj/clojure/core_deftype.clj
Expand Up @@ -268,7 +268,7 @@
`(if (= (count ~'overage) ~over-count)
(new ~classname
~@field-args
~@(for [i (range 0 (count over))]
~@(for [i (range1 0 (count over))]
(list `nth 'overage i)))
(throw (clojure.lang.ArityException. (+ ~arg-count (count ~'overage)) (name '~fn-name))))
`(new ~classname ~@field-args)))))
Expand Down
223 changes: 223 additions & 0 deletions src/clj/clojure/core_range.clj
@@ -0,0 +1,223 @@
(in-ns 'clojure.core)

(deftype RangeIterator_L [^long ^:unsynchronized-mutable i
^long end
^long step]
java.util.Iterator
(hasNext [_]
(if (pos? step)
(< i end)
(> i end)))
(next [_]
(let [ret i]
(set! i (+ i step))
ret)))

(declare range-seq*)

(deftype Range_L [^long start
^long end
^long step
_meta
^int ^:unsynchronized-mutable ^:transient _hash
^int ^:unsynchronized-mutable ^:transient _hasheq]
clojure.lang.IPersistentCollection
(cons [this o]
(clojure.lang.Cons. o this))
(empty [_]
(with-meta () _meta))
(equiv [this o]
(.equals this o))
clojure.lang.Seqable
(seq [_]
(seq (range-seq* start end step)))
clojure.lang.ISeq
(first [this]
(clojure.core/first (seq this)))
(more [this]
(clojure.core/rest (seq this)))
(next [this]
(clojure.core/next (seq this)))
java.util.List
(add [_ o]
(throw (UnsupportedOperationException.)))
(add [_ i o]
(throw (UnsupportedOperationException.)))
(addAll [_ c]
(throw (UnsupportedOperationException.)))
(addAll [_ i c]
(throw (UnsupportedOperationException.)))
(clear [_]
(throw (UnsupportedOperationException.)))
(contains [this o]
(let [iter (.iterator this)]
(loop []
(if (.hasNext iter)
(if (clojure.lang.Util/equiv (.next iter) o)
true
(recur))
false))))
(containsAll [this c]
(let [iter (.iterator c)]
(loop []
(if (.hasNext iter)
(if (.contains this (.next iter))
(recur)
false)
true))))
(equals [this obj]
(if (or (instance? clojure.lang.Sequential obj)
(instance? java.util.List obj))
(= (seq this) (seq obj))
false))

(get [this i]
(.nth this i))
(indexOf [_ o]) ;; TODO
(lastIndexOf [_ o]) ;; TODO
(isEmpty [this]
(boolean (seq this)))

(listIterator [this idx]
(.listIterator (java.util.Collections/unmodifiableList this) idx))
(listIterator [this]
(.listIterator (java.util.Collections/unmodifiableList this)))
(remove [_ ^int i]
(throw (UnsupportedOperationException.)))
(^boolean remove [_ o]
(throw (UnsupportedOperationException.)))
(removeAll [_ c]
(throw (UnsupportedOperationException.)))
(retainAll [_ c]
(throw (UnsupportedOperationException.)))
(set [_ i o]
(throw (UnsupportedOperationException.)))
(size [this] (count this))
(subList [this from to]
(.subList (java.util.Collections/unmodifiableList this) from to))
(toArray [this]
(clojure.lang.RT/seqToArray this)) ;; seqToArray counts length inefficiently
(toArray [this arr]
(clojure.lang.RT/seqToPassedArray this arr))

Iterable
(iterator [_]
(RangeIterator_L. start end step))
clojure.lang.IHashEq
(hasheq [this]
(when (= _hasheq -1)
(set! _hasheq (clojure.lang.Murmur3/hashOrdered this)))
_hasheq)
clojure.lang.Counted
(count [this]
(if-not (seq this)
0
(Math/ceil (/ (- end start) step))))
clojure.lang.Indexed
(nth [this i]
(if (< i (count this))
(+ start (* step i))
(if (and (zero? step)
(> start end))
start
(throw (IndexOutOfBoundsException.)))))
(nth [this i not-found]
(if (< i (count this))
(+ start (* step i))
(if (and (zero? step)
(> start end))
start
not-found)))
clojure.core.protocols/CollReduce
(coll-reduce [this f]
(.reduce this f))
(coll-reduce [this f init]
(.reduce this f init))
clojure.lang.IReduce
(reduce [this f]
(if (pos? step) ;; handle zero step?
(loop [acc (Long/valueOf start) i (+ start step)]
(if (< i end)
(let [ret (f acc i)]
(if (reduced? ret)
@ret
(recur ret (+ i step))))
acc))
(loop [acc (Long/valueOf start) i (+ start step)]
(if (> i end)
(let [ret (f acc i)]
(if (reduced? ret)
@ret
(recur ret (+ i step))))
acc))))
clojure.lang.IReduceInit
(reduce [_ f init]
(if (pos? step)
(loop [acc init i start]
(if (< i end)
(let [ret (f acc i)]
(if (reduced? ret)
@ret
(recur ret (+ i step))))
acc))
(loop [acc init i start]
(if (> i end)
(let [ret (f acc i)]
(if (reduced? ret)
@ret
(recur ret (+ i step))))
acc))))
clojure.lang.IObj
(withMeta [this m]
(if (identical? _meta m)
this
(Range_L. start end step m _hash _hasheq)))
clojure.lang.IMeta
(meta [_] _meta)
Object
(hashCode [this]
(when (== _hash -1)
(let [iter (.iterator this)
hc (loop [hc 1]
(if (.hasNext iter)
(recur (unchecked-add-int (unchecked-multiply-int 31 hc)
(.hashCode (.next iter))))
hc))]
(set! _hash (int hc)))) ;; TODO why must cast?
_hash)
java.io.Serializable
clojure.lang.Sequential)

(defn ^:private range-seq*
[start end step]
(lazy-seq
(let [b (chunk-buffer 32)
comp (cond (or (zero? step) (= start end)) not=
(pos? step) <
(neg? step) >)]
(loop [i start]
(if (and (< (count b) 32)
(comp i end))
(do
(chunk-append b i)
(recur (+ i step)))
(chunk-cons (chunk b)
(when (comp i end)
(range-seq* i end step))))))))

(defn range
"Returns a lazy seq of nums from start (inclusive) to end
(exclusive), by step, where start defaults to 0, step to 1, and end to
infinity. When step is equal to 0, returns an infinite sequence of
start. When start is equal to end, returns empty list."
{:added "1.0"
"static" true}
([] (range 0 Long/MAX_VALUE 1))
([end] (range 0 end 1))
([start end] (range start end 1))
([start end step]
(if (and (instance? Long start)
(instance? Long end)
(instance? Long step))
(Range_L. start end step nil -1 -1)
(range-seq* start end step))))

0 comments on commit 906cd6e

Please sign in to comment.