module Data.Ranged.Ranges (
Range (..),
emptyRange,
fullRange,
rangeIsEmpty,
rangeIsFull,
rangeOverlap,
rangeEncloses,
rangeSingletonValue,
rangeHas,
rangeListHas,
singletonRange,
rangeIntersection,
rangeUnion,
rangeDifference,
prop_unionRange,
prop_unionRangeLength,
prop_intersectionRange,
prop_differenceRange,
prop_intersectionOverlap,
prop_enclosureUnion,
prop_singletonRangeHas,
prop_singletonRangeHasOnly,
prop_singletonRangeConverse,
prop_emptyNonSingleton,
prop_fullNonSingleton,
prop_nonSingleton,
prop_intSingleton
) where
import Control.Monad
import Data.Ranged.Boundaries
import Data.Maybe
import Test.QuickCheck
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"
| rangeIsFull r = "All x"
| otherwise =
case rangeSingletonValue r of
Just v -> "x == " ++ show v
Nothing -> 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 -> ""
rangeHas :: Ord v => Range v -> v -> Bool
rangeHas (Range b1 b2) v =
(v />/ b1) && not (v />/ b2)
rangeListHas :: Ord v =>
[Range v] -> v -> Bool
rangeListHas ls v = or $ map (\r -> rangeHas r v) ls
emptyRange :: DiscreteOrdered v => Range v
emptyRange = Range BoundaryAboveAll BoundaryBelowAll
fullRange :: DiscreteOrdered v => Range v
fullRange = Range BoundaryBelowAll BoundaryAboveAll
singletonRange :: DiscreteOrdered v => v -> Range v
singletonRange v = Range (BoundaryBelow v) (BoundaryAbove v)
rangeSingletonValue :: DiscreteOrdered v => Range v -> Maybe v
rangeSingletonValue (Range (BoundaryBelow v1) (BoundaryBelow v2))
| adjacent v1 v2 = Just v1
| otherwise = Nothing
rangeSingletonValue (Range (BoundaryBelow v1) (BoundaryAbove v2))
| v1 == v2 = Just v1
| otherwise = Nothing
rangeSingletonValue (Range (BoundaryAbove v1) (BoundaryBelow v2)) =
do
v2' <- adjacentBelow v2
v2'' <- adjacentBelow v2'
if v1 == v2'' then return v2' else Nothing
rangeSingletonValue (Range (BoundaryAbove v1) (BoundaryAbove v2))
| adjacent v1 v2 = Just v2
| otherwise = Nothing
rangeSingletonValue (Range _ _) = Nothing
rangeIsEmpty :: DiscreteOrdered v => Range v -> Bool
rangeIsEmpty (Range lower upper) = upper <= lower
rangeIsFull :: DiscreteOrdered v => Range v -> Bool
rangeIsFull = (== fullRange)
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)
rangeEncloses :: DiscreteOrdered v => Range v -> Range v -> Bool
rangeEncloses r1 r2 =
(rangeLower r1 <= rangeLower r2 && rangeUpper r2 <= rangeUpper r1)
|| rangeIsEmpty r2
rangeIntersection :: DiscreteOrdered v => Range v -> Range v -> Range v
rangeIntersection r1@(Range lower1 upper1) r2@(Range lower2 upper2)
| rangeIsEmpty r1 || rangeIsEmpty r2 = emptyRange
| otherwise = Range (max lower1 lower2) (min upper1 upper2)
rangeUnion :: DiscreteOrdered v => Range v -> Range v -> [Range v]
rangeUnion r1@(Range lower1 upper1) r2@(Range lower2 upper2)
| rangeIsEmpty r1 = [r2]
| rangeIsEmpty r2 = [r1]
| otherwise =
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
rangeDifference :: DiscreteOrdered v => Range v -> Range v -> [Range v]
rangeDifference r1@(Range lower1 upper1) (Range lower2 upper2) =
if intersects
then
filter (not . rangeIsEmpty) [Range lower1 lower2, Range upper2 upper1]
else
[r1]
where
intersects = (max lower1 lower2) < (min upper1 upper2)
instance (Arbitrary v, DiscreteOrdered v, Show v) =>
Arbitrary (Range v) where
arbitrary = frequency [
(17, do
b1 <- arbitrary
b2 <- arbitrary
if b1 < b2
then return $ Range b1 b2
else return $ Range b2 b1
),
(1, do
v <- arbitrary
return $ singletonRange v
),
(1, return emptyRange),
(1, return fullRange)
]
instance (CoArbitrary v, DiscreteOrdered v, Show v) =>
CoArbitrary (Range v) where
coarbitrary (Range lower upper) =
variant (0 :: Int) . coarbitrary lower . coarbitrary upper
prop_unionRange :: (DiscreteOrdered a) => Range a -> Range a -> a -> Bool
prop_unionRange r1 r2 n =
(r1 `rangeHas` n || r2 `rangeHas` n)
== (r1 `rangeUnion` r2) `rangeListHas` n
prop_unionRangeLength :: (DiscreteOrdered a) => Range a -> Range a -> Bool
prop_unionRangeLength r1 r2 = (n == 1) || (n == 2)
where n = length $ rangeUnion r1 r2
prop_intersectionRange :: (DiscreteOrdered a) => Range a -> Range a -> a -> Bool
prop_intersectionRange r1 r2 n =
(r1 `rangeHas` n && r2 `rangeHas` n)
== (r1 `rangeIntersection` r2) `rangeHas` n
prop_differenceRange :: (DiscreteOrdered a) => Range a -> Range a -> a -> Bool
prop_differenceRange r1 r2 n =
(r1 `rangeHas` n && not (r2 `rangeHas` n))
== (r1 `rangeDifference` r2) `rangeListHas` n
prop_intersectionOverlap :: (DiscreteOrdered a) => Range a -> Range a -> Bool
prop_intersectionOverlap r1 r2 =
(rangeIsEmpty $ rangeIntersection r1 r2) == not (rangeOverlap r1 r2)
prop_enclosureUnion :: (DiscreteOrdered a) => Range a -> Range a -> Bool
prop_enclosureUnion r1 r2 = rangeEncloses r1 r2 == (rangeUnion r1 r2 == [r1])
prop_singletonRangeHas :: (DiscreteOrdered a) => a -> Bool
prop_singletonRangeHas v = singletonRange v `rangeHas` v
prop_singletonRangeHasOnly :: (DiscreteOrdered a) => a -> a -> Bool
prop_singletonRangeHasOnly v1 v2 =
(v1 == v2) == (singletonRange v1 `rangeHas` v2)
prop_singletonRangeConverse:: (DiscreteOrdered a) => a -> Bool
prop_singletonRangeConverse v =
rangeSingletonValue (singletonRange v) == Just v
prop_emptyNonSingleton :: Bool
prop_emptyNonSingleton =
rangeSingletonValue (emptyRange :: Range Int) == Nothing
prop_fullNonSingleton :: Bool
prop_fullNonSingleton =
rangeSingletonValue (fullRange :: Range Int) == Nothing
prop_nonSingleton :: Double -> Double -> Property
prop_nonSingleton x y = (x < y) ==> null $ mapMaybe rangeSingletonValue rs
where rs = [
Range (BoundaryBelow x) (BoundaryBelow y),
Range (BoundaryAbove x) (BoundaryBelow y),
Range (BoundaryBelow x) (BoundaryAbove y),
Range (BoundaryAbove x) (BoundaryAbove y)]
prop_intSingleton :: Integer -> Integer -> Property
prop_intSingleton x y = forAll (rangeAround x y) $ \r ->
case filter (rangeHas r) [x-1 .. y+1] of
[v] -> rangeSingletonValue r == Just v
_ -> rangeSingletonValue r == Nothing
where
rangeAround v1 v2 = return Range `ap` genBound v1 `ap` genBound v2
genBound v = elements [BoundaryAbove v, BoundaryBelow v]