From d0c78810f0ade6f9466881ef325a9a120c112024 Mon Sep 17 00:00:00 2001 From: Steven Proctor Date: Sat, 10 Jan 2026 18:28:24 -0600 Subject: [PATCH] add transducer step to combine abutting ranges ranges have previously been consolidated by having overlapping ranges be combined, resuting in a (possibly) consolidated set of ranges, which are now ordered by start and end dates. Add a transducer step to walk those ranges and find any pairs of ranges where the pair of ranges abutt each other, and combine them into a single range. --- src/challenge/discrete_value_range.clj | 39 ++++++++++++++++++-- test/challenge/discrete_value_range_test.clj | 35 +++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/challenge/discrete_value_range.clj b/src/challenge/discrete_value_range.clj index dc6d1d9..e9addb5 100644 --- a/src/challenge/discrete_value_range.clj +++ b/src/challenge/discrete_value_range.clj @@ -28,7 +28,8 @@ (value-after [this]) (start [this]) (end [this]) - (range-type [this])) + (range-type [this]) + (union [range1 range2])) (defn- ordered-range-values "Builds an ordered list or 'fenceposts' for the start and end @@ -56,7 +57,7 @@ (defmulti ->discrete-value-range (fn [range-type _start _end] range-type)) -(defn combine-overlapping-ranges +(defn- combine-overlapping-ranges "transducer to find and combine overlapping ranges by looking at ordered range value markers. @@ -91,9 +92,41 @@ result (xf result (->discrete-value-range (:type input) @start (:value input)))))))))))) +(defn- combine-abutting-ranges + "transducer to join ranges where the start and end of two ranges are consective + discrete values. + + Assumes the ranges are already in order and any overlapping ranges have already been + combined." + [] + (fn [xf] + (let [prev (volatile! nil)] + (fn + ([] (xf)) + ([result] (if @prev + (xf (xf result @prev)) + (xf result))) + ([result input] + (cond + (reduced? input) result + + (nil? @prev) (do + (vreset! prev input) + result) + + (abuts @prev input) + (let [item (union @prev input)] + (vreset! prev item) + result) + + :else (let [item @prev] + (vreset! prev input) + (xf result item)))))))) + (def consolidate-ranges-xf (comp - (combine-overlapping-ranges))) + (combine-overlapping-ranges) + (combine-abutting-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 27ee6c0..3e3badc 100644 --- a/test/challenge/discrete_value_range_test.clj +++ b/test/challenge/discrete_value_range_test.clj @@ -19,6 +19,13 @@ end) (range-type [_this] :int-range-inclusive) + (union [this other] + (when-not (range/abuts this other) + (throw (ex-info "Cannot union non-abutting ranges" {}))) + + (range/->discrete-value-range (.range-type this) + (min start (.start other)) + (max end (.end other)))) Object (toString [_this] @@ -86,10 +93,10 @@ (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 3 7) (int-range-inclusive 9 11)] (range/consolidate [(int-range-inclusive 0 1) - (int-range-inclusive 2 4) + (int-range-inclusive 3 4) (int-range-inclusive 3 7) (int-range-inclusive 5 5) (int-range-inclusive 9 11) @@ -99,4 +106,28 @@ (int-range-inclusive 3 7) (int-range-inclusive 5 5) (int-range-inclusive 6 11) + (int-range-inclusive 5 5)])))) + + (testing "conjoins abutting ranges" + (is (= [(int-range-inclusive 0 9)] + (range/consolidate [(int-range-inclusive 0 1) + (int-range-inclusive 2 4) + (int-range-inclusive 5 5) + (int-range-inclusive 6 9) + (int-range-inclusive 5 5)]))) + (is (= [(int-range-inclusive 0 9)] + (range/consolidate [(int-range-inclusive 0 1) + (int-range-inclusive 2 3) + (int-range-inclusive 4 5) + (int-range-inclusive 6 9) + (int-range-inclusive 5 5)])))) + + (testing "combines overlapping ranges and conjoins abutting ranges" + (is (= [(int-range-inclusive 0 7) + (int-range-inclusive 13 17)] + (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 13 17) (int-range-inclusive 5 5)])))))