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.
This commit is contained in:
2026-01-10 18:28:24 -06:00
parent 98761a79ea
commit d0c78810f0
2 changed files with 69 additions and 5 deletions

View File

@@ -28,7 +28,8 @@
(value-after [this]) (value-after [this])
(start [this]) (start [this])
(end [this]) (end [this])
(range-type [this])) (range-type [this])
(union [range1 range2]))
(defn- ordered-range-values (defn- ordered-range-values
"Builds an ordered list or 'fenceposts' for the start and end "Builds an ordered list or 'fenceposts' for the start and end
@@ -56,7 +57,7 @@
(defmulti ->discrete-value-range (fn [range-type _start _end] (defmulti ->discrete-value-range (fn [range-type _start _end]
range-type)) range-type))
(defn combine-overlapping-ranges (defn- combine-overlapping-ranges
"transducer to find and combine overlapping ranges by looking at "transducer to find and combine overlapping ranges by looking at
ordered range value markers. ordered range value markers.
@@ -91,9 +92,41 @@
result result
(xf result (->discrete-value-range (:type input) @start (:value input)))))))))))) (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 (def consolidate-ranges-xf
(comp (comp
(combine-overlapping-ranges))) (combine-overlapping-ranges)
(combine-abutting-ranges)))
(defn consolidate [ranges] (defn consolidate [ranges]
(eduction consolidate-ranges-xf (ordered-range-values ranges))) (eduction consolidate-ranges-xf (ordered-range-values ranges)))

View File

@@ -19,6 +19,13 @@
end) end)
(range-type [_this] (range-type [_this]
:int-range-inclusive) :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 Object
(toString [_this] (toString [_this]
@@ -86,10 +93,10 @@
(range/consolidate [(int-range-inclusive 5 5) (range/consolidate [(int-range-inclusive 5 5)
(int-range-inclusive 5 5)]))) (int-range-inclusive 5 5)])))
(is (= [(int-range-inclusive 0 1) (is (= [(int-range-inclusive 0 1)
(int-range-inclusive 2 7) (int-range-inclusive 3 7)
(int-range-inclusive 9 11)] (int-range-inclusive 9 11)]
(range/consolidate [(int-range-inclusive 0 1) (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 3 7)
(int-range-inclusive 5 5) (int-range-inclusive 5 5)
(int-range-inclusive 9 11) (int-range-inclusive 9 11)
@@ -99,4 +106,28 @@
(int-range-inclusive 3 7) (int-range-inclusive 3 7)
(int-range-inclusive 5 5) (int-range-inclusive 5 5)
(int-range-inclusive 6 11) (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)]))))) (int-range-inclusive 5 5)])))))