{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TupleSections #-} module Data.Interval where import Control.DeepSeq (NFData(..)) import Data.Bool import Data.Data (Data(..)) import Data.Eq (Eq(..)) import Data.Foldable (concat) import Data.Function (($), flip) import Data.Functor (Functor(..)) import qualified Data.Functor as Functor import Data.Maybe (Maybe(..)) import Data.Ord (Ord(..), Ordering(..)) import Data.Tuple import Data.Typeable (Typeable) import Prelude (Bounded(..), seq) import Text.Show (Show(..)) -- * Type 'Limit' data Limit x = Limit { adherence :: Adherence , limit :: x } deriving (Eq, Data, Show, Typeable) instance Functor Limit where fmap f (Limit a x) = Limit a (f x) instance NFData x => NFData (Limit x) where rnf (Limit _a l) = rnf l data Adherence = Out | In deriving (Eq, Data, Show, Typeable) -- | Return given 'Limit' with its 'adherence' set to the opposite one. flip_limit :: Limit x -> Limit x flip_limit (Limit a x) = Limit (case a of { In -> Out; Out -> In }) x -- ** Comparing 'Limit's -- | Compare two 'low' 'Limit's. newtype LL x = LL { unLL :: x } deriving (Eq) instance Ord x => Ord (LL (Limit x)) where compare (LL x) (LL y) = case compare (limit x) (limit y) of EQ -> case (adherence x, adherence y) of (Out, In ) -> GT (In , Out) -> LT _ -> EQ o -> o -- | Compare two 'high' 'Limit's. newtype HH x = HH { unHH :: x } deriving (Eq) instance Ord x => Ord (HH (Limit x)) where compare (HH x) (HH y) = case compare (limit x) (limit y) of EQ -> case (adherence x, adherence y) of (Out, In ) -> LT (In , Out) -> GT _ -> EQ o -> o -- * Type 'Interval' newtype Ord x => Interval x = Interval (Limit x, Limit x) deriving (Eq, Show, Data, Typeable) instance (NFData x, Ord x) => NFData (Interval x) where rnf (Interval (x, y)) = rnf x `seq` rnf y low :: Ord x => Interval x -> Limit x low (Interval t) = fst t high :: Ord x => Interval x -> Limit x high (Interval t) = snd t -- | Return 'Interval' with given 'low' then 'high' 'Limit's, -- if they form a valid 'Interval'. interval :: Ord x => Limit x -> Limit x -> Maybe (Interval x) interval x y = case compare_without_adherence x y of LT -> Just $ Interval (x, y) EQ -> case (adherence x, adherence y) of (In, In) -> Just $ Interval (x, y) _ -> Nothing GT -> Nothing -- | Like 'Functor.fmap', but may return 'Nothing', if mapped 'Interval' is not valid. fmap :: (Ord x, Ord y) => (x -> y) -> Interval x -> Maybe (Interval y) fmap f (Interval (il, ih)) = interval (Functor.fmap f il) (Functor.fmap f ih) -- | Like 'Functor.fmap', but only safe if given map preserves 'Ordering'. fmap_unsafe :: (Ord x, Ord y) => (x -> y) -> Interval x -> Interval y fmap_unsafe f (Interval (il, ih)) = Interval (Functor.fmap f il, Functor.fmap f ih) {- -- | Like 'Functor.fmap', but on 'Limit's, -- and may return 'Nothing', if mapped 'Interval' is not valid. fmap_limits :: (Ord x, Ord y) => (Limit x -> Limit y) -> Interval x -> Maybe (Interval y) fmap_limits f (Interval (il, ih)) = interval (f il) (f ih) -- | Like 'Functor.fmap', but on 'Limit's -- and only safe if given map preserves 'Ordering'. fmap_limits_unsafe :: (Ord x, Ord y) => (Limit x -> Limit y) -> Interval x -> Interval y fmap_limits_unsafe f (Interval (il, ih)) = Interval (f il, f ih) -} -- | Lexicographical order, handling 'Adherence' correctly. instance Ord x => Ord (Interval x) where compare (Interval (il, ih)) (Interval (jl, jh)) = case compare (LL il) (LL jl) of EQ -> compare (HH ih) (HH jh) o -> o -- | Return 'limit's of given 'Interval' as a tuple. limits :: Ord x => Interval x -> (Limit x, Limit x) limits (Interval t) = t -- | Return an 'Interval' spanning over a single 'limit'. point :: Ord x => x -> Interval x point x = Interval (Limit In x, Limit In x) -- | Return given 'Interval' with 'flip_limit' applied to its 'limit's. flip_limits :: Ord x => Interval x -> Interval x flip_limits (Interval (l, h)) = Interval (flip_limit l, flip_limit h) -- | Return 'Ordering' comparing given 'Interval's according to their 'limit's. compare_without_adherence :: Ord x => Limit x -> Limit x -> Ordering compare_without_adherence (Limit _ x) (Limit _ y) = compare x y -- | Return: -- -- * 'LT': if given value is lower than all values in given 'Interval'. -- * 'EQ': if given value is into the given 'Interval'. -- * 'GT': if given value is higher than all values in given 'Interval'. locate :: Ord x => x -> Interval x -> Ordering locate x (Interval (l, h)) = case compare x (limit l) of LT -> LT EQ | adherence l == In -> EQ EQ -> LT GT -> case compare x (limit h) of LT -> EQ EQ | adherence h == In -> EQ EQ -> GT GT -> GT -- | Return 'True' iif. given value is into the given 'Interval'. within :: Ord x => x -> Interval x -> Bool within x i = locate x i == EQ -- | Return 'True' iif. every value of the first 'Interval' is into the second 'Interval'. into :: Ord x => Interval x -> Interval x -> Bool into i j = case position i j of (Prefix , LT) -> True (Suffixed, GT) -> True (Include , GT) -> True (Equal , _) -> True _ -> False -- | Return 'True' iif. every value of the second 'Interval' is into the first 'Interval'. onto :: Ord x => Interval x -> Interval x -> Bool onto = flip into infix 5 <=..<= (<=..<=) :: Ord x => x -> x -> Maybe (Interval x) (<=..<=) x y = case compare x y of LT -> Just $ Interval (Limit In x, Limit In y) EQ -> Just $ Interval (Limit In x, Limit In y) GT -> Nothing infix 5 <..<= (<..<=) :: Ord x => x -> x -> Maybe (Interval x) (<..<=) x y = case compare x y of LT -> Just $ Interval (Limit Out x, Limit In y) EQ -> Nothing GT -> Nothing infix 5 <=..< (<=..<) :: Ord x => x -> x -> Maybe (Interval x) (<=..<) x y = case compare x y of LT -> Just $ Interval (Limit In x, Limit Out y) EQ -> Nothing GT -> Nothing infix 5 <..< (<..<) :: Ord x => x -> x -> Maybe (Interval x) (<..<) x y = case compare x y of LT -> Just $ Interval (Limit Out x, Limit Out y) EQ -> Nothing GT -> Nothing -- * Type 'Position' data Position = Away -- ^ @-_|@ ('LT') or @|_-@ ('GT') | Adjacent -- ^ @-|@ ('LT') or @|-@ ('GT') | Overlap -- ^ @-+|@ ('LT') or @|+-@ ('GT') | Prefix -- ^ @+|@ ('LT') or @+-@ ('GT') | Suffixed -- ^ @-+@ ('LT') or @|+@ ('GT') | Include -- ^ @-+-@ ('LT') or @|+|@ ('GT') | Equal -- ^ @+@ ('EQ') deriving (Eq, Show) position :: Ord x => Interval x -> Interval x -> (Position, Ordering) position (Interval (il, ih)) (Interval (jl, jh)) = case compare (LL il) (LL jl) of LT -> (, LT) $ case compare_without_adherence ih jl of LT -> Away -- PATTERN: -_| EQ -> case (adherence ih, adherence jl) of (In , In) -> Overlap -- PATTERN: -+| (Out, Out) -> Away -- PATTERN: -_| _ -> Adjacent -- PATTERN: -| GT -> case compare (HH ih) (HH jh) of LT -> Overlap -- PATTERN: -+| EQ -> Suffixed -- PATTERN: -+ GT -> Include -- PATTERN: -+- EQ -> case compare (HH ih) (HH jh) of LT -> (Prefix, LT) -- PATTERN: +| EQ -> (Equal , EQ) -- PATTERN: + GT -> (Prefix, GT) -- PATTERN: +- GT -> (, GT) $ case compare_without_adherence il jh of LT -> case compare (HH ih) (HH jh) of LT -> Include -- PATTERN: |+| EQ -> Suffixed -- PATTERN: |+ GT -> Overlap -- PATTERN: |+- EQ -> case (adherence il, adherence jh) of (In , In) -> Overlap -- PATTERN: |+- (Out, Out) -> Away -- PATTERN: |_- _ -> Adjacent -- PATTERN: |- GT -> Away -- PATTERN: |_- infix 4 ..<<.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT'). (..<<..) :: Ord x => Interval x -> Interval x -> Bool (..<<..) i j = case position i j of (Away, LT) -> True _ -> False infix 4 ..>>.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT'). (..>>..) :: Ord x => Interval x -> Interval x -> Bool (..>>..) i j = case position i j of (Away, GT) -> True _ -> False infix 4 ..<.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT') or ('Adjacent', 'LT'). (..<..) :: Ord x => Interval x -> Interval x -> Bool (..<..) i j = case position i j of (Away , LT) -> True (Adjacent, LT) -> True _ -> False infix 4 ..>.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT') or ('Adjacent', 'GT'). (..>..) :: Ord x => Interval x -> Interval x -> Bool (..>..) i j = case position i j of (Away , GT) -> True (Adjacent, GT) -> True _ -> False infix 4 ..<=.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'LT'), ('Adjacent', 'LT'), ('Overlap', 'LT'), ('Prefix', 'LT'), ('Suffixed', 'LT'), ('Include', 'GT'), or ('Equal', _). (..<=..) :: Ord x => Interval x -> Interval x -> Bool (..<=..) i j = case position i j of (Away , LT) -> True (Adjacent, LT) -> True (Overlap , LT) -> True (Prefix , LT) -> True (Suffixed, LT) -> True (Include , GT) -> True (Equal , _ ) -> True _ -> False infix 4 ..>=.. -- | Return 'True' iif. 'Position' of given 'Interval's is ('Away', 'GT'), ('Adjacent', 'GT'), ('Overlap', 'GT'), ('Prefix', 'GT'), ('Suffixed', 'GT'), ('Include', 'LT'), or ('Equal', _). (..>=..) :: Ord x => Interval x -> Interval x -> Bool (..>=..) i j = case position i j of (Away , GT) -> True (Adjacent, GT) -> True (Overlap , GT) -> True (Prefix , GT) -> True (Suffixed, GT) -> True (Include , LT) -> True (Equal , _ ) -> True _ -> False -- * Merge union :: Ord x => Interval x -> Interval x -> Maybe (Interval x) union i j = case position i j of (Away, _) -> -- PATTERN: -_| or |_- Nothing (Adjacent, o) -> case o of LT -> Just $ Interval (low i, high j) -- PATTERN: -| EQ -> Nothing GT -> Just $ Interval (low j, high i) -- PATTERN: |- (Overlap, o) -> case o of LT -> Just $ Interval (low i, high j) -- PATTERN: -+| EQ -> Nothing GT -> Just $ Interval (low j, high i) -- PATTERN: |+- (Prefix, o) -> case o of LT -> Just j -- PATTERN: +| EQ -> Nothing GT -> Just i -- PATTERN: +- (Suffixed, o) -> case o of LT -> Just i -- PATTERN: -+ EQ -> Nothing GT -> Just j -- PATTERN: |+ (Include, o) -> case o of LT -> Just i -- PATTERN: -+- EQ -> Nothing GT -> Just j -- PATTERN: |+| (Equal, _) -> -- PATTERN: + Just i intersection :: Ord x => Interval x -> Interval x -> Maybe (Interval x) intersection i j = case position i j of (Away, _) -> -- PATTERN: -_| or |_- Nothing (Adjacent, _) -> -- PATTERN: -| or |- Nothing (Overlap, o) -> case o of LT -> Just $ Interval (low j, high i) -- PATTERN: -+| EQ -> Nothing GT -> Just $ Interval (low i, high j) -- PATTERN: |+- (Prefix, o) -> case o of LT -> Just i -- PATTERN: +| EQ -> Nothing GT -> Just j -- PATTERN: +- (Suffixed, o) -> case o of LT -> Just j -- PATTERN: -+ EQ -> Nothing GT -> Just i -- PATTERN: |+ (Include, o) -> case o of LT -> Just j -- PATTERN: -+- EQ -> Nothing GT -> Just i -- PATTERN: |+| (Equal, _) -> -- PATTERN: + Just i span :: Ord x => Interval x -> Interval x -> Interval x span i j = Interval ( unLL (min (LL $ low i) (LL $ low j)) , unHH (max (HH $ high i) (HH $ high j)) ) -- * Type 'Unlimitable' data Unlimitable x = Unlimited_low | Limited { limited :: x } | Unlimited_high deriving (Eq, Ord, Show) instance Functor Unlimitable where fmap _f Unlimited_low = Unlimited_low fmap _f Unlimited_high = Unlimited_high fmap f (Limited x) = Limited (f x) instance Bounded (Unlimitable x) where minBound = Unlimited_low maxBound = Unlimited_high instance Bounded (Limit (Unlimitable x)) where minBound = Limit In Unlimited_low maxBound = Limit In Unlimited_high unlimited :: Ord x => Interval (Unlimitable x) unlimited = Interval ( Limit In Unlimited_low , Limit In Unlimited_high ) unlimit :: Ord x => Interval x -> Interval (Unlimitable x) unlimit = fmap_unsafe Limited (<..) :: Ord x => x -> Interval (Unlimitable x) (<..) x = Interval (Limit Out (Limited x), Limit In Unlimited_high) (<=..) :: Ord x => x -> Interval (Unlimitable x) (<=..) x = Interval (Limit In (Limited x), Limit In Unlimited_high) (..<) :: Ord x => x -> Interval (Unlimitable x) (..<) x = Interval (Limit In Unlimited_low, Limit Out (Limited x)) (..<=) :: Ord x => x -> Interval (Unlimitable x) (..<=) x = Interval (Limit In Unlimited_low, Limit In (Limited x)) -- * Type 'Pretty' newtype Pretty x = Pretty x deriving (Eq, Ord) instance (Ord x, Show x) => Show (Pretty (Interval x)) where show (Pretty i) = concat [ case adherence (low i) of In -> "[" Out -> "]" , show (limit $ low i) , ".." , show (limit $ high i) , case adherence (high i) of In -> "]" Out -> "[" ] instance Show x => Show (Pretty (Unlimitable x)) where show (Pretty x) = case x of Unlimited_low -> "-oo" Limited l -> show l Unlimited_high -> "+oo"