Add BEFORE-ELEM, AFTER-ELEM, and NONE-ELEM navigators

This commit is contained in:
nathanmarz 2017-02-15 20:34:44 -05:00
parent 7c798c1e3b
commit b79a71decd
5 changed files with 94 additions and 21 deletions

View file

@ -7,6 +7,7 @@
* `select-first` and `select-any` now avoid traversal beyond the first value matched by the path (like when using `ALL`), so they are faster now for those use cases.
* Add `NAME` and `NAMESPACE` navigators
* Extend `srange`, `BEGINNING`, `END`, `FIRST`, and `LAST` on strings to navigate to substrings
* Add `BEFORE-ELEM`, `AFTER-ELEM`, and `NONE-ELEM` for efficiently adding a single element to a sequence or set
* Improved `ALL` performance for PersistentHashSet
* Dynamic navs automatically compile sequence returns if completely static
* Eliminate reflection warnings for clj (thanks @mpenet)

View file

@ -199,9 +199,10 @@
(let [v (vec (range 1000))]
(run-benchmark "END on large vector"
(run-benchmark "Append to a large vector"
2000000
(setval END [1] v)
(setval AFTER-ELEM 1 v)
(reduce conj v [1])
(conj v 1)))

View file

@ -726,6 +726,51 @@
(let [to-append (next-fn [])]
(n/append-all structure to-append)))))
(defnav
^{:doc "Navigate to 'void' elem in the set.
For transformations - if result is not `NONE`,
then add that value to the set."}
NONE-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(conj structure newe)
))))
(defnav
^{:doc "Navigate to 'void' element before the sequence.
For transformations if result is not `NONE`,
then prepend that value."}
BEFORE-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(n/prepend-one structure newe)
))))
(defnav
^{:doc "Navigate to 'void' element after the sequence.
For transformations if result is not `NONE`,
then append that value."}
AFTER-ELEM
[]
(select* [this structure next-fn]
(next-fn NONE))
(transform* [this structure next-fn]
(let [newe (next-fn NONE)]
(if (identical? NONE newe)
structure
(n/append-one structure newe)
))))
(defnav
^{:doc "Navigates to the specified subset (by taking an intersection).
In a transform, that subset in the original set is changed to the

View file

@ -344,7 +344,10 @@
(defprotocol AddExtremes
(append-all [structure elements])
(prepend-all [structure elements]))
(prepend-all [structure elements])
(append-one [structure elem])
(prepend-one [structure elem])
)
(extend-protocol AddExtremes
nil
@ -352,6 +355,10 @@
elements)
(prepend-all [_ elements]
elements)
(append-one [_ elem]
(list elem))
(prepend-one [_ elem]
(list elem))
#?(:clj clojure.lang.PersistentVector :cljs cljs.core/PersistentVector)
(append-all [structure elements]
@ -362,13 +369,22 @@
(reduce conj! <> elements)
(reduce conj! <> structure)
(persistent! <>))))
(append-one [structure elem]
(conj structure elem))
(prepend-one [structure elem]
(into [elem] structure))
#?(:clj Object :cljs default)
(append-all [structure elements]
(concat structure elements))
(prepend-all [structure elements]
(concat elements structure)))
(concat elements structure))
(append-one [structure elem]
(concat structure [elem]))
(prepend-one [structure elem]
(cons elem structure))
)

View file

@ -1410,25 +1410,35 @@
)
(deftest name-namespace-test
(= :a (setval s/NAME "a" :e))
(= :a/b (setval s/NAME "b" :a/e))
(= 'a (setval s/NAME "a" 'e))
(= 'a/b (setval s/NAME "b" 'a/e))
(= :a/e (setval s/NAMESPACE "a" :e))
(= :a/e (setval s/NAMESPACE "a" :f/e))
(= 'a/e (setval s/NAMESPACE "a" 'e))
(= 'a/e (setval s/NAMESPACE "a" 'f/e))
(is (= :a (setval s/NAME "a" :e)))
(is (= :a/b (setval s/NAME "b" :a/e)))
(is (= 'a (setval s/NAME "a" 'e)))
(is (= 'a/b (setval s/NAME "b" 'a/e)))
(is (= :a/e (setval s/NAMESPACE "a" :e)))
(is (= :a/e (setval s/NAMESPACE "a" :f/e)))
(is (= 'a/e (setval s/NAMESPACE "a" 'e)))
(is (= 'a/e (setval s/NAMESPACE "a" 'f/e)))
)
(deftest string-navigation-test
(= "ad" (setval (s/srange 1 4) "" "abcd"))
(= "bc" (select-any (s/srange 1 4) "abcd"))
(= "ab" (setval s/END "b" "a"))
(= "ba" (setval s/BEGINNING "b" "a"))
(= "" (select-any s/BEGINNING "abc"))
(= "" (select-any s/END "abc"))
(= \a (select-any s/FIRST "abc"))
(= \c (select-any s/LAST "abc"))
(= "qbc" (setval s/FIRST \q "abc"))
(= "abq" (setval s/FIRST "q" "abc"))
(is (= "ad" (setval (s/srange 1 3) "" "abcd")))
(is (= "bc" (select-any (s/srange 1 3) "abcd")))
(is (= "ab" (setval s/END "b" "a")))
(is (= "ba" (setval s/BEGINNING "b" "a")))
(is (= "" (select-any s/BEGINNING "abc")))
(is (= "" (select-any s/END "abc")))
(is (= \a (select-any s/FIRST "abc")))
(is (= \c (select-any s/LAST "abc")))
(is (= "qbc" (setval s/FIRST \q "abc")))
(is (= "abq" (setval s/LAST "q" "abc")))
)
(deftest single-value-none-navigators-test
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2])))
(is (predand= list? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/AFTER-ELEM 1 nil)))
(is (predand= vector? [3 1 2] (setval s/BEFORE-ELEM 3 [1 2])))
(is (predand= list? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/BEFORE-ELEM 1 nil)))
(is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2})))
)