dawg-0.9: Data/DAWG/HashMap.hs
{-# LANGUAGE RecordWildCards #-}
-- | A map from hashable keys to values.
module Data.DAWG.HashMap
( Hash (..)
, HashMap (..)
, empty
, lookup
, insertUnsafe
, lookupUnsafe
, deleteUnsafe
) where
import Prelude hiding (lookup)
import Control.Applicative ((<$>), (<*>))
import Data.Binary (Binary, Get, put, get)
import qualified Data.Map as M
import qualified Data.IntMap as I
fromJust :: Maybe a -> a
fromJust (Just x) = x
fromJust Nothing = error "fromJust: Nothing"
{-# INLINE fromJust #-}
-- | Class for types which provide hash values.
class Ord a => Hash a where
hash :: a -> Int
-- | Value in a HashMap.
data Value a b
= Single !a !b
| Multi !(M.Map a b)
deriving (Show, Eq, Ord)
instance (Ord a, Binary a, Binary b) => Binary (Value a b) where
put (Single x y) = put (1 :: Int) >> put x >> put y
put (Multi m) = put (2 :: Int) >> put m
get = do
x <- get :: Get Int
case x of
1 -> Single <$> get <*> get
_ -> Multi <$> get
-- | Find element associated to a value key.
find :: Ord a => a -> Value a b -> Maybe b
find x (Single x' y) = if x == x'
then Just y
else Nothing
find x (Multi m) = M.lookup x m
-- | Assumption: element is a member of the 'Value'.
findUnsafe :: Ord a => a -> Value a b -> Maybe b
findUnsafe _ (Single _ y) = Just y -- unsafe
findUnsafe x (Multi m) = M.lookup x m
-- | Convert map into a 'Single' form if possible.
trySingle :: Ord a => M.Map a b -> Value a b
trySingle m = if M.size m == 1
then (uncurry Single) (M.findMin m)
else Multi m
-- | Insert element into a value.
embed :: Ord a => a -> b -> Value a b -> Value a b
embed x y (Single x' y') = Multi $ M.fromList [(x, y), (x', y')]
embed x y (Multi m) = Multi $ M.insert x y m
-- | Delete element from a value. Return 'Nothing' if the resultant
-- value is empty.
ejectUnsafe :: Ord a => a -> Value a b -> Maybe (Value a b)
ejectUnsafe _ (Single _ _) = Nothing -- unsafe
ejectUnsafe x (Multi m) = (Just . trySingle) (M.delete x m)
-- | A map from /a/ keys to /b/ elements where keys instantiate the
-- 'Hash' type class. Key/element pairs are kept in 'Value' objects
-- which takes care of potential hash collisions.
data HashMap a b = HashMap
{ size :: {-# UNPACK #-} !Int
, hashMap :: !(I.IntMap (Value a b)) }
deriving (Show, Eq, Ord)
instance (Ord a, Binary a, Binary b) => Binary (HashMap a b) where
put HashMap{..} = put size >> put hashMap
get = HashMap <$> get <*> get
-- | Empty map.
empty :: HashMap a b
empty = HashMap 0 I.empty
-- | Lookup element in the map.
lookup :: Hash a => a -> HashMap a b -> Maybe b
lookup x (HashMap _ m) = I.lookup (hash x) m >>= find x
-- | Assumption: element is present in the map.
lookupUnsafe :: Hash a => a -> HashMap a b -> b
lookupUnsafe x (HashMap _ m) = fromJust (I.lookup (hash x) m >>= findUnsafe x)
-- | Insert a new element. The function doesn't check
-- if the element was already present in the map.
insertUnsafe :: Hash a => a -> b -> HashMap a b -> HashMap a b
insertUnsafe x y (HashMap n m) =
let i = hash x
f (Just v) = embed x y v
f Nothing = Single x y
in HashMap (n + 1) $ I.alter (Just . f) i m
-- | Assumption: element is present in the map.
deleteUnsafe :: Hash a => a -> HashMap a b -> HashMap a b
deleteUnsafe x (HashMap n m) =
HashMap (n - 1) $ I.update (ejectUnsafe x) (hash x) m