diff --git a/src/challenge/discrete_value_range.clj b/src/challenge/discrete_value_range.clj index a8dc9c0..dc6d1d9 100644 --- a/src/challenge/discrete_value_range.clj +++ b/src/challenge/discrete_value_range.clj @@ -52,3 +52,48 @@ :boundary-type :end :type (range-type range)}])) (sort-by (juxt :value (comp {:start 0 :end 1} :boundary-type))))) + +(defmulti ->discrete-value-range (fn [range-type _start _end] + range-type)) + +(defn combine-overlapping-ranges + "transducer to find and combine overlapping ranges by looking at + ordered range value markers. + + When it encounters a range's start boundary it pushes an entry on + the stack, and captures the value for that start boundary if the + stack was previously empty. + + When it encounters a range's end boundary value, it pops an item + from the stack, and if the stack is now empty, it uses the value + of the range boundary end and the captured start value to create + a new range object, which will contain any other range start and + end pairs encountered between the new start and end values." + [] + (fn [xf] + (let [stack (volatile! []) + start (volatile! nil)] + (fn + ([] (xf)) + ([result] (xf result)) + ([result input] + (if (reduced? input) + result + (case (:boundary-type input) + :start (do + (when-not (seq @stack) + (vreset! start (:value input))) + (vswap! stack (fnil conj []) input) + result) + :end (do + (vswap! stack pop) + (if (seq @stack) + result + (xf result (->discrete-value-range (:type input) @start (:value input)))))))))))) + +(def consolidate-ranges-xf + (comp + (combine-overlapping-ranges))) + +(defn consolidate [ranges] + (eduction consolidate-ranges-xf (ordered-range-values ranges))) diff --git a/test/challenge/discrete_value_range_test.clj b/test/challenge/discrete_value_range_test.clj index df51735..27ee6c0 100644 --- a/test/challenge/discrete_value_range_test.clj +++ b/test/challenge/discrete_value_range_test.clj @@ -31,6 +31,9 @@ (assert (<= start end)) (->IntInclusiveDiscreteValueRange start end)) +(defmethod range/->discrete-value-range :int-range-inclusive [_ start end] + (int-range-inclusive start end)) + (deftest integer-ranges-sanity-test (testing "value equality" (is (= (int-range-inclusive 0 1) @@ -54,15 +57,19 @@ (testing "end" (is (= 8 (range/end (int-range-inclusive 1 8)))))) +;; test against integer ranges for easy of expression and interpretation (deftest ordered-range-values (testing "ordered range values are sorted by value and then the range's start boundary before any end boundary" - (is (= [{:value 4 :boundary-type :start :type :int-range-inclusive} + (is (= [{:value 1 :boundary-type :start :type :int-range-inclusive} + {:value 2 :boundary-type :end :type :int-range-inclusive} + {:value 4 :boundary-type :start :type :int-range-inclusive} {:value 5 :boundary-type :start :type :int-range-inclusive} {:value 5 :boundary-type :start :type :int-range-inclusive} {:value 5 :boundary-type :end :type :int-range-inclusive} {:value 5 :boundary-type :end :type :int-range-inclusive} {:value 8 :boundary-type :end :type :int-range-inclusive}] - (#'range/ordered-range-values [(int-range-inclusive 4 5) + (#'range/ordered-range-values [(int-range-inclusive 1 2) + (int-range-inclusive 4 5) (int-range-inclusive 5 8) (int-range-inclusive 5 5)]))) (is (= [{:value 5 :boundary-type :start :type :int-range-inclusive} @@ -71,3 +78,25 @@ {:value 5 :boundary-type :end :type :int-range-inclusive}] (#'range/ordered-range-values [(int-range-inclusive 5 5) (int-range-inclusive 5 5)]))))) + +;; test against integer ranges for easy of expression and interpretation +(deftest consolidate-ranges + (testing "combines overlapping ranges" + (is (= [(int-range-inclusive 5 5)] + (range/consolidate [(int-range-inclusive 5 5) + (int-range-inclusive 5 5)]))) + (is (= [(int-range-inclusive 0 1) + (int-range-inclusive 2 7) + (int-range-inclusive 9 11)] + (range/consolidate [(int-range-inclusive 0 1) + (int-range-inclusive 2 4) + (int-range-inclusive 3 7) + (int-range-inclusive 5 5) + (int-range-inclusive 9 11) + (int-range-inclusive 5 5)]))) + (is (= [(int-range-inclusive 2 11)] + (range/consolidate [(int-range-inclusive 2 4) + (int-range-inclusive 3 7) + (int-range-inclusive 5 5) + (int-range-inclusive 6 11) + (int-range-inclusive 5 5)])))))