module Data.LruCache
( LruCache
, Priority
, empty
, insert
, insertView
, lookup
) where
import qualified Data.HashPSQ as HashPSQ
import Data.Hashable (Hashable)
import Data.List.Compat (sortOn)
import Data.Maybe (isNothing)
import Prelude hiding (lookup)
import Data.LruCache.Internal
empty :: Int -> LruCache k v
empty capacity
| capacity < 1 = error "LruCache.empty: capacity < 1"
| otherwise =
LruCache
{ lruCapacity = capacity
, lruSize = 0
, lruTick = 0
, lruQueue = HashPSQ.empty
}
trim' :: (Hashable k, Ord k) => LruCache k v -> (Maybe (k, v), LruCache k v)
trim' c
| lruTick c == maxBound =
let queue' = HashPSQ.fromList . compress . HashPSQ.toList $ lruQueue c
in trim' $!
c { lruTick = fromIntegral (lruSize c)
, lruQueue = queue'
}
| lruSize c > lruCapacity c =
let Just (k, _, v) = HashPSQ.findMin (lruQueue c)
c' = c { lruSize = lruSize c 1
, lruQueue = HashPSQ.deleteMin (lruQueue c)
}
in seq c' (Just (k, v), c')
| otherwise = (Nothing, c)
compress :: [(k,Priority,v)] -> [(k,Priority,v)]
compress q =
let sortedQ = sortOn (\(_,p,_) -> p) q
in zipWith (\(k,_,v) p -> (k,p,v)) sortedQ [1..]
trim :: (Hashable k, Ord k) => LruCache k v -> LruCache k v
trim c
| lruTick c == maxBound = empty (lruCapacity c)
| lruSize c > lruCapacity c =
c { lruSize = lruSize c 1
, lruQueue = HashPSQ.deleteMin (lruQueue c)
}
| otherwise = c
insert :: (Hashable k, Ord k) => k -> v -> LruCache k v -> LruCache k v
insert key val c =
trim $!
let (mbOldVal,queue) = HashPSQ.insertView key (lruTick c) val (lruQueue c)
in c { lruSize = if isNothing mbOldVal
then lruSize c + 1
else lruSize c
, lruTick = lruTick c + 1
, lruQueue = queue
}
insertView :: (Hashable k, Ord k) => k -> v -> LruCache k v -> (Maybe (k, v), LruCache k v)
insertView key val cache =
let (mbOldVal,queue) =
HashPSQ.insertView key (lruTick cache) val (lruQueue cache)
in trim' $! cache
{ lruSize = if isNothing mbOldVal
then lruSize cache + 1
else lruSize cache
, lruTick = lruTick cache + 1
, lruQueue = queue
}
lookup :: (Hashable k, Ord k) => k -> LruCache k v -> Maybe (v, LruCache k v)
lookup k c =
case HashPSQ.alter lookupAndBump k (lruQueue c) of
(Nothing, _) -> Nothing
(Just x, q) ->
let !c' = trim $ c {lruTick = lruTick c + 1, lruQueue = q}
in Just (x, c')
where
lookupAndBump Nothing = (Nothing, Nothing)
lookupAndBump (Just (_, x)) = (Just x, Just ((lruTick c), x))