diff --git a/DEVELOPER.md b/DEVELOPER.md index 0752ba7..072438b 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -7,6 +7,5 @@ lein do clean, test # Running ClojureScript tests ``` -lein javac -lein doo node test-build once +lein do clean, javac, test-cljs ``` diff --git a/project.clj b/project.clj index f1d16b4..9a3e01c 100644 --- a/project.clj +++ b/project.clj @@ -40,4 +40,5 @@ [["clojars" {:url "https://repo.clojars.org" :sign-releases false}]] - :aliases {"deploy" ["do" "clean," "deploy" "clojars"]}) + :aliases {"deploy" ["do" "clean," "deploy" "clojars"] + "test-cljs" ["do" "doo" "node" "test-build" "once"]}) diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index e031ed3..3a29395 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -507,6 +507,11 @@ (defmacro end-fn [& args] `(n/->SrangeEndFunction (fn ~@args))) + (defmacro subseq-pred-fn + "Used in conjunction with `continuous-subseqs`. See [[continuous-subseqs]]." + [get-truthy-fn & args] + `(i/->SubseqsDynamicPredFn ~get-truthy-fn (i/wrap-pred-with-index (fn ~@args)))) + )) @@ -800,7 +805,14 @@ (defnav - ^{:doc "Navigates to every continuous subsequence of elements matching `pred`"} + ^{:doc "Navigates to every continuous subsequence of elements matching `pred`. `pred` can be specified one of two + forms. If a regular function (e.g. defined with `fn`), it takes in only the current element as input. If + defined using the special `subseq-pred-fn` macro, it takes in a `get-truthy-fn` as its first parameter, + followed by arguments to a predicate function [`elem` `prev`], followed by the predicate function body. The + `elem` argument to the predicate function is the current element, and the `pred` argument is the value + returned by your predicate on the previous element, so it can be in any structure you choose. `get-truthy-fn` + is a function that should return true from your predicate's return structure if that element should be + included in a subsequence."} continuous-subseqs [pred] (select* [this structure next-fn] diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 2493271..cfc12dd 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -546,8 +546,38 @@ res )))) +(defn wrap-pred-with-index [pred] + (fn [i elem prev] + [(pred elem (first prev)), i])) + +;; adapted from clojure.core$keep_indexed +(defn- subseq-pred-fn-transducer + ([pred-fn] + (fn [rf] + (let [last-val (volatile! nil) idx (volatile! -1)] + (fn + ([] (rf)) ;; init arity + ([result] (rf result)) ;; completion arity + ([result input] ;; reduction arity + (let [last @last-val + i (vswap! idx inc) + curr ((:pred-fn pred-fn) i input last)] + (vreset! last-val curr) + (if (nil? curr) + result + (rf result curr))))))))) + +;; see com.rpl.specter.navs.SrangeEndFunction +(defrecord SubseqsDynamicPredFn [get-truthy-fn pred-fn]) + (defn- matching-indices [aseq p] - (keep-indexed (fn [i e] (if (p e) i)) aseq)) + (if (instance? SubseqsDynamicPredFn p) + ;; use new subseq predicate form (taking current and previous vals) + (let [index-results (into [] (subseq-pred-fn-transducer p) aseq)] + ;; apply the get-truthy-fn to extract the truthy (i.e. include) result + (map last (filter (comp true? (:get-truthy-fn p) first) index-results))) + ;; else use the previous 1-arity predicate + (keep-indexed (fn [i e] (if (p e) i)) aseq))) (defn matching-ranges [aseq p] (first diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index 1ba123c..8a78d33 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -11,7 +11,7 @@ select-any selected-any? collected? traverse multi-transform path dynamicnav recursive-path defdynamicnav traverse-all satisfies-protpath? end-fn - vtransform]])) + subseq-pred-fn vtransform]])) (:use #?(:clj [clojure.test :only [deftest is]]) #?(:clj [clojure.test.check.clojure-test :only [defspec]]) @@ -23,7 +23,7 @@ select-any selected-any? collected? traverse multi-transform path dynamicnav recursive-path defdynamicnav traverse-all satisfies-protpath? end-fn - vtransform]])) + subseq-pred-fn vtransform]])) @@ -949,6 +949,23 @@ (= (setval (s/continuous-subseqs pred) nil aseq) (filter (complement pred) aseq)))) +(defn- make-bounds-pred-fn-vecs [start end] + (s/subseq-pred-fn first [elem prev] + (let [[included last] prev] + (cond + (= start elem) [false start] + (= end elem) [false end] + (= end last) [false elem] + :else [(or (= start last) included) elem])))) + +(defn- make-bounds-pred-fn-maps [start end] + (s/subseq-pred-fn :include [elem prev] + (let [{include :include last :last} prev] + (cond + (= start elem) {:include false :last start} + (= end elem) {:include false :last end} + (= end last) {:include false :last elem} + :else {:include (or (= start last) include) :last elem})))) (deftest continuous-subseqs-test (is (= [1 "ab" 2 3 "c" 4 "def"] @@ -960,7 +977,19 @@ (is (= [[] [2] [4 6]] (select [(s/continuous-subseqs number?) (s/filterer even?)] - [1 "a" "b" 2 3 "c" 4 5 6 "d" "e" "f"])))) + [1 "a" "b" 2 3 "c" 4 5 6 "d" "e" "f"]))) + (is (= [[1 2 3] [8 9]] + (select + [(s/continuous-subseqs (make-bounds-pred-fn-vecs :START :END))] + [:START 1 2 3 :END 5 6 7 :START 8 9 :END]))) + + (is (= [1 2 3 :START-SUM 15 :END-SUM 7 8 9 :START-SUM 21 :END-SUM 12 :START-SUM 27 :END-SUM] + (transform + (s/continuous-subseqs (make-bounds-pred-fn-maps :START-SUM :END-SUM)) + (fn [vals] [(apply + vals)]) + [1 2 3 :START-SUM 4 5 6 :END-SUM 7 8 9 :START-SUM 10 11 :END-SUM 12 :START-SUM 13 14 :END-SUM]))) + ) + @@ -1589,7 +1618,7 @@ (s/comp-paths (s/srange-dynamic (fn [aseq] (long (/ (count aseq) 2))) - (end-fn [aseq s] (if (empty? aseq) 0 (inc s)))) + (s/end-fn [aseq s] (if (empty? aseq) 0 (inc s)))) s/FIRST ))