(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 (abuts [_this other] (or (= 1 (abs (- start (.end other)))) (= 1 (abs (- end (.start other)))))) (value-before [__this] (dec start)) (value-after [_this] (inc end)) (start [_this] start) (end [_this] 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] (str start ".." end))) (defn int-range-inclusive [start end] (assert (<= start end) (str "start : " start "; end: " end)) (->IntInclusiveDiscreteValueRange start end)) (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 (= #{} (range/walk-range-boundaries [{: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)} (range/walk-range-boundaries [{: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)} (range/walk-range-boundaries [{: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 before source-ranges" (is (= #{(int-range-inclusive 11 15)} (range/walk-range-boundaries [{: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)} (range/walk-range-boundaries [{: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)} (range/walk-range-boundaries [{: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 "multiple filter-ranges between source-range" (is (= #{(int-range-inclusive 1 5) (int-range-inclusive 11 12) (int-range-inclusive 20 20)} (range/walk-range-boundaries [{: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 (= #{} (range/walk-range-boundaries [{: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)} (range/walk-range-boundaries [{: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}])))))