From 7abc71bbed040ca3a8dc232dc7e5e3d0bccb408a Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Mon, 23 Aug 2021 08:57:09 -0500 Subject: [PATCH 1/5] Add implementation for three new sorting navs --- src/clj/com/rpl/specter.cljc | 58 +++++++++++++++++++++++++++++++ src/clj/com/rpl/specter/impl.cljc | 10 ++++++ src/clj/com/rpl/specter/navs.cljc | 5 +++ 3 files changed, 73 insertions(+) diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index e031ed3..2894235 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -1504,3 +1504,61 @@ [& path] (map compact* path) )) + +(defnav + ^{:doc "Navigates to a sequence resulting from (sort ...), but is a + view to the original structure that can be transformed. + + If the transformed sequence is smaller than the input sequence, values + which are included are sorted by the same indices as the input value's + index in the input sequence. + + If the transformed sequence is larger than the input sequence, values + added to the end of the sequence will be appended to the end of the + original sequence."} + SORTED + [] + (select* [this structure next-fn] + (n/sorted-select structure identity compare next-fn)) + (transform* [this structure next-fn] + (n/sorted-transform structure identity compare next-fn))) + +(defnav + ^{:doc "Navigates to a sequence resulting from (sort comparator ...), but + is a view to the original structure that can be transformed. + + If the transformed sequence is smaller than the input sequence, values + which are included are sorted by the same indices as the input value's + index in the input sequence. + + If the transformed sequence is larger than the input sequence, values + added to the end of the sequence will be appended to the end of the + original sequence."} + sorted + [comparator] + (select* [this structure next-fn] + (n/sorted-select structure identity comparator next-fn)) + (transform* [this structure next-fn] + (n/sorted-transform structure identity comparator next-fn))) + +(defdynamicnav sorted-by + "Navigates to a sequence sorted by the value stored in the keypath, by the + comparator, if one is provided. + + This sequence is a view to the original structure that can be transformed. If + the transformed sequence is smaller than the input sequence, values which are + included are sorted by the same indices as the input value's index in the + input sequence. + + If the transformed sequence is larger than the input sequence, values added to + the end of the sequence will be appended to the end of the original sequence. + + Value collection (e.g. collect, collect-one) may not be used in the keypath." + ([keypath] (sorted-by keypath compare)) + ([keypath comparator] + (late-bound-nav [late (late-path keypath) + late-fn comparator] + (select* [this structure next-fn] + (n/sorted-select structure #(compiled-select late %) late-fn next-fn)) + (transform* [this structure next-fn] + (n/sorted-transform structure #(compiled-select late %) late-fn next-fn))))) diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 2493271..7a21b78 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -546,6 +546,16 @@ res )))) +(defn sorted-transform* + [structure keyfn comparator next-fn] + (let [sorted (sort-by (comp keyfn second) comparator (map-indexed vector structure)) + indices (map first sorted) + result (next-fn (map second sorted)) + unsorted (sort-by first compare (map vector (concat indices (repeat ##Inf)) result))] + (into (empty structure) + (map second) + unsorted))) + (defn- matching-indices [aseq p] (keep-indexed (fn [i e] (if (p e) i)) aseq)) diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index 6465432..2a43338 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -381,6 +381,11 @@ (def srange-transform i/srange-transform*) +(defn sorted-select + [structure keyfn comparator next-fn] + (next-fn (sort-by keyfn comparator structure))) + +(def sorted-transform i/sorted-transform*) (defn extract-basic-filter-fn [path] (cond (fn? path) From c309fdbeeff0dddc39d54ef3c5ec2efb2a3cfb62 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Mon, 23 Aug 2021 08:57:21 -0500 Subject: [PATCH 2/5] Update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 5d2f489..d0ec77d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ## 1.1.4-SNAPSHOT +* Add SORTED, sorted, and sorted-by navs (thanks @IGJoshua) * Add arglist metadata to navs (thanks @phronmophobic) * Improve before-index performance by 150x on lists and 5x on vectors (thanks @jeff303) * Bug fix: BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs From 499d1b63c0223a2d20184eae7897245c36b7a8ee Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Thu, 26 Aug 2021 13:21:19 -0500 Subject: [PATCH 3/5] Add tests to include the three new navigators --- test/com/rpl/specter/core_test.cljc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index 1ba123c..4adc0b0 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -1,6 +1,6 @@ (ns com.rpl.specter.core-test #?(:cljs (:require-macros - [cljs.test :refer [is deftest]] + [cljs.test :refer [is deftest testing]] [clojure.test.check.clojure-test :refer [defspec]] [com.rpl.specter.cljs-test-helpers :refer [for-all+]] [com.rpl.specter.test-helpers :refer [ic-test]] @@ -13,7 +13,7 @@ defdynamicnav traverse-all satisfies-protpath? end-fn vtransform]])) (:use - #?(:clj [clojure.test :only [deftest is]]) + #?(:clj [clojure.test :only [deftest is testing]]) #?(:clj [clojure.test.check.clojure-test :only [defspec]]) #?(:clj [com.rpl.specter.test-helpers :only [for-all+ ic-test]]) #?(:clj [com.rpl.specter @@ -1702,3 +1702,21 @@ (is (satisfies-protpath? FooPP "a")) (is (not (satisfies-protpath? FooPP 1))) ))) + +(deftest sorted-test + (let [initial-list [3 4 2 1]] + (testing "the SORTED navigator" + (is (= (sort initial-list) (select-one s/SORTED initial-list))) + (is (= [2 1 3 4] (transform s/SORTED reverse initial-list))) + (is (= [3 2 1] (transform s/SORTED butlast initial-list))) + (is (= [3 5 2 1] (setval [s/SORTED s/LAST] 5 initial-list)))) + (testing "the sorted navigator with comparator" + (let [reverse-comparator (comp - compare)] + (is (= (sort reverse-comparator initial-list) + (select-one (s/sorted reverse-comparator) initial-list))) + (is (= 4 (select-one [(s/sorted reverse-comparator) s/FIRST] initial-list)))))) + (testing "the sorted-by navigator with keypath" + (let [initial-list [{:a 3} {:a 4} {:a 2} {:a 1}]] + (is (= (sort-by :a initial-list) + (select-one (s/sorted-by :a) initial-list))) + (is (= {:a 4} (select-one [(s/sorted-by :a (comp - compare)) s/FIRST] initial-list)))))) From 6063e1a5c1fb244ea322f8c5cfb82b253ee11fbc Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Fri, 27 Aug 2021 16:48:33 -0500 Subject: [PATCH 4/5] Ensure that sequences are not reordered during transform --- src/clj/com/rpl/specter/impl.cljc | 10 ++++++---- test/com/rpl/specter/core_test.cljc | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 7a21b78..cc0afe9 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -551,10 +551,12 @@ (let [sorted (sort-by (comp keyfn second) comparator (map-indexed vector structure)) indices (map first sorted) result (next-fn (map second sorted)) - unsorted (sort-by first compare (map vector (concat indices (repeat ##Inf)) result))] - (into (empty structure) - (map second) - unsorted))) + unsorted (sort-by first compare (map vector indices result))] + (if (seq? structure) + (doall (map second unsorted)) + (into (empty structure) + (map second) + unsorted)))) (defn- matching-indices [aseq p] (keep-indexed (fn [i e] (if (p e) i)) aseq)) diff --git a/test/com/rpl/specter/core_test.cljc b/test/com/rpl/specter/core_test.cljc index 4adc0b0..957260c 100644 --- a/test/com/rpl/specter/core_test.cljc +++ b/test/com/rpl/specter/core_test.cljc @@ -1709,7 +1709,8 @@ (is (= (sort initial-list) (select-one s/SORTED initial-list))) (is (= [2 1 3 4] (transform s/SORTED reverse initial-list))) (is (= [3 2 1] (transform s/SORTED butlast initial-list))) - (is (= [3 5 2 1] (setval [s/SORTED s/LAST] 5 initial-list)))) + (is (= [3 5 2 1] (setval [s/SORTED s/LAST] 5 initial-list))) + (is (= (list 1 2 3 4 5) (transform [s/SORTED s/ALL] inc (range 5))))) (testing "the sorted navigator with comparator" (let [reverse-comparator (comp - compare)] (is (= (sort reverse-comparator initial-list) From 79f93e57d615660894386a911c1cc700ddc27d81 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Fri, 27 Aug 2021 16:48:54 -0500 Subject: [PATCH 5/5] Update sorted-by to use the correct selection macro --- src/clj/com/rpl/specter.cljc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index 2894235..45e6c5e 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -1559,6 +1559,6 @@ (late-bound-nav [late (late-path keypath) late-fn comparator] (select* [this structure next-fn] - (n/sorted-select structure #(compiled-select late %) late-fn next-fn)) + (n/sorted-select structure #(compiled-select-one! late %) late-fn next-fn)) (transform* [this structure next-fn] - (n/sorted-transform structure #(compiled-select late %) late-fn next-fn))))) + (n/sorted-transform structure #(compiled-select-one! late %) late-fn next-fn)))))