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:
@@ -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)))
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
Reference in New Issue
Block a user