module Hans.HashTable (
HashTable(),
newHashTable,
lookup,
delete, deletes,
mapHashTable, mapHashTableM_,
filterHashTable,
alter,
keys,
hasKey,
size,
) where
import Prelude hiding (lookup)
import Control.Monad (replicateM,forM_)
import Data.Array (Array,listArray,(!))
import Data.Function (on)
import Data.Hashable (Hashable,hash)
import Data.IORef (IORef,newIORef,atomicModifyIORef',readIORef)
import qualified Data.List as List
import Data.Ord (comparing)
data HashTable k a =
HashTable { htSize :: !Int
, htBuckets :: !(Array Int (Bucket k a))
}
type Bucket k a = IORef [(k,a)]
cons' :: a -> [a] -> [a]
cons' h tl = h `seq` tl `seq` (h:tl)
nfList :: [a] -> ()
nfList [] = ()
nfList (a:rest) = a `seq` nfList rest
nfListId :: [a] -> [a]
nfListId xs = nfList xs `seq` xs
newHashTable :: (Eq k, Hashable k) => Int -> IO (HashTable k a)
newHashTable htSize =
do buckets <- replicateM htSize (newIORef [])
return HashTable { htBuckets = listArray (0,htSize 1) buckets
, .. }
mapBuckets :: (Eq k, Hashable k) => ([(k,a)] -> [(k,a)]) -> HashTable k a -> IO ()
mapBuckets f HashTable { .. } = go 0
where
update bucket = (f bucket, ())
go ix | ix < htSize = do atomicModifyIORef' (htBuckets ! ix) update
go (succ ix)
| otherwise = return ()
filterHashTable :: (Eq k, Hashable k)
=> (k -> a -> Bool) -> HashTable k a -> IO ()
filterHashTable p = mapBuckets go
where
go [] = []
go (x@(k,a):rest) | p k a = cons' x (go rest)
| otherwise = go rest
mapHashTableM_ :: (Eq k, Hashable k)
=> (k -> a -> IO ()) -> HashTable k a -> IO ()
mapHashTableM_ f HashTable { .. } = go 0
where
go ix | ix < htSize = do bs <- readIORef (htBuckets ! ix)
mapM_ (\(k,a) -> f k a) bs
go (ix + 1)
| otherwise = return ()
mapHashTable :: (Eq k, Hashable k) => (k -> a -> a) -> HashTable k a -> IO ()
mapHashTable f = mapBuckets go
where
go [] = []
go ((k,a):rest) = cons' ((,) k $! f k a) (go rest)
getBucket :: Hashable k => HashTable k a -> k -> Bucket k a
getBucket HashTable { .. } k = htBuckets ! key
where
key = hash k `mod` htSize
modifyBucket :: Hashable k
=> HashTable k a -> k -> ([(k,a)] -> ([(k,a)],b)) -> IO b
modifyBucket ht k = atomicModifyIORef' (getBucket ht k)
lookup :: (Eq k, Hashable k) => k -> HashTable k a -> IO (Maybe a)
lookup k ht =
do bucket <- readIORef (getBucket ht k)
return $! List.lookup k bucket
delete :: (Eq k, Hashable k) => k -> HashTable k a -> IO ()
delete k ht = modifyBucket ht k (\ bucket -> (nfListId (removeEntry bucket), ()))
where
removeEntry (entry @ (k',_):rest)
| k' == k = rest
| otherwise = let rest' = removeEntry rest
in rest' `seq` (entry : rest')
removeEntry [] = []
deletes :: (Eq k, Hashable k) => [k] -> HashTable k a -> IO ()
deletes ks ht =
forM_ groups $ \ grp ->
case grp of
(_,ix):_ -> atomicModifyIORef' (htBuckets ht ! ix)
(\ bucket -> (bucket `without` grp, ()))
_ -> return ()
where
groups = List.groupBy ((==) `on` snd)
$ List.sortBy (comparing snd)
$ [ (k, hash k `mod` htSize ht) | k <- ks ]
without = List.foldl' (\b (k,_) -> remove k b)
remove k = go
where
go [] = []
go (e@(k',_):rest) | k == k' = rest
| otherwise = e:go rest
alter :: (Eq k, Hashable k)
=> (Maybe a -> (Maybe a, b)) -> k -> HashTable k a -> IO b
alter f k ht = modifyBucket ht k (update nfListId)
where
update mkBucket (e@(k',a):rest)
| k == k' = finish mkBucket (Just a) rest
| otherwise = update (\l -> mkBucket ( e : l )) rest
update mkBucket [] = finish mkBucket Nothing []
finish mkBucket mb rest =
case f mb of
(Just a, b) -> (mkBucket ((k,a):rest), b)
(Nothing,b) -> (mkBucket rest , b)
size :: HashTable k a -> IO Int
size HashTable { .. } = loop 0 0
where
loop acc ix | ix < htSize = do bucket <- readIORef (htBuckets ! ix)
(loop $! acc + length bucket) (ix + 1)
| otherwise = return acc
keys :: HashTable k a -> IO [k]
keys HashTable { .. } = go [] 0
where
go acc ix | ix < htSize = do bucket <- readIORef (htBuckets ! ix)
go (map fst bucket ++ acc) (ix + 1)
| otherwise = return acc
hasKey :: (Eq k, Hashable k) => k -> HashTable k a -> IO Bool
hasKey k ht =
do b <- readIORef (getBucket ht k)
return $! any (\ (k',_) -> k == k') b