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).  This macro also takes in a get-truthy-fn as its first
argument, which is a function that marks whether that item in the
sequence should be included in a subsequence.  This is necessary
because the predicate function can now be of any arbitrary form, so
we cannot make any assumption about how the user intends for that
result to be interpreted as a "filter".

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

TODO: figure out how to make predicate function handle an open-ended subsequence (ex: end marker not yet seen)
This commit is contained in:
Jeff Evans 2020-08-07 16:23:33 -05:00
parent a379893598
commit 0ddebad851
5 changed files with 80 additions and 9 deletions

View file

@ -7,6 +7,5 @@ lein do clean, test
# Running ClojureScript tests # Running ClojureScript tests
``` ```
lein javac lein do clean, javac, test-cljs
lein doo node test-build once
``` ```

View file

@ -40,4 +40,5 @@
[["clojars" {:url "https://repo.clojars.org" [["clojars" {:url "https://repo.clojars.org"
:sign-releases false}]] :sign-releases false}]]
:aliases {"deploy" ["do" "clean," "deploy" "clojars"]}) :aliases {"deploy" ["do" "clean," "deploy" "clojars"]
"test-cljs" ["do" "doo" "node" "test-build" "once"]})

View file

@ -507,6 +507,11 @@
(defmacro end-fn [& args] (defmacro end-fn [& args]
`(n/->SrangeEndFunction (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 (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 continuous-subseqs
[pred] [pred]
(select* [this structure next-fn] (select* [this structure next-fn]

View file

@ -546,8 +546,38 @@
res 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] (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] (defn matching-ranges [aseq p]
(first (first

View file

@ -11,7 +11,7 @@
select-any selected-any? collected? traverse select-any selected-any? collected? traverse
multi-transform path dynamicnav recursive-path multi-transform path dynamicnav recursive-path
defdynamicnav traverse-all satisfies-protpath? end-fn defdynamicnav traverse-all satisfies-protpath? end-fn
vtransform]])) subseq-pred-fn vtransform]]))
(:use (:use
#?(:clj [clojure.test :only [deftest is]]) #?(:clj [clojure.test :only [deftest is]])
#?(:clj [clojure.test.check.clojure-test :only [defspec]]) #?(:clj [clojure.test.check.clojure-test :only [defspec]])
@ -23,7 +23,7 @@
select-any selected-any? collected? traverse select-any selected-any? collected? traverse
multi-transform path dynamicnav recursive-path multi-transform path dynamicnav recursive-path
defdynamicnav traverse-all satisfies-protpath? end-fn defdynamicnav traverse-all satisfies-protpath? end-fn
vtransform]])) subseq-pred-fn vtransform]]))
@ -949,6 +949,23 @@
(= (setval (s/continuous-subseqs pred) nil aseq) (= (setval (s/continuous-subseqs pred) nil aseq)
(filter (complement pred) 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 (deftest continuous-subseqs-test
(is (= [1 "ab" 2 3 "c" 4 "def"] (is (= [1 "ab" 2 3 "c" 4 "def"]
@ -960,7 +977,19 @@
(is (= [[] [2] [4 6]] (is (= [[] [2] [4 6]]
(select (select
[(s/continuous-subseqs number?) (s/filterer even?)] [(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/comp-paths
(s/srange-dynamic (s/srange-dynamic
(fn [aseq] (long (/ (count aseq) 2))) (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 s/FIRST
)) ))