From 6191b5bab73acedfc868dde986d5e790b44881c6 Mon Sep 17 00:00:00 2001 From: Jeff Evans Date: Fri, 7 Aug 2020 16:23:33 -0500 Subject: [PATCH] #236 - Generalize continuous-subseqs Adding new subseq-pred-fn macro to create the new form of predicate function (taking the previous and current item), to preserve backward compatibility (still allowing predicate functions that only take the current item) Adding SubseqsDynamicPredFn, which works the same as SrangeEndFn, to support backward compatibility Adding wrapper to take a predicate on [prev current] and turn it into a predicate also taking the current index as the first param Creating transducer to combine this with the user-supplied predicate function Adding tests for select and transform WORK IN PROGRESS TODO: figure out how to make predicate function handle an open-ended subsequence (ex: end marker not yet seen) --- src/clj/com/rpl/specter.cljc | 3 +++ src/clj/com/rpl/specter/impl.cljc | 31 ++++++++++++++++++++++++++++- test/com/rpl/specter/core_test.cljc | 21 ++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) 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]))) + )