- 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
485 lines
31 KiB
Clojure
485 lines
31 KiB
Clojure
(ns challenge.discrete-value-range-test
|
|
(:require
|
|
[clojure.test :refer [deftest testing is]]
|
|
[challenge.discrete-value-range :as range]))
|
|
|
|
(defrecord IntInclusiveDiscreteValueRange
|
|
[^int start ^int end]
|
|
range/DiscreteValueRange
|
|
(range/abuts? [_this other]
|
|
(or (= 1 (abs (- start (range/end other))))
|
|
(= 1 (abs (- end (range/start other))))))
|
|
(value-before [__this]
|
|
(if (= start Integer/MIN_VALUE)
|
|
Integer/MIN_VALUE
|
|
(dec start)))
|
|
(value-after [_this]
|
|
(if (= end Integer/MAX_VALUE)
|
|
Integer/MAX_VALUE
|
|
(inc end)))
|
|
(start [_this]
|
|
start)
|
|
(end [_this]
|
|
end)
|
|
(range-type [_this]
|
|
:int-range-inclusive)
|
|
(union [this other]
|
|
(when-not (or (range/abuts? this other)
|
|
(or (<= start (range/start other) end)
|
|
(<= (range/start other) start (range/end other))))
|
|
(throw (ex-info "Cannot union non-abutting ranges" {})))
|
|
|
|
(range/->discrete-value-range (range/range-type this)
|
|
(min start (range/start other))
|
|
(max end (range/end other))))
|
|
|
|
Object
|
|
(toString [_this]
|
|
(str start ".." end)))
|
|
|
|
(defn- int-range-inclusive* [start end]
|
|
(assert (<= start end) (str "start : " start "; end: " end))
|
|
(->IntInclusiveDiscreteValueRange start end))
|
|
|
|
(def int-range-inclusive (memoize int-range-inclusive*))
|
|
|
|
(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)
|
|
(int-range-inclusive 0 1))))
|
|
|
|
(testing "abuts?"
|
|
(is (= true (range/abuts? (int-range-inclusive 0 1)
|
|
(int-range-inclusive 2 3))))
|
|
(is (= true (range/abuts? (int-range-inclusive 1 1)
|
|
(int-range-inclusive 2 3))))
|
|
(is (= true (range/abuts? (int-range-inclusive 4 7)
|
|
(int-range-inclusive 2 3))))
|
|
(is (= false (range/abuts? (int-range-inclusive 4 7)
|
|
(int-range-inclusive 1 2)))))
|
|
(testing "value-before"
|
|
(is (= 0 (range/value-before (int-range-inclusive 1 8)))))
|
|
(testing "value-after"
|
|
(is (= 9 (range/value-after (int-range-inclusive 1 8)))))
|
|
(testing "start"
|
|
(is (= 1 (range/start (int-range-inclusive 1 8)))))
|
|
(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 1 :prev-value 0 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 2 :next-value 3 :boundary-type :end :type :int-range-inclusive}
|
|
{:value 4 :prev-value 3 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 5 :prev-value 4 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 5 :prev-value 4 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 5 :next-value 6 :boundary-type :end :type :int-range-inclusive}
|
|
{:value 5 :next-value 6 :boundary-type :end :type :int-range-inclusive}
|
|
{:value 8 :next-value 9 :boundary-type :end :type :int-range-inclusive}]
|
|
(#'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 :prev-value 4 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 5 :prev-value 4 :boundary-type :start :type :int-range-inclusive}
|
|
{:value 5 :next-value 6 :boundary-type :end :type :int-range-inclusive}
|
|
{:value 5 :next-value 6 :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 3 7)
|
|
(int-range-inclusive 9 11)}
|
|
(range/consolidate [(int-range-inclusive 0 1)
|
|
(int-range-inclusive 3 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)]))))
|
|
|
|
(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)])))))
|
|
|
|
(deftest walk-range-boundaries-test
|
|
(testing "only filter-ranges"
|
|
(is (= #{}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 4
|
|
:next-value 5
|
|
:type :int-range-inclusive}]))))
|
|
(testing "only source-ranges"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 5
|
|
:next-value 6
|
|
:type :int-range-inclusive}])))
|
|
(is (= #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 11 15)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:next-value 2
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 5
|
|
:prev-value 4
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 11
|
|
:next-value 12
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 15
|
|
:prev-value 14
|
|
:type :int-range-inclusive}]))))
|
|
(testing "filter-ranges and source-ranges are the same"
|
|
(is (= #{}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 5
|
|
:next-value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 5
|
|
:next-value 6
|
|
:type :int-range-inclusive}]))))
|
|
(testing "filter-ranges before source-ranges"
|
|
(is (= #{(int-range-inclusive 11 15)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 5
|
|
:next-value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 11
|
|
:prev-value 10
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 15
|
|
:next-value 16
|
|
:type :int-range-inclusive}]))))
|
|
(testing "filter-ranges after source-ranges"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 5
|
|
:next-value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 10
|
|
:value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 15
|
|
:next-value 16
|
|
:type :int-range-inclusive}]))))
|
|
(testing "single filter-range between source-range"
|
|
(is (= #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 11 20)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 5
|
|
:value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 20
|
|
:next-value 21
|
|
:type :int-range-inclusive}]))))
|
|
(testing "single filter-range between source-range but ends align"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 5
|
|
:value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}]))))
|
|
(testing "multiple filter-ranges between source-range"
|
|
(is (= #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 11 12)
|
|
(int-range-inclusive 20 20)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 5
|
|
:value 6
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 12
|
|
:value 13
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 19
|
|
:next-value 20
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 20
|
|
:prev-value 19
|
|
:type :int-range-inclusive}]))))
|
|
(testing "source range between filter-range"
|
|
(is (= #{}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 1
|
|
:value 1
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 6
|
|
:prev-value 5
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 13
|
|
:prev-value 12
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 19
|
|
:next-value 20
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 20
|
|
:next-value 21
|
|
:type :int-range-inclusive}]))))
|
|
(testing "filter range overlaps source ranges"
|
|
(is (= #{(int-range-inclusive 1 4)
|
|
(int-range-inclusive 11 13)}
|
|
(into #{} (#'range/walk-tagged-range-boundaries-xf) [{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 1
|
|
:prev-value 0
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :filter-range
|
|
:prev-value 4
|
|
:value 5
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 6
|
|
:next-value 7
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :start
|
|
:range-source-type :source-range
|
|
:value 8
|
|
:prev-value 7
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :filter-range
|
|
:value 10
|
|
:next-value 11
|
|
:type :int-range-inclusive}
|
|
{:boundary-type :end
|
|
:range-source-type :source-range
|
|
:value 13
|
|
:next-value 14
|
|
:type :int-range-inclusive}])))))
|
|
|
|
(deftest difference-test
|
|
(testing "unary"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(range/difference #{(int-range-inclusive 1 5)})))
|
|
(is (= #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 10 15)}
|
|
(range/difference #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 10 15)})))
|
|
(is (= #{(int-range-inclusive 1 15)}
|
|
(range/difference #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 5 11)
|
|
(int-range-inclusive 12 15)}))))
|
|
(testing "binary"
|
|
(testing "empty starting set"
|
|
(is (= #{}
|
|
(range/difference #{}
|
|
#{(int-range-inclusive 6 10)}))))
|
|
(testing "single item in starting set"
|
|
(is (= #{(int-range-inclusive 1 10)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{}))))
|
|
(testing "ranges to remove are before all ranges in starting set"
|
|
(is (= #{(int-range-inclusive 100 150)}
|
|
(range/difference #{(int-range-inclusive 100 150)}
|
|
#{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 60 70)}))))
|
|
(testing "ranges to remove are after all ranges in starting set"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(range/difference #{(int-range-inclusive 1 5)}
|
|
#{(int-range-inclusive 10 15)
|
|
(int-range-inclusive 60 70)}))))
|
|
(testing "ranges to remove partially overlap at beginning of starting set"
|
|
(is (= #{(int-range-inclusive 6 10)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{(int-range-inclusive 1 5)}))))
|
|
(testing "ranges to remove partially overlap at end of starting set"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{(int-range-inclusive 6 10)}))))
|
|
(testing "ranges to remove partially overlap at multiple starting sets"
|
|
(is (= #{(int-range-inclusive 1 5)
|
|
(int-range-inclusive 34 40)}
|
|
(range/difference #{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 15 20)
|
|
(int-range-inclusive 30 40)}
|
|
#{(int-range-inclusive 6 33)}))))
|
|
(testing "disjoint ranges in starting set"
|
|
(testing "empty set of ranges to remove"
|
|
(is (= #{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 20 30)}
|
|
(range/difference #{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 20 30)}
|
|
#{}))))))
|
|
(testing "variadic"
|
|
(testing "empty starting set"
|
|
(is (= #{}
|
|
(range/difference #{}
|
|
#{(int-range-inclusive 6 10)}
|
|
#{(int-range-inclusive 16 100)}))))
|
|
(testing "ranges to remove partially overlap at beginning of starting set"
|
|
(is (= #{(int-range-inclusive 6 10)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{(int-range-inclusive 1 5)}
|
|
#{(int-range-inclusive -3 4)}))))
|
|
(testing "ranges to remove have some empty sets"
|
|
(is (= #{(int-range-inclusive 1 10)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{}
|
|
#{})))
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{(int-range-inclusive 6 10)}
|
|
#{}))))
|
|
(testing "ranges to remove partially overlap at end of starting set"
|
|
(is (= #{(int-range-inclusive 1 5)}
|
|
(range/difference #{(int-range-inclusive 1 10)}
|
|
#{(int-range-inclusive 6 10)}
|
|
#{(int-range-inclusive 14 19)}))))
|
|
(testing "disjoint ranges in starting set"
|
|
(testing "empty set of ranges to remove"
|
|
(is (= #{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 20 30)}
|
|
(range/difference #{(int-range-inclusive 1 10)
|
|
(int-range-inclusive 20 30)}
|
|
#{}
|
|
#{(int-range-inclusive 11 14)
|
|
(int-range-inclusive 16 17)})))))))
|