dao-0.1.0.1: src/Dao/Interval.hs
-- "src/Dao/SetM.hs" defines the Interval data type used to denote
-- a possibly infinite subset of contiguous elements of an Enum data type.
--
-- Copyright (C) 2008-2014 Ramin Honary.
-- This file is part of the Dao System.
--
-- The Dao System is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- The Dao System is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program (see the file called "LICENSE"). If not, see
-- <http://www.gnu.org/licenses/agpl.html>.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
-- | This module provides what I believe to be a better implementation of mathematical intervals
-- than what is provided by the "interval" and "data-interval" packages, although more work has yet
-- to be done instantiating all of the classes in the "latices" package.
--
-- This module improves on the concept of intervals by making them more general, specifically in
-- that intervals need only instantiate 'Prelude.Enum' rather than 'Prelude.Num'. This means
-- 'Prelude.Char' can now be used to create intervals, which is highly useful for constructing and
-- reasoning about regular expressions and parsers.
--
-- Another improvement provided by this module over other interval modules is that non-contiguous
-- interval sets can be constructed. Thus there are two data types, 'Interval' which is never empty
-- and can be used to construct 'Set's, and 'Set's which may or may not be empty or infinite, and do
-- the work of what the @Data.Interval.Interval@ data type would otherwise do.
module Dao.Interval
( -- * The 'Inf' data type
Inf(NegInf, PosInf, Finite)
, stepDown, stepUp, toPoint, enumIsInf
, InfBound, minBoundInf, maxBoundInf
-- * the 'Interval' data type
, Interval, startPoint, endPoint, segment, single, wholeInterval, negInfTo, toPosInf, enumInfSeg
, toBounded, toBoundedPair, segmentMember, singular, plural, segmentNub, segmentInvert
-- * Predicates on 'Interval's
, containingSet, numElems, isWithin, segmentHasEnumInf, segmentIsInfinite
-- * The 'SetM' monadic data type
, SetM, infiniteM, fromListM, rangeM, pointM
, toListM, defaultM, memberM, lookupM, nullM, isSingletonM
-- * Set Operators for monadic 'SetM's
, sieveM, invertM, setXUnionM, unionM, intersectM, deleteM
, setToSetM, setMtoSet
-- * The 'Set' non-monadic data type
, Set, whole, fromList, fromPairs, range, point
, toList, elems, member, Dao.Interval.null, isSingleton
-- * Set Operators for non-monadic 'Set's
, Dao.Interval.invert, setXUnion, Dao.Interval.union, Dao.Interval.intersect, Dao.Interval.delete
-- * Miscelaneous
, upperTriangular, nonAssociativeProduct
)
where
import Data.Monoid
import Data.List
import Data.Ratio
import Control.Monad
import Control.Applicative
import Control.DeepSeq
-- | Like 'Prelude.Bounded', except the bounds might be infiniteM, and return 'NegInf' or
-- 'PosInf' for the bounds. Using the GHC "flexible instances" and "undecidable instances"
-- feature, any data type that is an instance of 'Prelude.Bounded' is also a memberM of 'BoundInf'.
class InfBound c where
minBoundInf :: Inf c
maxBoundInf :: Inf c
instance InfBound () where { minBoundInf = Finite (); maxBoundInf = Finite (); }
instance InfBound Int where { minBoundInf = Finite minBound; maxBoundInf = Finite maxBound; }
instance InfBound Char where { minBoundInf = Finite minBound; maxBoundInf = Finite maxBound; }
instance InfBound Integer where { minBoundInf = NegInf; maxBoundInf = PosInf; }
instance InfBound (Ratio Integer) where { minBoundInf = NegInf; maxBoundInf = PosInf; }
instance InfBound Float where { minBoundInf = NegInf; maxBoundInf = PosInf; }
instance InfBound Double where { minBoundInf = NegInf; maxBoundInf = PosInf; }
-- | Enumerable elements with the possibility of infinity.
data Inf c
= NegInf -- ^ negative infinity
| PosInf -- ^ positive infinity
| Finite c -- ^ a single pointM
deriving Eq
enumIsInf :: Inf c -> Bool
enumIsInf c = case c of
NegInf -> True
PosInf -> True
_ -> False
instance Ord c => Ord (Inf c) where
compare a b = f a b where
f a b | a == b = EQ
f NegInf _ = LT
f _ NegInf = GT
f PosInf _ = GT
f _ PosInf = LT
f (Finite a) (Finite b) = compare a b
instance Show c => Show (Inf c) where
show e = case e of
Finite c -> show c
NegInf -> "-infnt"
PosInf -> "+infnt"
instance Functor Inf where
fmap f e = case e of
NegInf -> NegInf
PosInf -> PosInf
Finite e -> Finite (f e)
-- | Increment a given value, but if the value is 'Prelude.maxBound', return 'PosInf'. In some
-- circumstances this is better than incrementing with @'Data.Functor.fmap' 'Prelude.succ'@ because
-- 'Prelude.succ' evaluates to an error when passing 'Prelude.maxBound' as the argument. This
-- function will never evaluate to an error.
stepUp :: (Eq c, Enum c, InfBound c) => Inf c -> Inf c
stepUp x = if x==maxBoundInf then PosInf else fmap succ x
-- | Decrement a given value, but if the value is 'Prelude.minBound', returns 'NegInf'. In some
-- circumstances this is better than incrementing @'Data.Functor.fmap' 'Prelude.pred'@ because
-- 'Prelude.pred' evaluates to an error when passing 'Prelude.maxBound' as the argument. This
-- function will never evaluate to an error.
stepDown :: (Eq c, Enum c, InfBound c) => Inf c -> Inf c
stepDown x = if x==minBoundInf then NegInf else fmap pred x
-- | Retrieve the value contained in an 'Inf', if it exists.
toPoint :: Inf c -> Maybe c
toPoint c = case c of
Finite c -> Just c
_ -> Nothing
-- | A enumInfSeg of 'Inf' items is a subset of consectutive items in the set of all @c@ where @c@
-- is any type satisfying the 'Prelude.Ix' class. To construct a 'Interval' object, use 'enumInfSeg'.
data Interval c
= Single { startPoint :: Inf c }
| Interval { startPoint :: Inf c, endPoint :: Inf c }
deriving Eq
-- NOTE: the constructor for this data type is not exported because all of the functions in this
-- module that operate on 'Interval's make the assumption that the first parameter *less than* the
-- second parameter. To prevent anyone from screwing it up, the constructor is hidden and
-- constructing a 'Interval' must be done with the 'enumInfSeg' function which checks the parameters.
instance Ord c => Ord (Interval c) where
compare x y = case x of
Single a -> case y of
Single b -> compare a b
Interval b _ -> if a==b then LT else compare a b
Interval a b -> case y of
Single a' -> if a==b then GT else compare a a'
Interval a' b' -> if a==a' then compare b' b else compare a a'
-- not exported
mkSegment :: Eq c => Inf c -> Inf c -> Interval c
mkSegment a b
| a==b = Single a
| otherwise = Interval a b
-- | If the 'Interval' was constructed with 'single', return the pointM (possibly 'PosInf' or
-- 'NegInf') value used to construct it, otherwise return 'Data.Maybe.Nothing'.
singular :: Interval a -> Maybe (Inf a)
singular seg = case seg of
Interval _ _ -> mzero
Single a -> return a
-- | If the 'Interval' was constructed with 'segment', return a pair of points (possibly 'PosInf'
-- or 'NegInf') value used to construct it, otherwise return 'Data.Maybe.Nothing'.
plural :: Interval a -> Maybe (Inf a, Inf a)
plural a = case a of
Interval a b -> return (a, b)
Single _ -> mzero
showSegment :: Show c => Interval c -> String
showSegment seg = case seg of
Single a -> "at "++show a
Interval a b -> "from "++show a++" to "++show b
-- | This gets rid of as many infiniteM elements as possible. All @'Single' 'PosInf'@ and
-- @'Single' 'NegInf'@ points are eliminated, and if an 'NegInf' or 'PosInf' can be
-- replaced with a corresponding 'minBoundInf' or 'maxBoundInf', then it is. This function is
-- intended to be used as a list monadic function, so use it like so:
-- @let myListOfSegments = [...] in myListOfSegments >>= 'delInfPoints'@
canonicalSegment :: (Eq c, InfBound c) => Interval c -> [Interval c]
canonicalSegment seg = nonInf seg >>= \seg -> case seg of
Single a -> [Single a]
Interval a b -> nonInf (mkSegment (bounds a) (bounds b))
where
nonInf seg = case seg of
Single NegInf -> []
Single PosInf -> []
Single a -> [Single a ]
Interval a b -> [Interval a b]
bounds x = case x of
NegInf -> minBoundInf
PosInf -> maxBoundInf
x -> x
instance Show c =>
Show (Interval c) where { show seg = showSegment seg }
-- | A predicate evaluating whether or not a segment includes an 'PosInf' or 'NegInf' value.
-- This should not be confused with a predicate evaluating whether the set of elements included by
-- the rangeM is infiniteM, because types that are instances of 'Prelude.Bounded' may also contain
-- 'PosInf' or 'NegInf' elements, values of these types may be evaluated as "infintie" by
-- this function, even though they are 'Prelude.Bounded'. To check if a segment is infiniteM, use
-- 'segmentIsInfinite' instead.
segmentHasEnumInf :: Interval c -> Bool
segmentHasEnumInf seg = case seg of
Single c -> enumIsInf c
Interval a b -> enumIsInf a || enumIsInf b
-- | A predicate evaluating whether or not a segment is infiniteM. Types that are 'Prelude.Bounded'
-- are always finite, and thus this function will always evaluate to 'Prelude.False' for these
-- types.
segmentIsInfinite :: InfBound c => Interval c -> Bool
segmentIsInfinite seg = case [Single minBoundInf, Single maxBoundInf, seg] of
[Single a, Single b, c] | enumIsInf a || enumIsInf b -> case c of
Single c -> enumIsInf c
Interval a b -> enumIsInf a || enumIsInf b
_ -> False
-- | Construct a 'Interval' from two 'Inf' items. /NOTE/ if the 'Inf' type you are
-- constructing is an instance of 'Prelude.Bounded', use the 'boundedSegment' constructor instead of
-- this function.
enumInfSeg :: (Ord c, Enum c, InfBound c) => Inf c -> Inf c -> Interval c
enumInfSeg a b = seg a b where
seg a b = construct (ck minBoundInf NegInf a) (ck maxBoundInf PosInf b)
ck infnt subst ab = if infnt==ab then subst else ab
construct a b
| a == b = Single a
| a < b = Interval a b
| otherwise = Interval b a
-- | Construct a 'Interval' from two values.
segment :: Ord c => c -> c -> Interval c
segment a b = mkSegment (Finite (min a b)) (Finite (max a b))
-- | Construct a 'Interval' that is only a single unit, i.e. it starts at X and ends at X.
single :: Ord c => c -> Interval c
single a = segment a a
-- | Construct a 'Interval' from negative infinity to a given value.
negInfTo :: InfBound c => c -> Interval c
negInfTo a = Interval minBoundInf (Finite a)
-- | Construct a 'Interval' from a given value to positive infinity.
toPosInf :: InfBound c => c -> Interval c
toPosInf a = Interval (Finite a) maxBoundInf
-- | Construct the infiniteM 'Interval'
wholeInterval :: Interval c
wholeInterval = Interval NegInf PosInf
-- | Tests whether an element is a memberM is enclosed by the 'Interval'.
segmentMember :: Ord c => Interval c -> c -> Bool
segmentMember seg c = case seg of
Single (Finite d) -> c == d
Interval lo hi -> let e = Finite c in lo <= e && e <= hi
_ -> False
-- | Construct a 'Interval', like the 'enumInfSeg' constructor above, however does not require 'Inf'
-- parameters as inputs. This function performs the additional check of testing whether or not a
-- value is equivalent to 'Prelude.minBound' or 'Prelude.maxBound', and if it is, replaces that
-- value with 'NegInf' or 'PosInf' respectively. In other words, you can use
-- 'Prelude.minBound' in place of NegInf and 'Prelude.maxBound' in place of 'PosInf' without
-- changing the semantics of the data structure as it is used throughout the program.
-- boundedSegment :: (Ord c, Enum c, Bounded c) => c -> c -> Interval c
-- boundedSegment a b = if a>b then co b a else co a b where
-- co a b = enumInfSeg (f a minBound NegInf) (f b maxBound PosInf)
-- f x bound infnt = if x==bound then infnt else Finite x
-- | If an 'Inf' is also 'Prelude.Bounded' then you can convert it to some value in the set of
-- 'Prelude.Bounded' items. 'NegInf' translates to 'Prelude.minBound', 'PosInf' translates
-- to 'Prelude.maxBound', and 'Finite' translates to the value at that pointM.
toBounded :: Bounded c => Inf c -> c
toBounded r = case r of
NegInf -> minBound
PosInf -> maxBound
Finite c -> c
-- | Like 'toBounded', but operates on a segment and returns a pair of values.
toBoundedPair :: (Enum c, Bounded c) => Interval c -> (c, c)
toBoundedPair r = case r of
Single c -> (toBounded c, toBounded c)
Interval c d -> (toBounded c, toBounded d)
enumBoundedPair :: (Enum c, Bounded c) => Interval c -> [c]
enumBoundedPair seg = let (lo, hi) = toBoundedPair seg in [lo..hi]
-- | Computes the minimum 'Interval' that can contain the list of all given 'EnumRanges'.
-- 'Data.Maybe.Nothing' indicates the empty set.
containingSet :: (Ord c, Enum c, InfBound c) => [Interval c] -> Maybe (Interval c)
containingSet ex = foldl fe Nothing ex where
fe Nothing a = Just a
fe (Just a) c = Just $ case a of
Single a -> case c of
Single c -> enumInfSeg (min a c) (max a c)
Interval c d -> enumInfSeg (min a c) (max a d)
Interval a b -> case c of
Single c -> enumInfSeg (min a b) (max b c)
Interval c d -> enumInfSeg (min a c) (max b d)
-- | Evaluates to the number of elements covered by this region. Returns 'Prelude.Nothing' if there
-- are an infiniteM number of elements. For data of a type that is not an instance of 'Prelude.Num',
-- for example @'Interval' 'Data.Char.Char'@, it is recommended you first convert to the type
-- @'Interval' 'Data.Int.Int'@ using @'Control.Functor.fmap' 'Prelude.fromEnum'@ before using this
-- function, then convert the result back using @'Control.Functor.fmap' 'Prelude.toEnum'@ if
-- necessary.
numElems :: (Integral c, Enum c) => Interval c -> Maybe Integer
numElems seg = case seg of
Single (Finite _) -> Just 1
Interval (Finite a) (Finite b) -> Just (fromIntegral a - fromIntegral b + 1)
_ -> Nothing
-- | Tests whether an 'Inf' is within the enumInfSeg. It is handy when used with backquote noation:
-- @enumInf `isWithin` enumInfSeg@
isWithin :: (Ord c, Enum c) => Inf c -> Interval c -> Bool
isWithin pointM seg = case seg of
Single x -> pointM == x
Interval NegInf hi -> pointM <= hi
Interval lo PosInf -> lo <= pointM
Interval lo hi -> lo <= pointM && pointM <= hi
-- | Returns true if two 'Interval's are intersecting.
areIntersecting :: (Ord c, Enum c) => Interval c -> Interval c -> Bool
areIntersecting a b = case a of
Single aa -> case b of
Single bb -> aa == bb
Interval _ _ -> aa `isWithin` b
Interval x y -> case b of
Single bb -> bb `isWithin` a
Interval x' y' -> x' `isWithin` a || y' `isWithin` a || x `isWithin` b || y `isWithin` b
-- | Returns true if two 'Interval's are consecutive, that is, if the end is the 'Prelude.pred'essor
-- of the start of the other.
areConsecutive :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> Bool
areConsecutive a b = case a of
Single a -> case b of
Single b
| a < b -> consec a b
| otherwise -> consec b a
Interval x y
| a < x -> consec a x
| otherwise -> consec y a
Interval x y -> case b of
Single a
| a < x -> consec a x
| otherwise -> consec y a
Interval x' y'
| y < x' -> consec y x'
| otherwise -> consec y' x
where { consec a b = stepUp a == b || a == stepDown b }
-- | Performs a set union on two 'Interval's of elements to create a new enumInfSeg. If the elements of
-- the new enumInfSeg are not contiguous, each enumInfSeg is returned separately and unchanged. The first
-- item in the pair of items returned is 'Prelude.True' if any of the items were modified.
segmentUnion :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> (Bool, [Interval c])
segmentUnion a b
| areIntersecting a b = case a of
Single _ -> case b of
Single _ -> (True, [a])
Interval _ _ -> (True, [b])
Interval x y -> case b of
Single _ -> (True, [a])
Interval x' y' -> (True, [enumInfSeg (min x x') (max y y')])
| areConsecutive a b = case a of
Single aa -> case b of
Single bb -> (True, [enumInfSeg aa bb ])
Interval x y -> (True, [enumInfSeg (min aa x) (max aa y)])
Interval x y -> case b of
Single bb -> (True, [enumInfSeg (min bb x) (max bb y)])
Interval x' y' -> (True, [enumInfSeg (min x x') (max y y')])
| otherwise = (False, [a, b])
-- | Performs a set intersection on two 'Interval's of elements to create a new enumInfSeg. If the
-- elements of the new enumInfSeg are not contiguous, this function evaluates to an empty list.
segmentIntersect :: (Ord c, Enum c, InfBound c) => Interval c -> Interval c -> (Bool, [Interval c])
segmentIntersect a b = if areIntersecting a b then joined else (False, []) where
joined = case a of
Single aa -> case b of
Single aa -> (True, [Single aa])
Interval _ _ -> (True, [Single aa])
Interval x y -> case b of
Single aa -> (True, [Single aa])
Interval x' y' -> (True, [enumInfSeg (max x x') (min y y')])
-- | Performs a set "delete" operation, deleteing any elements selected by the first enumInfSeg if
-- they are contained in the second enumInfSeg. This operation is not associative, i.e.
-- @'segmentDelete' a b /= 'segmentDelete' b a@.
segmentDelete :: (Ord c, Enum c, InfBound c) =>
Interval c -> Interval c -> (Bool, [Interval c])
segmentDelete a b = if not (areIntersecting a b) then (False, [a]) else del where
del = case a of
Single _ -> case b of
Single _ -> (True, [])
Interval _ _ -> (True, [])
Interval x y -> case b of
Single x'
| x==x' -> (True, [enumInfSeg (stepUp x) y ])
| y==x' -> (True, [enumInfSeg x (stepDown y)])
| otherwise -> (True, [enumInfSeg x (stepDown x'), enumInfSeg (stepUp x') y])
Interval x' y'
| x' > x && y' < y -> (True, [enumInfSeg x (stepDown x'), enumInfSeg (stepUp y') y])
| x' <= x && y' >= y -> (True, [])
| x' <= x && y' < y -> (True, [enumInfSeg (stepUp y') y])
| x' > x && y' >= y -> (True, [enumInfSeg x (stepDown x')])
| otherwise -> error "segmentDelete"
-- | Evaluates to the set of all elements not selected by the given 'Interval'.
segmentInvert :: (Ord c, Enum c, InfBound c) => Interval c -> [Interval c]
segmentInvert seg = canonicalSegment =<< case seg of
Single x -> case x of
NegInf -> [] -- [Single PosInf]
PosInf -> [] -- [Single NegInf]
Finite _ -> [mkSegment NegInf (stepDown x), mkSegment (stepUp x) PosInf]
Interval x y -> case x of
NegInf -> case y of
NegInf -> [] -- [Single PosInf]
PosInf -> [] -- []
Finite _ -> [mkSegment (stepUp y) PosInf]
PosInf -> case y of
PosInf -> [] -- [Single NegInf]
NegInf -> [] -- []
Finite _ -> [mkSegment NegInf (stepDown y)]
Finite _ -> case y of
NegInf -> [mkSegment (stepUp x) PosInf ]
PosInf -> [mkSegment NegInf (stepDown x)]
Finite _ ->
[ mkSegment NegInf (min (stepDown x) (stepDown y))
, mkSegment (max (stepUp x) (stepUp y)) PosInf
]
-- | Eliminate overlapping and duplicate 'Interval's from a list of segments.
segmentNub :: (Ord c, Enum c, InfBound c) => [Interval c] -> [Interval c]
segmentNub = toList . fromList
----------------------------------------------------------------------------------------------------
-- | This function is used by 'associativeProduct' to generate the list of pairs on which to execute the
-- inner production function. It is a general function that may come in handy, but otherwise does
-- not specifically relate to 'SetM' or 'Interval' types.
--
-- What it does is, Given two lists of items, returns every possible unique combination of two
-- items. For example the pair (1,2) and (2,1) are considered to be the same combination, so only
-- (1,2) is selected. The selected items are returned as a list. The name of this function derives
-- from a similar matrix operation were all possible pairs are placed in a matrix and the
-- upper-triangluar elements are selected and returned. Pass 'Prelude.True' as the first parameter
-- to select items on the main diagonal of the matrix. Passing 'Prelude.False' is handy when you are
-- trying to evaluate a function on every possible 2-element combination of elements from a single
-- list, but don't need to evaluate each element with itself.
upperTriangular :: Bool -> [a] -> [b] -> [(a, b)]
upperTriangular mainDiag ax bx = do
let iter bx = if Data.List.null bx then [] else bx : iter (tail bx)
(a, bx) <- zip ax (if mainDiag then iter bx else if Data.List.null bx then [] else iter (tail bx))
map (\b -> (a, b)) bx
-- Used by the various set operations, including 'unionWithM', 'intersectWithM', and 'deleteWithM', to
-- compute a new set from two parameter sets and a single operation on the compnent 'Interval's. What
-- it does is, given two lists of elements, the largest possible upper triangular matrix (using
-- 'upperTriangular') of all possible pairs of elements from a and b is formed, and on each pair a
-- given inner product function is executed. The first parameter, the product function, is intended
-- to be a function like 'segmentUnion', 'segmentIntersect', or 'segmentDelete'.
associativeProduct
:: (Ord c, Enum c, InfBound c)
=> (Interval c -> Interval c -> (Bool, [Interval c]))
-> [Interval c] -> [Interval c] -> [Interval c]
associativeProduct reduce a b =
let f a b = upperTriangular True a b >>= snd . uncurry reduce
in segmentNub (if length b > length a then f a b else f b a)
-- not for export
-- This equation assumes list arguments passed to it are already sorted list. This alrorithm works
-- in O(log (n^2)) time. Pass two functions, a function for combining intersecting items, and a
-- function for converting non-intersecting items in the list of @a@ to the list of @b@.
exclusiveProduct :: (a -> b -> (Bool, [a])) -> [a] -> [b] -> [a]
exclusiveProduct product ax bx = ax >>= loop False bx where
loop hitOne bx a = case bx of
[] -> if hitOne then [] else [a]
b:bx ->
let (hit, ax) = product a b
in if hit
then ax >>= loop False bx
else if hitOne then [] else loop False bx a
-- The logic is this: we are deleting or XOR-ing items bounded by segments in B from items
-- bounded by segments in A. Both A and B are sorted. For every segment 'a' in A, the following
-- evaluations take place: every element 'b' in B is checked against 'a' until we find a segment
-- 'b[first]' that hits (intersects with) 'a'. The 'hitOne' boolean is set to True as soon as
-- 'b[first]' is found. Now we continue with every 'b' segment after 'b[first]' until we find a
-- segment 'b[missed]' that does not hit (intersect with) 'a'. Since 'b[missed]' does not
-- intersect, every element 'b' above 'b[missed]' will also miss (not intersect with) 'a',
-- assuming 'b' elements are sorted. Therefore, we can stop scanning for further elements in B,
-- we know they will all miss (not intersect). If every element in B misses (does not intersect
-- with) 'a', then the segment 'a' is returned unmodified (because of the definition of XOR).
-- However if even one segment in B hit this 'a', the only the segments produced by
-- 'segmentDelete' are returned.
-- | Unlike inner product, which works with associative operators, 'segmentExclusive'
-- works with non-associative operators, like 'segmentDelete' and 'segmentXOR'. Lists of elements
-- passed to this function are sorted. Lists that are already sorted can be multiplied in
-- in O(log (n*m)) time. The product function you pass will return @(Prelude.True, result)@ if the
-- two arguments passed to it "react" with each other, that is, if they can be multiplied to a
-- non-null or non-zero result. This function is used to implement set deletion.
nonAssociativeProduct :: Ord c => (c -> c -> (Bool, [c])) -> [c] -> [c] -> [c]
nonAssociativeProduct product ax bx = exclusiveProduct product (sort ax) (sort bx)
----------------------------------------------------------------------------------------------------
-- | A set-union of serveral 'Interval's, each segment being paired with a list of arbitrary value
-- @x@. It is like an extension of the list monad, except lists may be divided up and assigned to
-- ranges of integral values (or any type that instantiates 'InfBound').
data SetM c x
= EmptySetM
| InfiniteM{ setValue :: [x] }
| SetM { setSegmentsM :: [(Interval c, [x])], setValue :: [x] }
deriving Eq
instance (Ord c, Enum c, InfBound c, Monoid x) =>
Monoid (SetM c x) where
mempty = EmptySetM
mappend a b = foldValueSetM mappend (unionM a b)
instance
Functor (SetM c) where
fmap f a = case a of
EmptySetM -> EmptySetM
InfiniteM x -> InfiniteM (fmap f x)
SetM a x -> SetM (fmap (fmap (fmap f)) a) (fmap f x)
instance (Ord c, Enum c, InfBound c) =>
Monad (SetM c) where
return = InfiniteM . (:[])
a >>= b = case a of
EmptySetM -> EmptySetM
InfiniteM x -> msum (map b x)
SetM a x -> msum (map segs a ++ map deflts x) where
segs (a, x) = msum (map (sieveM a . b) x)
deflts x = case b x of
EmptySetM -> EmptySetM
InfiniteM x -> InfiniteM x
SetM _ x -> InfiniteM x
instance (Ord c, Enum c, InfBound c) =>
MonadPlus (SetM c) where { mzero = EmptySetM; mplus = unionM; }
instance (Ord c, Enum c, InfBound c) =>
Applicative (SetM c) where { pure = return; (<*>) = ap; }
instance (Ord c, Enum c, InfBound c) =>
Alternative (SetM c) where { empty = mzero; (<|>) = mplus; }
-- | Remove any component segment within the set that does not intersect with the given segment.
sieveM :: (Ord c, Enum c, InfBound c) => Interval c -> SetM c x -> SetM c x
sieveM b a = case a of
EmptySetM -> EmptySetM
InfiniteM x -> SetM [(b, x)] []
SetM a x -> case filter (areIntersecting b . fst) a of
[] -> EmptySetM
[(a, x)] | a==wholeInterval -> InfiniteM x
a -> SetM a x
-- | 'SetM' monads contain values accumulate into lists. This function will reduce these lists to a
-- single element using a folding function.
foldValueSetM :: (x -> x -> x) -> SetM c x -> SetM c x
foldValueSetM f s = case s of
EmptySetM -> EmptySetM
InfiniteM x -> InfiniteM (fol x)
SetM s x ->
SetM
{ setSegmentsM = map (\ (s, x) -> (s, fol x)) s
, setValue = fol x
}
where
fol x = case x of
(a:b:x) -> [foldl f a (b:x)]
[x] -> [x]
[] -> []
-- not for export
-- Used to create a function useful to 'unionM' and 'intersectM' when those functions call
-- 'exclusiveProduct' to determine which component segments are overlaping, and what to do with 1.
-- segments on the low end do not overlap, 2. segments that overlap, and 3. segments on the high end
-- that do not overlap. The first parameter to this function is a function that takes three lists
-- for each of (1) (2) and (3), and returns a new list that combines the three. In the case of
-- 'intersectionM', (1) and (3) (the non-overlapping parts) are disgarded and only (2) (the
-- overlapping part) is returned. In the case of 'unionM', (1) (2) and (3) are simply concatenated.
joinSegments
:: (Ord c, Enum c, InfBound c)
=> ([(Interval c, [x])] -> [(Interval c, [x])] -> [(Interval c, [x])] -> [(Interval c, [x])])
-> (Interval c, [x]) -> (Interval c, [x])
-> (Bool, [(Interval c, [x])])
joinSegments joiner (a, ax) (b, bx) =
let (isecting, andAB) = segmentIntersect a b
(_ , delAB) = segmentDelete a b
(_ , delBA) = segmentDelete b a
newA = map (\s -> (s, ax )) delAB
newAB = map (\s -> (s, ax++bx)) andAB
newB = map (\s -> (s, bx )) delBA
in if not isecting
then (False, [(a, ax), (b, bx)])
else (,) True $ case (newA, newB) of
([], [lo, hi]) -> joiner [lo] newAB [hi]
([lo, hi], []) -> joiner [lo] newAB [hi]
([lo], [hi]) -> joiner [lo] newAB [hi]
_ -> error "joinSegments"
unionM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x
unionM a b = case a of
EmptySetM -> b
InfiniteM ax -> case b of
EmptySetM -> InfiniteM ax
InfiniteM bx -> InfiniteM (ax++bx)
SetM b bx -> SetM b (ax++bx)
SetM a ax -> case b of
EmptySetM -> SetM a ax
InfiniteM bx -> SetM a (ax++bx)
SetM b bx ->
SetM (exclusiveProduct (joinSegments (\lo mid hi -> lo++mid++hi)) a b) (ax++bx)
intersectM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x
intersectM a b = case a of
EmptySetM -> b
InfiniteM ax -> case b of
EmptySetM -> InfiniteM ax
InfiniteM bx -> InfiniteM (ax++bx)
SetM b bx -> SetM b (ax++bx)
SetM a ax -> case b of
EmptySetM -> SetM a ax
InfiniteM bx -> SetM a (ax++bx)
SetM b bx ->
SetM (exclusiveProduct (joinSegments (\_ mid _ -> mid)) a b) (ax++bx)
deleteM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x
deleteM a b = case a of
EmptySetM -> EmptySetM
InfiniteM ax -> case b of
EmptySetM -> InfiniteM ax
InfiniteM _ -> EmptySetM
SetM b _ -> invertM (SetM b []) ax
SetM a ax -> case b of
EmptySetM -> SetM a ax
InfiniteM _ -> EmptySetM
SetM b _ -> flip SetM ax $ nubBy (\a b -> fst a == fst b) $
exclusiveProduct (joinSegments (\lo _ hi -> lo++hi)) a b
setXUnionM :: (Ord c, Enum c, InfBound c) => SetM c x -> SetM c x -> SetM c x
setXUnionM a b = unionM (deleteM a b) (deleteM b a)
invertM :: (Ord c, Enum c, InfBound c) => SetM c x -> [y] -> SetM c y
invertM a y = case a of
EmptySetM -> InfiniteM y
InfiniteM _ -> EmptySetM
SetM a _ ->
SetM (map (flip (,) y) $ toList $ invert $ fromList $ map fst a) []
-- | Initialize a new intinite 'SetM', that is, the set that contains all possible elements.
infiniteM :: (Ord c, Enum c, InfBound c) => [x] -> SetM c x
infiniteM = InfiniteM
-- | Initialize a new 'SetM' object with a list of 'Interval's, which are 'segmentUnion'ed
-- together to create the set.
fromListM :: (Ord c, Enum c, InfBound c) => [Interval c] -> [x] -> SetM c x
fromListM a ax = case segmentNub a of
[a] | a==wholeInterval -> InfiniteM ax
[] -> EmptySetM
a -> SetM (map (flip (,) ax) a) []
-- | Create a set with a single rangeM of elements, no gaps.
rangeM :: (Ord c, Enum c, InfBound c) => c -> c -> [x] -> SetM c x
rangeM a b x = SetM [(segment a b, x)] []
-- | Create a set with a single element.
pointM :: (Ord c, Enum c, InfBound c) => c -> [x] -> SetM c x
pointM c x = rangeM c c x
-- | Tests if an element is a memberM of the set.
memberM :: Ord c => SetM c x -> c -> Bool
memberM a c = case a of
EmptySetM -> False
InfiniteM _ -> True
SetM a _ -> or (map (flip segmentMember c . fst) a)
-- | Test if a set encompases only one element, and if so, returns that one element.
isSingletonM :: (Ord c, Enum c, InfBound c) => SetM c x -> Maybe (c, [x])
isSingletonM a = case a of
SetM a _ -> case a of
[(Single (Finite a), x)] -> Just (a, x)
_ -> Nothing
_ -> Nothing
-- | Tests if a set is empty.
nullM :: SetM c x -> Bool
nullM a = case a of
EmptySetM -> True
SetM [] _ -> True
_ -> False
lookupM :: (Ord c, Enum c, InfBound c) => SetM c x -> c -> [x]
lookupM a c = case a of
EmptySetM -> []
InfiniteM x -> x
SetM a x -> case concatMap snd (filter (isWithin (Finite c) . fst) a) of { [] -> x; x -> x; }
toListM :: SetM c x -> [(Interval c, [x])]
toListM set = case set of
EmptySetM -> []
InfiniteM _ -> []
SetM a _ -> a
defaultM :: SetM c x -> [x]
defaultM set = case set of
EmptySetM -> []
InfiniteM x -> x
SetM _ x -> x
setToSetM :: (Ord c, Enum c, InfBound c) => Set c -> [x] -> SetM c x
setToSetM a x = case a of
EmptySet -> EmptySetM
InfiniteSet -> InfiniteM x
InverseSet a -> setToSetM (forceInvert a) x
Set a -> SetM (map (flip (,) x) a) []
setMtoSet :: SetM c x -> Set c
setMtoSet a = case a of
EmptySetM -> EmptySet
InfiniteM _ -> InfiniteSet
SetM a _ -> Set (map fst a)
----------------------------------------------------------------------------------------------------
data Set c
= EmptySet
| InfiniteSet
| InverseSet (Set c)
| Set [Interval c]
instance (Ord c, Enum c, InfBound c) => Eq (Set c) where
a == b = case a of
EmptySet -> case b of
EmptySet -> True
Set [] -> True
_ -> False
InfiniteSet -> case b of
InfiniteSet -> True
Set [s] | s==wholeInterval -> True
_ -> False
InverseSet a -> case b of
InverseSet b -> a==b
_ -> forceInvert a == b
Set a -> case b of
Set b -> a==b
_ -> False
instance (Ord c, Enum c, InfBound c) => Monoid (Set c) where
mempty = EmptySet
mappend = Dao.Interval.union
instance Show c => Show (Set c) where
show s = case s of
EmptySet -> "enumSet()"
InfiniteSet -> "enumSet(-Inf to +Inf)"
InverseSet s -> "!enumSet("++show s++")"
Set s -> "enumSet("++intercalate ", " (map show s)++")"
whole :: Set c
whole = InfiniteSet
-- not exported, creates a list from segments, but does not clean it with 'segmentNub'
fromListNoNub :: (Ord c, Enum c, InfBound c) => [Interval c] -> Set c
fromListNoNub a =
if Data.List.null a
then EmptySet
else if a==[wholeInterval] then InfiniteSet else Set a
fromList :: (Ord c, Enum c, InfBound c) => [Interval c] -> Set c
fromList a = if Data.List.null a then EmptySet else fromListNoNub a
fromPairs :: (Ord c, Enum c, InfBound c) => [(c, c)] -> Set c
fromPairs = fromList . map (uncurry segment)
range :: (Ord c, Enum c, InfBound c) => c -> c -> Set c
range a b = Set [segment a b]
point :: (Ord c, Enum c, InfBound c) => c -> Set c
point a = Set [single a]
toList :: (Ord c, Enum c, InfBound c) => Set c -> [Interval c]
toList s = case s of
EmptySet -> []
InfiniteSet -> [wholeInterval]
InverseSet s -> toList (forceInvert s)
Set s -> s
elems :: (Ord c, Enum c, Bounded c, InfBound c) => Set c -> [c]
elems = concatMap enumBoundedPair . toList
member :: (Ord c, InfBound c) => Set c -> c -> Bool
member s b = case s of
EmptySet -> False
InfiniteSet -> True
InverseSet s -> not (member s b)
Set [] -> False
Set s -> or (map (flip segmentMember b) s)
null :: Set c -> Bool
null s = case s of
EmptySet -> True
InfiniteSet -> False
InverseSet s -> not (Dao.Interval.null s)
Set [] -> True
Set _ -> False
isSingleton :: (Ord c, Enum c, InfBound c) => Set c -> Maybe c
isSingleton s = case s of
InverseSet s -> isSingleton (forceInvert s)
Set [Single c] -> toPoint c
_ -> mzero
invert :: (Ord c, Enum c, InfBound c) => Set c -> Set c
invert s = case s of
EmptySet -> InfiniteSet
InfiniteSet -> EmptySet
InverseSet s -> s
Set s -> InverseSet (Set s)
-- not for export
forceInvert :: (Ord c, Enum c, InfBound c) => Set c -> Set c
forceInvert s = case s of
EmptySet -> InfiniteSet
InfiniteSet -> EmptySet
InverseSet s -> s
Set [] -> InfiniteSet
Set [s] | s==wholeInterval -> EmptySet
Set s -> fromListNoNub (loop NegInf s >>= canonicalSegment) where
loop mark s = case s of
[] -> [mkSegment (stepUp mark) PosInf]
[Interval a PosInf] -> [mkSegment (stepUp mark) (stepDown a)]
Interval NegInf b : s -> loop b s
Interval a b : s -> mkSegment (stepUp mark) (stepDown a) : loop b s
Single a : s -> mkSegment (stepUp mark) (stepDown a) : loop a s
setXUnion :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c
setXUnion a b = Dao.Interval.delete (Dao.Interval.union a b) (Dao.Interval.intersect a b)
union :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c
union a b = case a of
EmptySet -> b
InfiniteSet -> InfiniteSet
InverseSet a -> Dao.Interval.union (forceInvert a) b
Set [] -> b
Set a -> case b of
EmptySet -> Set a
InfiniteSet -> InfiniteSet
InverseSet b -> Dao.Interval.union (Set a) (forceInvert b)
Set [] -> Set a
Set b -> fromListNoNub (associativeProduct segmentUnion a b)
intersect :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c
intersect a b = case a of
EmptySet -> EmptySet
InfiniteSet -> b
InverseSet a -> Dao.Interval.intersect (forceInvert a) b
Set [] -> EmptySet
Set a -> case b of
EmptySet -> EmptySet
InfiniteSet -> Set a
InverseSet b -> Dao.Interval.intersect (Set a) (forceInvert b)
Set [] -> EmptySet
Set b -> fromListNoNub (associativeProduct segmentIntersect a b)
delete :: (Ord c, Enum c, InfBound c) => Set c -> Set c -> Set c
delete a b = case b of
EmptySet -> a
InfiniteSet -> EmptySet
InverseSet b -> Dao.Interval.delete a (forceInvert b)
Set [] -> a
Set b -> case a of
EmptySet -> EmptySet
InfiniteSet -> forceInvert (Set b)
InverseSet a -> Dao.Interval.delete (forceInvert a) (Set b)
Set [] -> EmptySet
Set a -> fromList (exclusiveProduct segmentDelete a b)
-- Here we call 'fromList' instead of 'fromListNoNub' because an additional 'segmentNub'
-- operation is required.
----------------------------------------------------------------------------------------------------
instance NFData a =>
NFData (Inf a) where
rnf NegInf = ()
rnf PosInf = ()
rnf (Finite c) = deepseq c ()
instance NFData a =>
NFData (Interval a) where
rnf (Single a ) = deepseq a ()
rnf (Interval a b) = deepseq a $! deepseq b ()
instance (NFData a, NFData x) =>
NFData (SetM a x) where
rnf a = case a of
EmptySetM -> ()
InfiniteM ax -> deepseq ax ()
SetM a ax -> deepseq a $! deepseq ax ()
instance NFData a => NFData (Set a) where
rnf EmptySet = ()
rnf InfiniteSet = ()
rnf (Set a) = deepseq a ()
rnf (InverseSet a) = deepseq a ()