g2-0.2.0.0: src/G2/Data/UFMap.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TupleSections #-}
module G2.Data.UFMap ( UFMap
, empty
, toList
, fromList
, toSet
, fromSet
, toSimpleMap
, join
, joinAll
, lookup
, lookupWithRep
, (!)
, find
, insert
, insertWith
, adjust
, alter
, clear
, filterWithKey
, unionWith
, mergeJoiningWithKey
, map
, null
, keys
, joinedKeys
, elems
, member) where
import qualified G2.Data.UnionFind as UF
import Control.Exception
import qualified Control.Monad as Mon
import Data.Data (Data (..), Typeable)
import Data.Hashable
import qualified Data.HashMap.Lazy as M
import qualified Data.HashSet as S
import qualified Data.List as L
import Data.Maybe
import Prelude hiding (lookup, null, map)
import qualified Prelude as P
import Text.Read
import qualified Text.Read.Lex as L
import GHC.Read
import GHC.Generics (Generic)
import Test.Tasty.QuickCheck
-- | A map allowing inserts and lookups via keys, and for sets of keys to be
-- unioned so that all keys in the set point to the same element.
-- Keys @k1@ and @k2@ may be unioned regardless of whether both are already in the `UFMap`.
-- If @k1@ and @k2@ are unioned without either being in the `UFMap`, and then a value
-- for either is inserted, the other will also then point to that value.
data UFMap k v = UFMap { joined :: UF.UnionFind k
, store :: M.HashMap k v }
deriving (Typeable, Data, Generic)
instance (Eq k, Hashable k, Hashable v) => Hashable (UFMap k v)
-- | An empty `UFMap`.
empty :: UFMap k v
empty = UFMap UF.empty M.empty
-- | Convert a @`UFMap` k v@ to a list. Each tuple in the list contains a list of
-- unioned keys, alongside a @`Maybe` v@. If the keys were pointing to a value
-- @v@ in the Map, this is @`Just` v@, otherwise it is `Nothing`.
toList :: (Eq k, Hashable k) => UFMap k v -> [([k], Maybe v)]
toList uf =
let
uf_l = UF.toList $ joined uf
c_uf_l = concat uf_l
not_acc = P.map (:[]) $ keys uf L.\\ c_uf_l
in
P.map (\l -> (l, lookup (head l) uf)) $ uf_l ++ not_acc
toSet :: (Eq k, Eq v, Hashable k, Hashable v) => UFMap k v -> S.HashSet (S.HashSet k, Maybe v)
toSet = S.fromList . P.map (\(ks, v) -> (S.fromList ks, v)) . toList
fromList :: (Eq k, Hashable k) => [([k], Maybe v)] -> UFMap k v
fromList xs =
let
xs_j = concatMap (\(ks, v) -> case v of
Just v' -> P.map (\k -> (k, v')) ks
Nothing -> [] ) xs
m = foldr (uncurry insert) empty xs_j
in
foldr (\(ks, _) m' ->
case ks of
[] -> m'
(k:_) -> foldr (\k' -> join const k k') m' ks)
m xs
fromSet :: (Eq k, Hashable k) => S.HashSet (S.HashSet k, Maybe v) -> UFMap k v
fromSet = fromList . P.map (\(ks, v) -> (S.toList ks, v)) . S.toList
toSimpleMap :: UFMap k v -> M.HashMap k v
toSimpleMap = store
-- | Joins two keys, regardless of whether they are present in the map.
-- If the keys are already joined, the map is not changed
join :: (Eq k, Hashable k) => (v -> v -> v) -> k -> k -> UFMap k v -> UFMap k v
join f k1 k2 ufm@(UFMap uf m)
| UF.find k1 uf == UF.find k2 uf = ufm
| otherwise =
let
(r1, v1) = lookupWithRep k1 ufm
(r2, v2) = lookupWithRep k2 ufm
uf' = UF.union k1 k2 uf
r = UF.find k1 uf'
m' = M.delete r1 . M.delete r2 $ m
m'' = case (v1, v2) of
(Just v1', Just v2') -> M.insert r (f v1' v2') m'
(Just v1', _) -> M.insert r v1' m'
(_, Just v2') -> M.insert r v2' m'
_ -> m
in
assert (isNothing (M.lookup k1 m'') || isNothing (M.lookup k2 m''))
UFMap uf' m''
joinAll :: (Eq k, Hashable k) => (v -> v -> v) -> [k] -> UFMap k v -> UFMap k v
joinAll _ [] uf = uf
joinAll f xs@(x:_) uf = foldr (join f x) uf xs
-- | Lookup the value at a key in the `UFMap`.
-- Returns the corresponding @`Just` v@, or `Nothing` if the key isn't in the map.
lookup :: (Eq k, Hashable k) => k -> UFMap k v -> Maybe v
lookup k = snd . lookupWithRep k
-- | Lookup the value at a key in the `UFMap`, as well as the representative of the key.
-- Returns the corresponding value as @`Just` v@, or `Nothing` if the key isn't in the map.
lookupWithRep :: (Eq k, Hashable k) => k -> UFMap k v -> (k, Maybe v)
lookupWithRep k (UFMap uf m) =
let r = UF.find k uf in
(r, M.lookup r m)
-- | Find the representative of the key in the `UFMap`.
find :: (Eq k, Hashable k) => k -> UFMap k v -> k
find k = UF.find k . joined
-- | Lookup the value at a key in the `UFMap`.
-- The function will call error if the key isn't in the map.
(!) :: (Eq k, Hashable k) => UFMap k v -> k -> v
uf ! k = case lookup k uf of
Just v -> v
Nothing -> error "!: key not found"
-- | Adds a key-value pair to the `UFMap`.
-- If the key is already in the map, it's value is overwritten.
insert :: (Eq k, Hashable k) => k -> v -> UFMap k v -> UFMap k v
insert k v (UFMap uf m) =
let
m' = M.insert (UF.find k uf) v m
in
assert (UF.find k uf == k || isNothing (M.lookup k m'))
UFMap uf m'
insertWith :: (Eq k, Hashable k) => (v -> v -> v) -> k -> v -> UFMap k v -> UFMap k v
insertWith f k v (UFMap uf m) = UFMap uf $ M.insertWith f (UF.find k uf) v m
adjust :: (Eq k, Hashable k) => (v -> v) -> k -> UFMap k v -> UFMap k v
adjust f k (UFMap uf m) = UFMap uf $ M.adjust f (UF.find k uf) m
alter :: (Eq k, Hashable k) => (Maybe v -> Maybe v) -> k -> UFMap k v -> UFMap k v
alter f k (UFMap uf m) = UFMap uf $ M.alter f (UF.find k uf) m
-- | Preserves the joined keys, but deletes all contained elements
clear :: UFMap k v -> UFMap k v
clear (UFMap uf _) = UFMap uf M.empty
filterWithKey :: (k -> v -> Bool) -> UFMap k v -> UFMap k v
filterWithKey p (UFMap uf m) = UFMap uf $ M.filterWithKey p m
unionWith :: (Eq k, Hashable k) => (v -> v -> v) -> UFMap k v -> UFMap k v -> UFMap k v
unionWith f ufm1 (UFMap uf2 m2) =
let
ufm1' = foldr (joinAll f) ufm1 (UF.toList uf2)
in
M.foldrWithKey (insertWith f) ufm1' m2
-- One key (from the joined set of keys) is given
mergeJoiningWithKey :: (Eq k, Hashable k, Show k, Show v, Show v1, Show v2)
=> (k -> v1 -> v2 -> (v, [(k, k)])) -- ^ How to merge values that are joined from both maps
-> (k -> v1 -> (v, [(k, k)])) -- ^ How to merge values of keys only in the first map
-> (k -> v2 -> (v, [(k, k)])) -- ^ How to merge values of keys only in the second map
-> (v1 -> v1 -> v1) -- ^ How to merge values that are joined within the first map
-> (v2 -> v2 -> v2) -- ^ How to merge values that are joined within the second map
-> (v -> v -> v) -- ^ Final extra joins, generated by prior higher order functions
-> UFMap k v1
-> UFMap k v2
-> UFMap k v
mergeJoiningWithKey fb f1 f2 fj1 fj2 j (UFMap uf1 m1) (UFMap uf2 m2) =
let
j_uf = UF.unionOfUFs uf1 uf2
-- We build a map where the keys are the values of j_f, and the values have type (Maybe v1, Maybe v2)
j_m1 = foldr (\(k, v) m ->
let uf_k = UF.find k j_uf in
case M.lookup uf_k m of
Nothing -> M.insert uf_k (Just v, Nothing) m
Just (Nothing, _) -> M.insert uf_k (Just v, Nothing) m
Just (Just v1, v2) -> M.insert uf_k (Just $ fj1 v v1, v2) m
) M.empty (M.toList m1)
j_m2 = foldr (\(k, v) m ->
let uf_k = UF.find k j_uf in
case M.lookup uf_k m of
Nothing -> M.insert uf_k (Nothing, Just v) m
Just (v1, Nothing) -> M.insert uf_k (v1, Just v) m
Just (v1, Just v2) -> M.insert uf_k (v1, Just $ fj2 v v2) m
) j_m1 (M.toList m2)
j_m = M.mapWithKey (\k (v1, v2) -> case (v1, v2) of
(Just v1', Just v2') -> fb k v1' v2'
(Just v1', Nothing) -> f1 k v1'
(Nothing, Just v2') -> f2 k v2'
_ -> error "merge: impossible state!") j_m2
ks = concat . M.elems $ M.map snd j_m
n_ufm = UFMap j_uf $ M.map fst j_m
n_ufm' = foldr (uncurry (join j)) n_ufm ks
in
n_ufm'
map :: (v1 -> v2) -> UFMap k v1 -> UFMap k v2
map f uf = uf { store = M.map f $ store uf }
null :: UFMap k v -> Bool
null = M.null . store
keys :: (Eq k, Hashable k) => UFMap k v -> [k]
keys = S.toList . keysSet
keysSet :: (Eq k, Hashable k) => UFMap k v -> S.HashSet k
keysSet uf =
(foldr S.union S.empty . UF.toSet . joined $ uf) `S.union` (M.keysSet . store $ uf)
joinedKeys :: UFMap k v -> UF.UnionFind k
joinedKeys = joined
elems :: UFMap k v -> [v]
elems = M.elems . store
member :: (Eq k, Hashable k) => k -> UFMap k v -> Bool
member k = M.member k . store
instance (Eq k, Eq v, Hashable k, Hashable v) => Eq (UFMap k v) where
x == y = toSet x == toSet y
instance (Eq k, Hashable k, Show k, Show v) => Show (UFMap k v) where
show uf = "fromList " ++ show (toList uf)
instance (Eq k, Hashable k, Read k, Read v) => Read (UFMap k v) where
readPrec = parens $
do expectP (L.Ident "fromList")
x <- step readListPrec
return (fromList x)
readListPrec = readListPrecDefault
instance (Arbitrary k, Arbitrary v, Eq k, Hashable k) => Arbitrary (UFMap k v) where
arbitrary = do
ufsz <- arbitrary
jnum <- arbitrary
ks <- Mon.replicateM ufsz arbitrary
vs <- Mon.replicateM ufsz arbitrary
js <- Mon.replicateM jnum arbitrary
let uf = foldr (uncurry insert) empty (zip ks vs)
ufm = foldr (uncurry (join const)) uf js
return ufm
shrink = P.map fromList . shrink . toList
-- shrink = P.map (fromList . filter (not . P.null . fst)) . shrink . toList