General cleanup and performance review changes

- move to transducer for walking the tagged range boundary items
- remove use of core.match, after getting logic correct to reduce
  loading of unneeded library
- added docstrings to function
- added type hints and removed reflection warnings
This commit is contained in:
2026-01-15 20:34:01 -06:00
parent 9ffa5d1b97
commit 3f03936cfb
4 changed files with 361 additions and 335 deletions

View File

@@ -5,23 +5,23 @@
(extend-type LocalDateRange
range/DiscreteValueRange
(abuts [this other]
(abuts? [^LocalDateRange this ^LocalDateRange other]
(.abuts this other))
(value-before [this]
(value-before ^LocalDate [^LocalDateRange this]
(if (.isUnboundedStart this)
(.getStart this)
(.. this getStart (minusDays 1))))
(value-after [this]
(value-after [^LocalDateRange this]
(if (.isUnboundedEnd this)
(.getEnd this)
(.. this getEndInclusive (plusDays 1))))
(start [this]
(start ^LocalDate [^LocalDateRange this]
(.getStart this))
(end [this]
(end ^LocalDate [^LocalDateRange this]
(.getEndInclusive this))
(range-type [_this]
:local-date-range)
(union [this other]
(union [this ^LocalDateRange other]
(when-not (.isConnected this other)
(throw (ex-info "Cannot union non-connecting ranges" {})))
(.union this other)))

View File

@@ -1,7 +1,5 @@
(ns challenge.discrete-value-range
(:require
[clojure.core.match :refer [match]]
[clojure.set :as set]))
(:require [clojure.set :as set]))
(defprotocol DiscreteValueRange
"A protocol for generic behavior on Ranges over
@@ -26,15 +24,23 @@
that before/after values against other values that the range
is over.
"
(abuts [this other])
(value-before [this])
(value-after [this])
(start [this])
(end [this])
(range-type [this])
(union [this other]))
(abuts? [this other] "Does this range abuts the specified range")
(value-before [this] "The discrete value that is considered to be directly before the start value")
(value-after [this] "The discrete value that is considered to be directly after the end value")
(start [this] "The starting value (inclusive) for this range")
(end [this] "The ending value (inclusive) for this range")
(range-type [this]
"Type of the range. Used in dispatch of multimethod to construct new ranges of the type.")
(union [this other]
"Calculates the range that is the union of this range and the other range.
The two ranges should overlap or abut each other."))
(defn ->range-boundaries* [range]
(defmulti ->discrete-value-range (fn [range-type _start _end]
range-type))
(defn ->range-boundaries*
"Construct range boundary 'fencepost' markers for the range"
[range]
[{:value (start range)
:boundary-type :start
:type (range-type range)
@@ -46,6 +52,9 @@
(def ->range-boundaries (memoize ->range-boundaries*))
(def untagged-range-boundary-compare
(juxt :value (comp {:start 0 :end 1} :boundary-type)))
(defn- ordered-range-values
"Builds an ordered list or 'fenceposts' for the start and end
of all given ranges, to prepare to produces a consolidated
@@ -61,12 +70,9 @@
[ranges]
(->> ranges
(mapcat ->range-boundaries)
(sort-by (juxt :value (comp {:start 0 :end 1} :boundary-type)))))
(sort-by untagged-range-boundary-compare)))
(defmulti ->discrete-value-range (fn [range-type _start _end]
range-type))
(defn- combine-overlapping-ranges
(defn- combine-overlapping-ranges-xf
"transducer to find and combine overlapping ranges by looking at
ordered range value markers.
@@ -97,11 +103,11 @@
result)
:end (do
(.pop stack)
(if (not (.empty stack))
result
(xf result (->discrete-value-range type @start value)))))))))))
(if (.empty stack)
(xf result (->discrete-value-range type @start value))
result)))))))))
(defn- combine-abutting-ranges
(defn- combine-abutting-ranges-xf
"transducer to join ranges where the start and end of two ranges are consective
discrete values.
@@ -109,82 +115,97 @@
combined."
[]
(fn [xf]
(let [prev (volatile! nil)]
(let [previous-range (volatile! nil)]
(fn
([] (xf))
([result] (if @prev
(xf (xf result @prev))
([result] (if-let [prev @previous-range]
(xf (xf result prev))
(xf result)))
([result input]
(cond
(reduced? input) result
(let [prev @previous-range]
(cond
(reduced? input) result
(nil? @prev) (do
(vreset! prev input)
result)
(nil? prev) (do
(vreset! previous-range input)
result)
(abuts @prev input)
(let [item (union @prev input)]
(vreset! prev item)
result)
(abuts? prev input)
(let [item (union prev input)]
(vreset! previous-range item)
result)
:else (let [item @prev]
(vreset! prev input)
(xf result item))))))))
:else (let [item prev]
(vreset! previous-range input)
(xf result item)))))))))
(def consolidate-ranges-xf
(comp
(combine-overlapping-ranges)
(combine-abutting-ranges)))
(combine-overlapping-ranges-xf)
(combine-abutting-ranges-xf)))
(defn consolidate [ranges]
(defn consolidate
"Take a set of ranges and consolidate/collapse the ranges
into the minimal set of ranges needed to represent the original
range set"
[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 (transient #{})
close-fn nil]
(match [boundary-item]
[nil]
(persistent! ranges)
(defn- walk-tagged-range-boundaries-xf
"transducer to create the set of ranges for the difference function
by walking range boundaries items that have been taged as either
belonging to the the source set, or the set of ranges to filter
out of the source set, and construct the set of resulting ranges
with the ranges in the filter ranges item removed from the source
set of ranges."
[]
(let [close-working-range (fn [range-type start end]
(->discrete-value-range range-type
start
end))
in-filter-range (volatile! nil)
in-source-range (volatile! nil)
close-fn (volatile! nil)]
(fn [xf]
(fn
([] (xf))
([result] (xf result))
([result boundary-item]
(case [(:boundary-type boundary-item) (:range-source-type boundary-item)]
[:start :source-range]
(do
(vreset! in-source-range true)
(vreset! close-fn (partial close-working-range
(:type boundary-item)
(:value boundary-item)))
result)
[{: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))
[:end :source-range]
(let [close @close-fn]
(vreset! in-source-range false)
(vreset! close-fn nil)
(if @in-filter-range
result
(xf result (close (:value boundary-item)))))
[{: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))
[:start :filter-range]
(do
(vreset! in-filter-range true)
(if-let [close @close-fn]
(do
(vreset! close-fn nil)
(xf result (close (:prev-value boundary-item))))
result))
[{: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))
[:end :filter-range]
[{: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))))))
(do
(vreset! in-filter-range false)
(when @in-source-range
(vreset! close-fn (partial close-working-range
(:type boundary-item)
(:next-value boundary-item))))
result)))))))
(def ^:private range-boundary-source-compare
"Sorts range boundary items
@@ -219,6 +240,6 @@
all-range-set-boundaries (->> (into range-set-boundaries
range-sets-to-remove-boundaries)
(sort-by range-boundary-source-compare))]
(walk-range-boundaries all-range-set-boundaries)))
(into #{} (walk-tagged-range-boundaries-xf) all-range-set-boundaries)))
([range-set range-set-to-remove & additional-range-sets-to-remove]
(difference range-set (apply set/union (conj additional-range-sets-to-remove range-set-to-remove)))))