From 98a9fd43feffacb5f49c452167b896a8cc25e241 Mon Sep 17 00:00:00 2001 From: Steven Proctor Date: Sun, 11 Jan 2026 16:20:03 -0600 Subject: [PATCH] walk sequence of range boundaries values for source and filter ranges add lower level logic for walking the boundary markers of sets but distinguish between a source range and a filter range when looking at the start and end boundary behavior --- deps.edn | 1 + src/challenge/discrete_value_range.clj | 54 ++++- test/challenge/discrete_value_range_test.clj | 209 +++++++++++++++++++ 3 files changed, 261 insertions(+), 3 deletions(-) diff --git a/deps.edn b/deps.edn index aa4bcf9..efa6d45 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,5 @@ {:deps {org.clojure/clojure {:mvn/version "1.11.2"} + org.clojure/core.match {:mvn/version "1.1.1"} org.threeten/threeten-extra {:mvn/version "1.8.0"}} :paths ["src"] :aliases {:test {:extra-paths ["test"] diff --git a/src/challenge/discrete_value_range.clj b/src/challenge/discrete_value_range.clj index e9addb5..4c54463 100644 --- a/src/challenge/discrete_value_range.clj +++ b/src/challenge/discrete_value_range.clj @@ -1,4 +1,5 @@ -(ns challenge.discrete-value-range) +(ns challenge.discrete-value-range + (:require [clojure.core.match :refer [match]])) (defprotocol DiscreteValueRange "A protocol for generic behavior on Ranges over @@ -29,7 +30,7 @@ (start [this]) (end [this]) (range-type [this]) - (union [range1 range2])) + (union [this other])) (defn- ordered-range-values "Builds an ordered list or 'fenceposts' for the start and end @@ -129,4 +130,51 @@ (combine-abutting-ranges))) (defn consolidate [ranges] - (eduction consolidate-ranges-xf (ordered-range-values ranges))) + (into #{} consolidate-ranges-xf (ordered-range-values ranges))) + +(defn walk-range-boundaries [range-boundary-items] + (let [close-working-range (fn [range-type start end ranges] + (conj ranges (->discrete-value-range range-type + start + end)))] + + (loop [[boundary-item & boundary-items] range-boundary-items + in-filter-range nil + in-source-range nil + ranges #{} + close-fn nil] + (match [boundary-item] + [nil] + ranges + + [{:boundary-type :start + :range-source-type :source-range + :type range-type + :value new-working-range-start-value}] + (recur boundary-items in-filter-range true ranges (partial close-working-range + range-type + new-working-range-start-value)) + + [{:boundary-type :end + :range-source-type :source-range + :value value}] + (if in-filter-range + (recur boundary-items in-filter-range false ranges nil) + (recur boundary-items in-filter-range false (close-fn value ranges) nil)) + + [{:boundary-type :start + :range-source-type :filter-range + :prev-value prev-value}] + (if close-fn + (recur boundary-items true in-source-range (close-fn prev-value ranges) nil) + (recur boundary-items true in-source-range ranges close-fn)) + + [{:boundary-type :end + :range-source-type :filter-range + :type range-type + :next-value new-working-range-start-value}] + (if in-source-range + (recur boundary-items false in-source-range ranges (partial close-working-range + range-type + new-working-range-start-value)) + (recur boundary-items false in-source-range ranges close-fn)))))) diff --git a/test/challenge/discrete_value_range_test.clj b/test/challenge/discrete_value_range_test.clj index 5d42c0c..3b18a50 100644 --- a/test/challenge/discrete_value_range_test.clj +++ b/test/challenge/discrete_value_range_test.clj @@ -128,3 +128,212 @@ (int-range-inclusive 5 5) (int-range-inclusive 13 17) (int-range-inclusive 5 5)]))))) + +(deftest walk-range-boundaries-test + (testing "only filter-ranges" + (is (= #{} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :filter-range + :value 1 + :prev-value 0 + :next-value 2 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :value 4 + :prev-value 3 + :next-value 5 + :type :int-range-inclusive}])))) + (testing "only source-ranges" + (is (= #{(int-range-inclusive 1 5)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 5 + :type :int-range-inclusive}]))) + (is (= #{(int-range-inclusive 1 5) + (int-range-inclusive 11 15)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 5 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :source-range + :value 11 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 15 + :type :int-range-inclusive}])))) + (testing "filter-ranges before source-ranges" + (is (= #{(int-range-inclusive 11 15)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :filter-range + :value 1 + :prev-value 0 + :next-value 2 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 4 + :value 5 + :next-value 6 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :source-range + :value 11 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 15 + :type :int-range-inclusive}])))) + (testing "filter-ranges after source-ranges" + (is (= #{(int-range-inclusive 1 5)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 5 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :filter-range + :prev-value 10 + :value 11 + :next-value 12 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 14 + :value 15 + :next-value 16 + :type :int-range-inclusive}])))) + (testing "single filter-range between source-range" + (is (= #{(int-range-inclusive 1 5) + (int-range-inclusive 11 20)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :filter-range + :prev-value 5 + :value 6 + :next-value 7 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 10 + :value 10 + :next-value 11 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 20 + :type :int-range-inclusive}])))) + (testing "multiple filter-ranges between source-range" + (is (= #{(int-range-inclusive 1 5) + (int-range-inclusive 11 12) + (int-range-inclusive 20 20)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :filter-range + :prev-value 5 + :value 6 + :next-value 7 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 10 + :value 10 + :next-value 11 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :filter-range + :prev-value 12 + :value 13 + :next-value 14 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 18 + :value 19 + :next-value 20 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 20 + :type :int-range-inclusive}])))) + (testing "source range between filter-range" + (is (= #{} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :filter-range + :prev-value 1 + :value 1 + :next-value 2 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :source-range + :value 6 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 10 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :source-range + :value 13 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 19 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 19 + :value 20 + :next-value 21 + :type :int-range-inclusive}])))) + (testing "filter range overlaps source ranges" + (is (= #{(int-range-inclusive 1 4) + (int-range-inclusive 11 13)} + (range/walk-range-boundaries [{:boundary-type :start + :range-source-type :source-range + :value 1 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :filter-range + :prev-value 4 + :value 5 + :next-value 6 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 6 + :type :int-range-inclusive} + {:boundary-type :start + :range-source-type :source-range + :value 8 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :filter-range + :prev-value 9 + :value 10 + :next-value 11 + :type :int-range-inclusive} + {:boundary-type :end + :range-source-type :source-range + :value 13 + :type :int-range-inclusive}]))))) +