From e72567e465ab0ef2e2d5c76280bbe0f0d3b45750 Mon Sep 17 00:00:00 2001
From: Alan Malloy <alan@malloys.org>
Date: Thu, 10 May 2012 19:39:36 -0700
Subject: [PATCH] CLJ-993 - implement range and iterate as reducers.

New functions in core.reducers mirroring iterate and range, but working in
the reducers framework. iterate is reducible, and range is foldable.
---
 src/clj/clojure/core/reducers.clj      |  130 ++++++++++++++++++++++++++-----
 test/clojure/test_clojure/reducers.clj |   34 ++++++++
 2 files changed, 143 insertions(+), 21 deletions(-)

diff --git a/src/clj/clojure/core/reducers.clj b/src/clj/clojure/core/reducers.clj
index efe6118..f730226 100644
--- a/src/clj/clojure/core/reducers.clj
+++ b/src/clj/clojure/core/reducers.clj
@@ -13,7 +13,7 @@
       dependency info."
       :author "Rich Hickey"}
   clojure.core.reducers
-  (:refer-clojure :exclude [reduce map mapcat filter remove take take-while drop flatten])
+  (:refer-clojure :exclude [reduce map mapcat filter remove take take-while drop drop-while flatten iterate range])
   (:require [clojure.walk :as walk]))
 
 (alias 'core 'clojure.core)
@@ -129,6 +129,29 @@
       (coll-fold [_ n combinef reducef]
                  (coll-fold coll n combinef (xf reducef))))))
 
+(defn- fold-by-halves
+  "Creates an implementation of CollFold which works by halving the collection
+  until it is smaller than the requested size, and folding each subsection.
+  halving-fn will be passed as input a collection and its size (so you need not
+  recompute the size); it should return the left and right halves of the
+  collection as a pair. Those halves will normally be of the same type as the
+  parent collection, but anything foldable is sufficient."
+  [halving-fn]
+  {:coll-fold
+   (fn [coll n combinef reducef]
+     (let [size (count coll)]
+       (cond
+         (zero? size) (combinef)
+         (<= size n) (reduce reducef (combinef) coll)
+         :else
+         (let [[left right] (halving-fn coll size)
+               fc (fn [child] #(coll-fold child n combinef reducef))]
+           (fjinvoke
+            #(let [f1 (fc left)
+                   t2 (fjtask (fc right))]
+               (fjfork t2)
+               (combinef (f1) (fjjoin t2))))))))})
+
 (defn- do-curried
   [name doc meta args body]
   (let [cargs (vec (butlast args))]
@@ -254,6 +277,20 @@
               (f1 ret k v)
               ret)))))))
 
+(defcurried drop-while
+  "Skips values from the reduction of coll while (pred val) returns logical true."
+  {:added "1.5"}
+  [pred coll]
+  (reducer coll
+    (fn [f1]
+      (let [keeping? (atom false)]
+        (rfn [f1 k]
+          ([ret k v]
+             (if (or @keeping?
+                     (reset! keeping? (not (pred k v))))
+               (f1 ret k v)
+               ret)))))))
+
 ;;do not construct this directly, use cat
 (deftype Cat [cnt left right]
   clojure.lang.Counted
@@ -300,6 +337,70 @@
       :else
       (Cat. (+ (count left) (count right)) left right))))
 
+(defcurried iterate
+  "A reducible collection of [seed, (f seed), (f (f seed)), ...]"
+  {:added "1.5"}
+  [f seed]
+  (reify
+    clojure.core.protocols/CollReduce
+    (coll-reduce [this f1] (clojure.core.protocols/coll-reduce this f1 (f1)))
+    (coll-reduce [this f1 init]
+      (loop [ret (f1 init seed), seed seed]
+        (if (reduced? ret)
+          @ret
+          (let [next (f seed)]
+            (recur (f1 ret next) next)))))
+
+    clojure.lang.Seqable
+    (seq [this]
+      (seq (clojure.core/iterate f seed)))))
+
+;;do not construct this directly, use range
+(deftype Range [start end step]
+  clojure.lang.Counted
+  (count [this]
+    (int (Math/ceil (/ (- end start) step))))
+
+  clojure.lang.Seqable
+  (seq [this]
+    (seq (clojure.core/range start end step)))
+
+  clojure.core.protocols/CollReduce
+  (coll-reduce [this f1] (clojure.core.protocols/coll-reduce this f1 (f1)))
+  (coll-reduce [this f1 init]
+    (let [cmp (if (pos? step) < >)]
+      (loop [ret init, i start]
+        (if (reduced? ret)
+          @ret
+          (if (cmp i end)
+            (recur (f1 ret i) (+ i step))
+            ret)))))
+
+  ;; CollFold will be extended to this type in just a moment
+  )
+
+(extend Range
+  CollFold
+  (fold-by-halves (fn [^Range r size]
+                    (let [start (.-start r)
+                          step (.-step r)
+                          end (.-end r)
+                          split (-> (quot size 2)
+                                    (* step)
+                                    (+ start))]
+                      [(Range. start split step)
+                       (Range. split end step)]))))
+
+(defn range
+  "Returns a reducible collection of nums from start (inclusive) to end
+  (exclusive), by step, where start defaults to 0, step to 1, and end
+  to infinity."
+  {:added "1.5"}
+  ([] (iterate inc 0))
+  ([end] (Range. 0 end 1))
+  ([start end] (Range. start end 1))
+  ([start end step] (Range. start end step)))
+
 (defn append!
   ".adds x to acc and returns acc"
   {:added "1.5"}
@@ -323,21 +424,13 @@
     ([a b] (op a b))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; fold impls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defn- foldvec
-  [v n combinef reducef]
-  (cond
-   (empty? v) (combinef)
-   (<= (count v) n) (reduce reducef (combinef) v)
-   :else
-   (let [split (quot (count v) 2)
-         v1 (subvec v 0 split)
-         v2 (subvec v split (count v))
-         fc (fn [child] #(foldvec child n combinef reducef))]
-     (fjinvoke
-      #(let [f1 (fc v1)
-             t2 (fjtask (fc v2))]
-         (fjfork t2)
-         (combinef (f1) (fjjoin t2)))))))
+
+(extend clojure.lang.IPersistentVector
+  CollFold
+  (fold-by-halves (fn [v size]
+                    (let [split (quot size 2)]
+                      [(subvec v 0 split)
+                       (subvec v split size)]))))
 
 (extend-protocol CollFold
  Object
@@ -346,11 +439,6 @@
   ;;can't fold, single reduce
   (reduce reducef (combinef) coll))
 
- clojure.lang.IPersistentVector
- (coll-fold
-  [v n combinef reducef]
-  (foldvec v n combinef reducef))
-
  clojure.lang.PersistentHashMap
  (coll-fold
   [m n combinef reducef]
diff --git a/test/clojure/test_clojure/reducers.clj b/test/clojure/test_clojure/reducers.clj
index 1123c36..a2f0b14 100644
--- a/test/clojure/test_clojure/reducers.clj
+++ b/test/clojure/test_clojure/reducers.clj
@@ -39,3 +39,37 @@
 (defequivtest test-filter
   [filter r/filter #(into [] %)]
   [even? odd? #(< 200 %) identity])
+
+(defequivtest test-drop-while
+  [drop-while r/drop-while #(into [] %)]
+  [neg? pos? #(< % 200) #(> % 200) #{-100}])
+
+(deftest test-iterate
+  (is (= [100000]
+         (->> (r/iterate inc 0)
+              (r/drop 1e5)
+              (r/take 1)
+              (into [])))))
+
+(deftest test-range
+  (is (= (take 10000 (range))
+         (->> (r/range)
+              (r/take 10000)
+              (into []))))
+  (is (not (counted? (r/range))))
+  (doseq [argvec [[7000]
+                  [0 5736]
+                  [10000 21 -2]
+                  [0 3710 2/3]
+                  [1 -8642 -2]]]
+    (let [seq-version (apply range argvec)
+          reduce-version (apply r/range argvec)
+          reduced-vector (into [] reduce-version)]
+      (is (counted? reduce-version))
+      (is (= seq-version reduced-vector))
+      (is (= (count reduce-version)
+             (count reduced-vector)))
+      (let [seq-sum (reduce + seq-version)
+            folded-sum (r/fold + reduce-version)
+            vector-fold-sum (r/fold + reduced-vector)]
+        (is (= seq-sum folded-sum vector-fold-sum))))))
-- 
1.7.4.1

