diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index 8d763262..3389b325 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -471,6 +471,9 @@ (defmacro end-fn [& args] `(n/->SrangeEndFunction (fn ~@args))) + (defmacro subseq-pred-fn [& args] + `(i/->SubseqsDynamicPredFn (i/wrap-pred-with-index (fn ~@args)))) + )) diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index ede71e21..403bc179 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -560,8 +560,37 @@ 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 [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)] + (map last (filter (comp true? 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 740bf3e3..bce79236 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -960,7 +960,26 @@ (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"]))) + (defn- make-bounds-pred-fn [start end] + (s/subseq-pred-fn [elem prev] + (cond + (identical? start elem) start + (identical? end elem) end + (identical? end prev) false + :else (or (identical? start prev) prev) + ))) + (is (= [[1 2 3] [8 9]] + (select + [(s/continuous-subseqs (make-bounds-pred-fn :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 13 14] + (transform + (s/continuous-subseqs (make-bounds-pred-fn :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]))) + )