tests pass
This commit is contained in:
@@ -1,6 +1,36 @@
|
||||
(ns challenge.core
|
||||
(:require [clojure.set :as set])
|
||||
(:import (java.time LocalDate Period)
|
||||
(:require [challenge.discrete-value-range :as range])
|
||||
(:import (java.time LocalDate)
|
||||
(org.threeten.extra LocalDateRange)))
|
||||
|
||||
(defn difference [& input])
|
||||
(extend-type LocalDateRange
|
||||
range/DiscreteValueRange
|
||||
(abuts [this other]
|
||||
(.abuts this other))
|
||||
(value-before [this]
|
||||
(if (.isUnboundedStart this)
|
||||
(.getStart this)
|
||||
(.. this getStart (minusDays 1))))
|
||||
(value-after [this]
|
||||
(if (.isUnboundedEnd this)
|
||||
(.getEnd this)
|
||||
(.. this getEndInclusive (plusDays 1))))
|
||||
(start [this]
|
||||
(.getStart this))
|
||||
(end [this]
|
||||
(.getEndInclusive this))
|
||||
(range-type [_this]
|
||||
:local-date-range)
|
||||
(union [this other]
|
||||
(when-not (.isConnected this other)
|
||||
(throw (ex-info "Cannot union non-connecting ranges" {})))
|
||||
(.union this other))
|
||||
(before [^LocalDateRange this ^LocalDate x]
|
||||
(.isBefore this x))
|
||||
(after [this ^LocalDate x]
|
||||
(.isAfter (.getEndInclusive this) x)))
|
||||
|
||||
(defmethod range/->discrete-value-range :local-date-range [_ start end]
|
||||
(LocalDateRange/ofClosed start end))
|
||||
|
||||
(def difference range/difference)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
(ns challenge.discrete-value-range
|
||||
(:require [clojure.core.match :refer [match]]))
|
||||
(:require
|
||||
[clojure.core.match :refer [match]]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defprotocol DiscreteValueRange
|
||||
"A protocol for generic behavior on Ranges over
|
||||
@@ -180,3 +182,40 @@
|
||||
range-type
|
||||
new-working-range-start-value))
|
||||
(recur boundary-items false in-source-range ranges close-fn))))))
|
||||
|
||||
(def ^:private range-boundary-source-compare
|
||||
"Sorts range boundary items
|
||||
|
||||
Sorts by (in precidence order)
|
||||
1. :value; lower values first
|
||||
2. :boundary-type; :start before :end
|
||||
3. :range-source-type
|
||||
- filter-ranges should 'wrap' source ranges
|
||||
- for starts, the filter-ranges should be before source-ranges
|
||||
- and for ends, the source-ranges should be before the filter ranges"
|
||||
(juxt :value
|
||||
(comp {:start 0 :end 1} :boundary-type)
|
||||
(comp {[:filter-range :start] 0
|
||||
[:source-range :start] 1
|
||||
[:source-range :end] 2
|
||||
[:filter-range :end] 3}
|
||||
(juxt :range-source-type :boundary-type))))
|
||||
|
||||
(defn difference
|
||||
([range-set]
|
||||
(consolidate range-set))
|
||||
([range-set range-sets-to-remove]
|
||||
(let [range-set-boundaries (->> range-set
|
||||
consolidate
|
||||
ordered-range-values
|
||||
(map #(assoc % :range-source-type :source-range)))
|
||||
range-sets-to-remove-boundaries (->> range-sets-to-remove
|
||||
consolidate
|
||||
ordered-range-values
|
||||
(map #(assoc % :range-source-type :filter-range)))
|
||||
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)))
|
||||
([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)))))
|
||||
|
||||
@@ -86,19 +86,19 @@
|
||||
;; test against integer ranges for easy of expression and interpretation
|
||||
(deftest consolidate-ranges
|
||||
(testing "combines overlapping ranges"
|
||||
(is (= [(int-range-inclusive 5 5)]
|
||||
(is (= #{(int-range-inclusive 5 5)}
|
||||
(range/consolidate [(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 3 7)
|
||||
(int-range-inclusive 9 11)]
|
||||
(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)]
|
||||
(is (= #{(int-range-inclusive 2 11)}
|
||||
(range/consolidate [(int-range-inclusive 2 4)
|
||||
(int-range-inclusive 3 7)
|
||||
(int-range-inclusive 5 5)
|
||||
@@ -106,13 +106,13 @@
|
||||
(int-range-inclusive 5 5)]))))
|
||||
|
||||
(testing "conjoins abutting ranges"
|
||||
(is (= [(int-range-inclusive 0 9)]
|
||||
(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)]
|
||||
(is (= #{(int-range-inclusive 0 9)}
|
||||
(range/consolidate [(int-range-inclusive 0 1)
|
||||
(int-range-inclusive 2 3)
|
||||
(int-range-inclusive 4 5)
|
||||
@@ -120,8 +120,8 @@
|
||||
(int-range-inclusive 5 5)]))))
|
||||
|
||||
(testing "combines overlapping ranges and conjoins abutting ranges"
|
||||
(is (= [(int-range-inclusive 0 7)
|
||||
(int-range-inclusive 13 17)]
|
||||
(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)
|
||||
@@ -176,6 +176,28 @@
|
||||
:value 15
|
||||
:prev-value 14
|
||||
:type :int-range-inclusive}]))))
|
||||
(testing "filter-ranges and source-ranges are the same"
|
||||
(is (= #{}
|
||||
(range/walk-range-boundaries [{: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)}
|
||||
(range/walk-range-boundaries [{:boundary-type :start
|
||||
@@ -243,6 +265,28 @@
|
||||
: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)}
|
||||
(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 :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)
|
||||
@@ -343,3 +387,90 @@
|
||||
: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)})))))))
|
||||
|
||||
Reference in New Issue
Block a user