bff-mono-0.2.2: Data/BffMono/EquivMap.hs
{-|
An implementation of maps based on Union-Find.
-}
{-# LANGUAGE FlexibleContexts #-}
module Data.BffMono.EquivMap
(
EquivMap
, equals, equalsM
, equate, equateM
, lookup, lookupM
, insert, insertM
, empty
)
where
import Prelude hiding (lookup)
import Data.IntMap (IntMap)
import qualified Data.IntMap as IM
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Control.Monad.State
-- | A datastructure implements "map" based on Union-Find tree.
data EquivMap k a =
EquivMap {
elemMap :: Map k Int,
-- ^ mapping from keys to integers
tree :: IntMap Int,
-- ^ body of union-find tree (negative value means -rank)
valueMap :: IntMap a
-- ^ mapping to values (only correct for a root)
}
-- | Checks that two keys are in the same set or not.
-- The resulting EquivMap is tuned for later queries.
equals :: Ord k => k -> k -> EquivMap k a -> (Bool, EquivMap k a)
equals x1 x2 t =
let (r1,t1) = find x1 t
(r2,t2) = find x2 t1
in (r1 == r2, t2)
-- | Monadic-version of @equals@
equalsM :: (MonadState (EquivMap k a) m, Ord k) => k -> k -> m Bool
equalsM x1 x2 =
do { t <- get
; let (r,t') = equals x1 x2 t
; put t'
; return r }
-- | Empty map
empty :: EquivMap k a
empty = EquivMap M.empty IM.empty IM.empty
-- | "find" of "Union-Find"
find :: Ord k => k -> EquivMap k a -> (Int, EquivMap k a)
find k equivMap =
case M.lookup k (elemMap equivMap) of
Nothing ->
let i = M.size (elemMap equivMap)
equivMap' =
equivMap { elemMap = M.insert k i (elemMap equivMap)
, tree = IM.insert i (-1) (tree equivMap) }
in (i, equivMap')
Just i ->
let (root, t') = findAux i (tree equivMap)
in (root, equivMap { tree = t' } )
where
findAux i t =
let p = fromJust $ IM.lookup i t
in if p < 0 then
(i,t)
else
let (root,t') = findAux p t
in (root, IM.insert i root t')
-- | Unify the set of the given two keys. This operation corresponds to
-- "Union" of "Union-Find".
--
-- FIXME: The function does not touch the values associated with the keys
-- This does not affect the correctness of our bidirectionalization,
-- which does not call @equate@ after @insert@ and @lookup@.
equate :: Ord k => k -> k -> EquivMap k a -> EquivMap k a
equate a1 a2 equivMap =
let (root1, equivMap1) = find a1 equivMap
(root2, equivMap2) = find a2 equivMap1
rk1 = - (fromJust $ IM.lookup root1 $ tree equivMap2)
rk2 = - (fromJust $ IM.lookup root2 $ tree equivMap2)
in if root1 == root2 then
equivMap2
else
if rk1 < rk2 then
insertUF root1 root2 rk1 rk2 (tree equivMap2) equivMap2
else
insertUF root2 root1 rk2 rk1 (tree equivMap2) equivMap2
where
insertUF r1 r2 rk1 rk2 t em =
let t' = IM.insert r1 (- (rk1 + rk2)) t
t'' = IM.insert r2 r1 t'
in em { tree = t'' }
-- | Monadic version of @equte@
equateM :: (MonadState (EquivMap k a) m, Ord k) => k -> k -> m ()
equateM a1 a2 = modify (equate a1 a2)
-- | Associates a value to the set that contains the given key
insert :: Ord k => k -> a -> EquivMap k a -> EquivMap k a
insert k v equivMap =
let (i, equivMap') = find k equivMap
in equivMap' { valueMap = IM.insert i v (valueMap equivMap') }
-- | A monadic version of @insert@
insertM :: (MonadState (EquivMap k a) m, Ord k) => k -> a -> m ()
insertM k v = modify (insert k v)
-- | Loops-up values associated with the set of the given key
lookup :: Ord k => k -> EquivMap k a -> (Maybe a, EquivMap k a)
lookup k equivMap =
let (i, equivMap') = find k equivMap
in (IM.lookup i (valueMap equivMap'), equivMap')
-- | A monadic version of @lookup@
lookupM :: (MonadState (EquivMap k a) m, Ord k) => k -> m (Maybe a)
lookupM k =
do { t <- get
; let (r,t') = lookup k t
; put t'
; return r }