(ns challenge.discrete-value-range) (defprotocol DiscreteValueRange "A protocol for generic behavior on Ranges over discrete values. This is for discrete values as we want to be able to determine the value before the range start and the value immediately after the range end. By using discrete values we: 1. Determine if two ranges abut (touch) each other, and can therefore be combined into a single range 2. Can combine overlapping by listing all ranges' start and end values, and using a stack's push/pop functionality to know if we are in a larger composite range, similiar to parenthesis matching. e.g. If I encounter two range start items, I know I should be expecting two end values, even if the range is 'unbounded', there should be a MIN/MAX value specified. 3. Know the discrete values for both before and or after the range (even if a MIN/MAX marker) value that we can use to compare that before/after values against other values that the range is over. " (abuts [this ^DiscreteValueRange other]) (value-before [this]) (value-after [this]) (start [this]) (end [this]) (range-type [this])) (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 set of ranges, by doing open/close count matching. By building this list, it will allow us to push a `start` onto a stack, and `pop` when we encounter an `end.` If we pop, and get an empty stack, we have found the item that represents the end value that matches the start value that we just popped off the stack, and are able to ignore any intermediate matching starts/stops." [ranges] (->> ranges (mapcat (fn [range] [{:value (start range) :boundary-type :start :type (range-type range)} {:value (end range) :boundary-type :end :type (range-type range)}])) (sort-by (juxt :value (comp {:start 0 :end 1} :boundary-type)))))