data-interval-2.0.0: src/Data/IntervalMap/Base.hs
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE CPP, LambdaCase, ScopedTypeVariables, TypeFamilies, DeriveDataTypeable, MultiWayIf, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE Trustworthy #-}
#if __GLASGOW_HASKELL__ >= 708
{-# LANGUAGE RoleAnnotations #-}
#endif
-----------------------------------------------------------------------------
-- |
-- Module : Data.IntervalMap.Base
-- Copyright : (c) Masahiro Sakai 2016
-- License : BSD-style
--
-- Maintainer : masahiro.sakai@gmail.com
-- Stability : provisional
-- Portability : non-portable (CPP, ScopedTypeVariables, TypeFamilies, DeriveDataTypeable, MultiWayIf, GeneralizedNewtypeDeriving)
--
-- Interval datatype and interval arithmetic.
--
-----------------------------------------------------------------------------
module Data.IntervalMap.Base
(
-- * IntervalMap type
IntervalMap (..)
, module Data.ExtendedReal
-- * Operators
, (!)
, (\\)
-- * Query
, null
, member
, notMember
, lookup
, findWithDefault
, span
-- * Construction
, whole
, empty
, singleton
-- ** Insertion
, insert
, insertWith
-- ** Delete\/Update
, delete
, adjust
, update
, alter
-- * Combine
, union
, unionWith
, unions
, unionsWith
, intersection
, intersectionWith
, difference
-- * Traversal
, map
, mapKeysMonotonic
-- * Conversion
, elems
, keys
, assocs
, keysSet
-- ** List
, fromList
, fromListWith
, toList
-- ** Ordered List
, toAscList
, toDescList
-- * Filter
, filter
, split
-- * Submap
, isSubmapOf
, isSubmapOfBy
, isProperSubmapOf
, isProperSubmapOfBy
)
where
import Prelude hiding (null, lookup, map, filter, span, and)
import Control.DeepSeq
import Data.Data
import Data.ExtendedReal
import Data.Hashable
import Data.Foldable hiding (null, toList)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Semigroup as Semigroup
import Data.Interval (Interval)
import qualified Data.Interval as Interval
import Data.IntervalSet (IntervalSet)
import qualified Data.IntervalSet as IntervalSet
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
import Data.Traversable (Traversable(..))
#endif
#if __GLASGOW_HASKELL__ < 804
import Data.Monoid (Monoid(..))
#endif
#if __GLASGOW_HASKELL__ >= 708
import qualified GHC.Exts as GHCExts
#endif
-- ------------------------------------------------------------------------
-- The IntervalMap type
-- | A Map from non-empty, disjoint intervals over k to values a.
--
-- Unlike 'IntervalSet', 'IntervalMap' never merge adjacent mappings,
-- even if adjacent intervals are connected and mapped to the same value.
newtype IntervalMap r a = IntervalMap (Map (LB r) (Interval r, a))
deriving (Eq, Typeable)
#if __GLASGOW_HASKELL__ >= 708
type role IntervalMap nominal representational
#endif
instance (Ord k, Show k, Show a) => Show (IntervalMap k a) where
showsPrec p (IntervalMap m) = showParen (p > appPrec) $
showString "fromList " .
showsPrec (appPrec+1) (Map.elems m)
instance (Ord k, Read k, Read a) => Read (IntervalMap k a) where
readsPrec p =
(readParen (p > appPrec) $ \s0 -> do
("fromList",s1) <- lex s0
(xs,s2) <- readsPrec (appPrec+1) s1
return (fromList xs, s2))
appPrec :: Int
appPrec = 10
-- This instance preserves data abstraction at the cost of inefficiency.
-- We provide limited reflection services for the sake of data abstraction.
instance (Data k, Data a, Ord k) => Data (IntervalMap k a) where
gfoldl k z x = z fromList `k` toList x
toConstr _ = fromListConstr
gunfold k z c = case constrIndex c of
1 -> k (z fromList)
_ -> error "gunfold"
dataTypeOf _ = mapDataType
dataCast1 f = gcast1 f
fromListConstr :: Constr
fromListConstr = mkConstr mapDataType "fromList" [] Prefix
mapDataType :: DataType
mapDataType = mkDataType "Data.IntervalMap.Base.IntervalMap" [fromListConstr]
instance (NFData k, NFData a) => NFData (IntervalMap k a) where
rnf (IntervalMap m) = rnf m
instance (Hashable k, Hashable a) => Hashable (IntervalMap k a) where
hashWithSalt s m = hashWithSalt s (toList m)
instance Ord k => Monoid (IntervalMap k a) where
mempty = empty
mappend = union
mconcat = unions
instance Ord k => Semigroup.Semigroup (IntervalMap k a) where
(<>) = union
#if !defined(VERSION_semigroups)
stimes = Semigroup.stimesIdempotentMonoid
#else
#if MIN_VERSION_semigroups(0,17,0)
stimes = Semigroup.stimesIdempotentMonoid
#else
times1p _ a = a
#endif
#endif
#if __GLASGOW_HASKELL__ >= 708
instance Ord k => GHCExts.IsList (IntervalMap k a) where
type Item (IntervalMap k a) = (Interval k, a)
fromList = fromList
toList = toList
#endif
-- ------------------------------------------------------------------------
newtype LB r = LB (Extended r, Interval.Boundary)
deriving (Eq, NFData, Typeable)
instance Ord r => Ord (LB r) where
compare (LB (lb1, lb1in)) (LB (lb2, lb2in)) =
-- inclusive lower endpoint shuold be considered smaller
(lb1 `compare` lb2) `mappend` (lb2in `compare` lb1in)
-- ------------------------------------------------------------------------
-- Operators
infixl 9 !,\\ --
-- | Find the value at a key. Calls 'error' when the element can not be found.
(!) :: Ord k => IntervalMap k a -> k -> a
IntervalMap m ! k =
case Map.lookupLE (LB (Finite k, Interval.Closed)) m of
Just (_, (i, a)) | k `Interval.member` i -> a
_ -> error "IntervalMap.!: given key is not an element in the map"
-- | Same as 'difference'.
(\\) :: Ord k => IntervalMap k a -> IntervalMap k b -> IntervalMap k a
m1 \\ m2 = difference m1 m2
-- ------------------------------------------------------------------------
-- Query
-- | Is the map empty?
null :: Ord k => IntervalMap k a -> Bool
null (IntervalMap m) = Map.null m
-- | Is the key a member of the map? See also 'notMember'.
member :: Ord k => k -> IntervalMap k a -> Bool
member k (IntervalMap m) =
case Map.lookupLE (LB (Finite k, Interval.Closed)) m of
Just (_, (i, _)) -> k `Interval.member` i
Nothing -> False
-- | Is the key not a member of the map? See also 'member'.
notMember :: Ord k => k -> IntervalMap k a -> Bool
notMember k m = not $ member k m
-- | Lookup the value at a key in the map.
--
-- The function will return the corresponding value as @('Just' value)@,
-- or 'Nothing' if the key isn't in the map.
lookup :: Ord k => k -> IntervalMap k a -> Maybe a
lookup k (IntervalMap m) =
case Map.lookupLE (LB (Finite k, Interval.Closed)) m of
Just (_, (i, a)) | k `Interval.member` i -> Just a
_ -> Nothing
-- | The expression @('findWithDefault' def k map)@ returns
-- the value at key @k@ or returns default value @def@
-- when the key is not in the map.
findWithDefault :: Ord k => a -> k -> IntervalMap k a -> a
findWithDefault def k (IntervalMap m) =
case Map.lookupLE (LB (Finite k, Interval.Closed)) m of
Just (_, (i, a)) | k `Interval.member` i -> a
_ -> def
lookupInterval :: Ord k => Interval k -> IntervalMap k a -> Maybe a
lookupInterval i (IntervalMap m) =
case Map.lookupLE (LB (Interval.lowerBound' i)) m of
Just (_, (j, a)) | i `Interval.isSubsetOf` j -> Just a
_ -> Nothing
-- | convex hull of key intervals.
span :: Ord k => IntervalMap k a -> Interval k
span = IntervalSet.span . keysSet
-- ------------------------------------------------------------------------
-- Construction
-- | The empty map.
empty :: Ord k => IntervalMap k a
empty = IntervalMap Map.empty
-- | The map that maps whole range of k to a.
whole :: Ord k => a -> IntervalMap k a
whole a = IntervalMap $ Map.singleton (LB (Interval.lowerBound' i)) (i, a)
where
i = Interval.whole
-- | A map with a single interval.
singleton :: Ord k => Interval k -> a -> IntervalMap k a
singleton i a
| Interval.null i = empty
| otherwise = IntervalMap $ Map.singleton (LB (Interval.lowerBound' i)) (i, a)
-- ------------------------------------------------------------------------
-- Insertion
-- | insert a new key and value in the map.
-- If the key is already present in the map, the associated value is
-- replaced with the supplied value.
insert :: Ord k => Interval k -> a -> IntervalMap k a -> IntervalMap k a
insert i _ m | Interval.null i = m
insert i a m =
case split i m of
(IntervalMap m1, _, IntervalMap m2) ->
IntervalMap $ Map.union m1 (Map.insert (LB (Interval.lowerBound' i)) (i,a) m2)
-- | Insert with a function, combining new value and old value.
-- @'insertWith' f key value mp@ will insert the pair (interval, value) into @mp@.
-- If the interval overlaps with existing entries, the value for the entry is replace
-- with @(f new_value old_value)@.
insertWith :: Ord k => (a -> a -> a) -> Interval k -> a -> IntervalMap k a -> IntervalMap k a
insertWith _ i _ m | Interval.null i = m
insertWith f i a m = alter g i m
where
g Nothing = Just a
g (Just a') = Just $ f a a'
-- ------------------------------------------------------------------------
-- Delete/Update
-- | Delete an interval and its value from the map.
-- When the interval does not overlap with the map, the original map is returned.
delete :: Ord k => Interval k -> IntervalMap k a -> IntervalMap k a
delete i m | Interval.null i = m
delete i m =
case split i m of
(IntervalMap m1, _, IntervalMap m2) ->
IntervalMap $ Map.union m1 m2
-- | Update a value at a specific interval with the result of the provided function.
-- When the interval does not overlatp with the map, the original map is returned.
adjust :: Ord k => (a -> a) -> Interval k -> IntervalMap k a -> IntervalMap k a
adjust f = update (Just . f)
-- | The expression (@'update' f i map@) updates the value @x@
-- at @i@ (if it is in the map). If (@f x@) is 'Nothing', the element is
-- deleted. If it is (@'Just' y@), the key @i@ is bound to the new value @y@.
update :: Ord k => (a -> Maybe a) -> Interval k -> IntervalMap k a -> IntervalMap k a
update _ i m | Interval.null i = m
update f i m =
case split i m of
(IntervalMap m1, IntervalMap m2, IntervalMap m3) ->
IntervalMap $ Map.unions [m1, Map.mapMaybe (\(j,a) -> (\b -> (j,b)) <$> f a) m2, m3]
-- | The expression (@'alter' f i map@) alters the value @x@ at @i@, or absence thereof.
-- 'alter' can be used to insert, delete, or update a value in a 'IntervalMap'.
alter :: Ord k => (Maybe a -> Maybe a) -> Interval k -> IntervalMap k a -> IntervalMap k a
alter _ i m | Interval.null i = m
alter f i m =
case split i m of
(IntervalMap m1, IntervalMap m2, IntervalMap m3) ->
let m2' = Map.mapMaybe (\(j,a) -> (\b -> (j,b)) <$> f (Just a)) m2
js = IntervalSet.singleton i `IntervalSet.difference` keysSet (IntervalMap m2)
IntervalMap m2'' =
case f Nothing of
Nothing -> empty
Just a -> fromList [(j,a) | j <- IntervalSet.toList js]
in IntervalMap $ Map.unions [m1, m2', m2'', m3]
-- ------------------------------------------------------------------------
-- Combine
-- | The expression (@'union' t1 t2@) takes the left-biased union of @t1@ and @t2@.
-- It prefers @t1@ when overlapping keys are encountered,
union :: Ord k => IntervalMap k a -> IntervalMap k a -> IntervalMap k a
union m1 m2 =
foldl' (\m (i,a) -> insert i a m) m2 (toList m1)
-- | Union with a combining function.
unionWith :: Ord k => (a -> a -> a) -> IntervalMap k a -> IntervalMap k a -> IntervalMap k a
unionWith f m1 m2 =
foldl' (\m (i,a) -> insertWith f i a m) m2 (toList m1)
-- | The union of a list of maps:
-- (@'unions' == 'Prelude.foldl' 'union' 'empty'@).
unions :: Ord k => [IntervalMap k a] -> IntervalMap k a
unions = foldl' union empty
-- | The union of a list of maps, with a combining operation:
-- (@'unionsWith' f == 'Prelude.foldl' ('unionWith' f) 'empty'@).
unionsWith :: Ord k => (a -> a -> a) -> [IntervalMap k a] -> IntervalMap k a
unionsWith f = foldl' (unionWith f) empty
-- | Return elements of the first map not existing in the second map.
difference :: Ord k => IntervalMap k a -> IntervalMap k b -> IntervalMap k a
difference m1 m2 = foldl' (\m i -> delete i m) m1 (IntervalSet.toList (keysSet m2))
-- | Intersection of two maps.
-- Return data in the first map for the keys existing in both maps.
intersection :: Ord k => IntervalMap k a -> IntervalMap k a -> IntervalMap k a
intersection = intersectionWith const
-- | Intersection with a combining function.
intersectionWith :: Ord k => (a -> b -> c) -> IntervalMap k a -> IntervalMap k b -> IntervalMap k c
intersectionWith f im1@(IntervalMap m1) im2@(IntervalMap m2)
| Map.size m1 >= Map.size m2 = g f im1 im2
| otherwise = g (flip f) im2 im1
where
g :: Ord k => (a -> b -> c) -> IntervalMap k a -> IntervalMap k b -> IntervalMap k c
g h jm1 (IntervalMap m3) = IntervalMap $ Map.unions $ go jm1 (Map.elems m3)
where
go _ [] = []
go im ((i,b) : xs) =
case split i im of
(_, IntervalMap m, jm2) ->
Map.map (\(j, a) -> (j, h a b)) m : go jm2 xs
-- ------------------------------------------------------------------------
-- Traversal
instance Ord k => Functor (IntervalMap k) where
fmap = map
instance Ord k => Foldable (IntervalMap k) where
foldMap f (IntervalMap m) = foldMap (\(_,a) -> f a) m
instance Ord k => Traversable (IntervalMap k) where
traverse f (IntervalMap m) = IntervalMap <$> traverse (\(i,a) -> (\b -> (i,b)) <$> f a) m
-- | Map a function over all values in the map.
map :: (a -> b) -> IntervalMap k a -> IntervalMap k b
map f (IntervalMap m) = IntervalMap $ Map.map (\(i, a) -> (i, f a)) m
-- | @'mapKeysMonotonic' f s@ is the map obtained by applying @f@ to each key of @s@.
-- @f@ must be strictly monotonic.
-- That is, for any values @x@ and @y@, if @x@ < @y@ then @f x@ < @f y@.
mapKeysMonotonic :: forall k1 k2 a. (Ord k1, Ord k2) => (k1 -> k2) -> IntervalMap k1 a -> IntervalMap k2 a
mapKeysMonotonic f = fromList . fmap g . toList
where
g :: (Interval k1, a) -> (Interval k2, a)
g (i, a) = (Interval.mapMonotonic f i, a)
-- ------------------------------------------------------------------------
-- | Return all elements of the map in the ascending order of their keys.
elems :: IntervalMap k a -> [a]
elems (IntervalMap m) = [a | (_,a) <- Map.elems m]
-- | Return all keys of the map in ascending order. Subject to list
keys :: IntervalMap k a -> [Interval k]
keys (IntervalMap m) = [i | (i,_) <- Map.elems m]
-- | An alias for 'toAscList'. Return all key\/value pairs in the map
-- in ascending key order.
assocs :: IntervalMap k a -> [(Interval k, a)]
assocs = toAscList
-- | The set of all keys of the map.
keysSet :: Ord k => IntervalMap k a -> IntervalSet k
keysSet (IntervalMap m) = IntervalSet.fromAscList [i | (i,_) <- Map.elems m]
-- | Convert the map to a list of key\/value pairs.
toList :: IntervalMap k a -> [(Interval k, a)]
toList = toAscList
-- | Convert the map to a list of key/value pairs where the keys are in ascending order.
toAscList :: IntervalMap k a -> [(Interval k, a)]
toAscList (IntervalMap m) = Map.elems m
-- | Convert the map to a list of key/value pairs where the keys are in descending order.
toDescList :: IntervalMap k a -> [(Interval k, a)]
toDescList (IntervalMap m) = fmap snd $ Map.toDescList m
-- | Build a map from a list of key\/value pairs.
-- If the list contains more than one value for the same key, the last value
-- for the key is retained.
fromList :: Ord k => [(Interval k, a)] -> IntervalMap k a
fromList = foldl' (\m (i,a) -> insert i a m) empty
-- | Build a map from a list of key\/value pairs with a combining function.
fromListWith :: Ord k => (a -> a -> a) -> [(Interval k, a)] -> IntervalMap k a
fromListWith f = foldl' (\m (i,a) -> insertWith f i a m) empty
-- ------------------------------------------------------------------------
-- Filter
-- | Filter all values that satisfy some predicate.
filter :: Ord k => (a -> Bool) -> IntervalMap k a -> IntervalMap k a
filter p (IntervalMap m) = IntervalMap $ Map.filter (\(_,a) -> p a) m
-- | The expression (@'split' i map@) is a triple @(map1,map2,map3)@ where
-- the keys in @map1@ are smaller than @i@, the keys in @map2@ are included in @i@, and the keys in @map3@ are larger than @i@.
split :: Ord k => Interval k -> IntervalMap k a -> (IntervalMap k a, IntervalMap k a, IntervalMap k a)
split i (IntervalMap m) =
case splitLookupLE (LB (Interval.lowerBound' i)) m of
(smaller, m1, xs) ->
case splitLookupLE (LB (Interval.upperBound i, Interval.Closed)) xs of
(middle, m2, larger) ->
( IntervalMap $
case m1 of
Nothing -> Map.empty
Just (j,b) ->
let k = Interval.intersection (upTo i) j
in if Interval.null k
then smaller
else Map.insert (LB (Interval.lowerBound' k)) (k,b) smaller
, IntervalMap $ Map.unions $ middle :
[ Map.singleton (LB (Interval.lowerBound' k)) (k, b)
| (j, b) <- maybeToList m1 ++ maybeToList m2
, let k = Interval.intersection i j
, not (Interval.null k)
]
, IntervalMap $ Map.unions $ larger :
[ Map.singleton (LB (Interval.lowerBound' k)) (k, b)
| (j, b) <- maybeToList m1 ++ maybeToList m2
, let k = Interval.intersection (downTo i) j
, not (Interval.null k)
]
)
-- ------------------------------------------------------------------------
-- Submap
-- | This function is defined as (@'isSubmapOf' = 'isSubmapOfBy' (==)@).
isSubmapOf :: (Ord k, Eq a) => IntervalMap k a -> IntervalMap k a -> Bool
isSubmapOf = isSubmapOfBy (==)
-- | The expression (@'isSubmapOfBy' f t1 t2@) returns 'True' if
-- all keys in @t1@ are in tree @t2@, and when @f@ returns 'True' when
-- applied to their respective values.
isSubmapOfBy :: Ord k => (a -> b -> Bool) -> IntervalMap k a -> IntervalMap k b -> Bool
isSubmapOfBy f m1 m2 = and $
[ case lookupInterval i m2 of
Nothing -> False
Just b -> f a b
| (i,a) <- toList m1 ]
-- | Is this a proper submap? (ie. a submap but not equal).
-- Defined as (@'isProperSubmapOf' = 'isProperSubmapOfBy' (==)@).
isProperSubmapOf :: (Ord k, Eq a) => IntervalMap k a -> IntervalMap k a -> Bool
isProperSubmapOf = isProperSubmapOfBy (==)
-- | Is this a proper submap? (ie. a submap but not equal).
-- The expression (@'isProperSubmapOfBy' f m1 m2@) returns 'True' when
-- @m1@ and @m2@ are not equal,
-- all keys in @m1@ are in @m2@, and when @f@ returns 'True' when
-- applied to their respective values.
isProperSubmapOfBy :: Ord k => (a -> b -> Bool) -> IntervalMap k a -> IntervalMap k b -> Bool
isProperSubmapOfBy f m1 m2 =
isSubmapOfBy f m1 m2 &&
keysSet m1 `IntervalSet.isProperSubsetOf` keysSet m2
-- ------------------------------------------------------------------------
splitLookupLE :: Ord k => k -> Map k v -> (Map k v, Maybe v, Map k v)
splitLookupLE k m =
case Map.splitLookup k m of
(smaller, Just v, larger) -> (smaller, Just v, larger)
(smaller, Nothing, larger) ->
case Map.maxView smaller of
Just (v, smaller') -> (smaller', Just v, larger)
Nothing -> (smaller, Nothing, larger)
upTo :: Ord r => Interval r -> Interval r
upTo i =
case Interval.lowerBound' i of
(NegInf, _) -> Interval.empty
(PosInf, _) -> Interval.whole
(Finite lb, incl) ->
Interval.interval (NegInf, Interval.Open) (Finite lb, notB incl)
downTo :: Ord r => Interval r -> Interval r
downTo i =
case Interval.upperBound' i of
(PosInf, _) -> Interval.empty
(NegInf, _) -> Interval.whole
(Finite ub, incl) ->
Interval.interval (Finite ub, notB incl) (PosInf, Interval.Open)
notB :: Interval.Boundary -> Interval.Boundary
notB = \case
Interval.Open -> Interval.Closed
Interval.Closed -> Interval.Open