{-# OPTIONS_GHC -cpp -fglasgow-exts #-} ----------------------------------------------------------------------------- -- | -- Module : Data.Ranged.Ranges -- Copyright : (c) Paul Johnson 2006 -- License : BSD-style -- Maintainer : paul@cogito.org.uk -- Stability : experimental -- Portability : portable -- ----------------------------------------------------------------------------- -- | A range has an upper and lower boundary. module Data.Ranged.Ranges ( -- ** Construction Range (..), emptyRange, fullRange, -- ** Predicates rangeIsEmpty, rangeOverlap, rangeEncloses, -- ** Membership rangeHas, rangeListHas, -- ** Set Operations singletonRange, rangeIntersection, rangeUnion, rangeDifference -- ** QuickCheck properties -- $properties ) where import Data.Ranged.Boundaries import Data.Maybe import Test.QuickCheck -- | A Range has upper and lower boundaries. data Ord v => Range v = Range {rangeLower, rangeUpper :: Boundary v} instance (DiscreteOrdered a) => Eq (Range a) where r1 == r2 = (rangeIsEmpty r1 && rangeIsEmpty r2) || (rangeLower r1 == rangeLower r2 && rangeUpper r1 == rangeUpper r2) instance (DiscreteOrdered a) => Ord (Range a) where compare r1 r2 | r1 == r2 = EQ | rangeIsEmpty r1 = LT | rangeIsEmpty r2 = GT | otherwise = compare (rangeLower r1, rangeUpper r1) (rangeLower r2, rangeUpper r2) instance (Show a, DiscreteOrdered a) => Show (Range a) where show r | rangeIsEmpty r = "Empty" | otherwise = lowerBound ++ "x" ++ upperBound where lowerBound = case rangeLower r of BoundaryBelowAll -> "" BoundaryBelow v -> show v ++ " <= " BoundaryAbove v -> show v ++ " < " BoundaryAboveAll -> error "show Range: lower bound is BoundaryAboveAll" upperBound = case rangeUpper r of BoundaryBelowAll -> error "show Range: upper bound is BoundaryBelowAll" BoundaryBelow v -> " < " ++ show v BoundaryAbove v -> " <= " ++ show v BoundaryAboveAll -> "" -- | True if the value is within the range. rangeHas :: Ord v => Range v -> v -> Bool rangeHas (Range b1 b2) v = (v />/ b1) && not (v />/ b2) -- | True if the value is within one of the ranges. rangeListHas :: Ord v => [Range v] -> v -> Bool rangeListHas ls v = or $ map (\r -> rangeHas r v) ls -- | The empty range emptyRange :: DiscreteOrdered v => Range v emptyRange = Range BoundaryAboveAll BoundaryBelowAll -- | The full range. All values are within it. fullRange :: DiscreteOrdered v => Range v fullRange = Range BoundaryBelowAll BoundaryAboveAll -- | A range containing a single value singletonRange :: DiscreteOrdered v => v -> Range v singletonRange v = Range (BoundaryBelow v) (BoundaryAbove v) -- | A range is empty unless its upper boundary is greater than its lower -- boundary. rangeIsEmpty :: DiscreteOrdered v => Range v -> Bool rangeIsEmpty (Range lower upper) = upper <= lower -- | Two ranges overlap if their intersection is non-empty. rangeOverlap :: DiscreteOrdered v => Range v -> Range v -> Bool rangeOverlap r1 r2 = not (rangeIsEmpty r1) && not (rangeIsEmpty r2) && not (rangeUpper r1 <= rangeLower r2 || rangeUpper r2 <= rangeLower r1) -- | The first range encloses the second if every value in the second range is -- also within the first range. If the second range is empty then this is -- always true. rangeEncloses :: DiscreteOrdered v => Range v -> Range v -> Bool rangeEncloses r1 r2 = (rangeLower r1 <= rangeLower r2 && rangeUpper r2 <= rangeUpper r1) || rangeIsEmpty r2 -- | Intersection of two ranges, if any. rangeIntersection :: DiscreteOrdered v => Range v -> Range v -> Range v rangeIntersection (Range lower1 upper1) (Range lower2 upper2) = Range (max lower1 lower2) (min upper1 upper2) -- | Union of two ranges. Returns one or two results. -- -- If there are two results then they are guaranteed to have a non-empty -- gap in between, but may not be in ascending order. rangeUnion :: DiscreteOrdered v => Range v -> Range v -> [Range v] rangeUnion r1@(Range lower1 upper1) r2@(Range lower2 upper2) = if touching then [Range lower upper] else [r1, r2] where touching = (max lower1 lower2) <= (min upper1 upper2) lower = min lower1 lower2 upper = max upper1 upper2 -- | @range1@ minus @range2@. Returns zero, one or two results. Multiple -- results are guaranteed to have non-empty gaps in between, but may not be in -- ascending order. rangeDifference :: DiscreteOrdered v => Range v -> Range v -> [Range v] rangeDifference r1@(Range lower1 upper1) r2@(Range lower2 upper2) = -- There are six possibilities -- 1: r2 completely less than r1 -- 2: r2 overlaps bottom of r1 -- 3: r2 encloses r1 -- 4: r1 encloses r2 -- 5: r2 overlaps top of r1 -- 6: r2 completely greater than r1 if intersects then -- Cases 2,3,4,5 filter (not . rangeIsEmpty) [Range lower1 lower2, Range upper2 upper1] else -- Cases 1, 6 [r1] where intersects = (max lower1 lower2) < (min upper1 upper2) -- QuickCheck generators instance (Arbitrary v, DiscreteOrdered v, Show v) => Arbitrary (Range v) where arbitrary = frequency [ (18, do b1 <- arbitrary b2 <- arbitrary if b1 < b2 then return $ Range b1 b2 else return $ Range b2 b1 ), (1, return emptyRange), (1, return fullRange) ] coarbitrary (Range lower upper) = variant 0 . coarbitrary lower . coarbitrary upper -- QuickCheck Properties {- $properties Range union > prop_union r1 r2 n = > (r1 `rangeHas` n || r2 `rangeHas` n) > == (r1 `rangeUnion` r2) `rangeListHas` n Range intersection > prop_intersection r1 r2 n = > (r1 `rangeHas` n && r2 `rangeHas` n) > == (r1 `rangeIntersection` r2) `rangeHas` n Range difference > prop_difference r1 r2 n = > (r1 `rangeHas` n && not (r2 `rangeHas` n)) > == (r1 `rangeDifference` r2) `rangeListHas` n -} -- Range union prop_union_int r1 r2 n = (r1 `rangeHas` n || r2 `rangeHas` n) == (r1 `rangeUnion` r2) `rangeListHas` n where t :: Integer ; t = n -- Range intersection prop_intersection_int r1 r2 n = (r1 `rangeHas` n && r2 `rangeHas` n) == (r1 `rangeIntersection` r2) `rangeHas` n where t :: Integer ; t = n -- Range difference prop_difference_int r1 r2 n = (r1 `rangeHas` n && not (r2 `rangeHas` n)) == (r1 `rangeDifference` r2) `rangeListHas` n where t :: Integer ; t = n prop_union_real r1 r2 n = (r1 `rangeHas` n || r2 `rangeHas` n) == (r1 `rangeUnion` r2) `rangeListHas` n where t :: Double ; t = n -- Range intersection prop_intersection_real r1 r2 n = (r1 `rangeHas` n && r2 `rangeHas` n) == (r1 `rangeIntersection` r2) `rangeHas` n where t :: Double ; t = n -- Range difference prop_difference_real r1 r2 n = (r1 `rangeHas` n && not (r2 `rangeHas` n)) == (r1 `rangeDifference` r2) `rangeListHas` n where t :: Double ; t = n