module Data.Cache.LRU.Internal where
import Prelude hiding ( last, lookup )
import Data.Map ( Map )
import qualified Data.Map as Map
#if MIN_VERSION_containers(0,5,0)
import qualified Data.Map.Strict as MapStrict
#endif
data LRU key val = LRU {
first :: !(Maybe key)
, last :: !(Maybe key)
, maxSize :: !(Maybe Integer)
, content :: !(Map key (LinkedVal key val))
} deriving Eq
instance (Ord key, Show key, Show val) => Show (LRU key val) where
show lru = "fromList " ++ show (toList lru)
instance Functor (LRU key) where
fmap f lru = lru { content = fmap (fmap f) . content $ lru }
data LinkedVal key val = Link {
value :: val
, prev :: !(Maybe key)
, next :: !(Maybe key)
} deriving Eq
instance Functor (LinkedVal key) where
fmap f lv = lv { value = f . value $ lv }
newLRU :: (Ord key) => Maybe Integer
-> LRU key val
newLRU (Just s) | s <= 0 = error "non-positive size LRU"
newLRU s = LRU Nothing Nothing s Map.empty
fromList :: Ord key => Maybe Integer
-> [(key, val)] -> LRU key val
fromList s l = appendAll $ newLRU s
where appendAll = foldr ins id l
ins (k, v) = (insert k v .)
toList :: Ord key => LRU key val -> [(key, val)]
toList lru = maybe [] (listLinks . content $ lru) $ first lru
where
listLinks m key =
let Just lv = Map.lookup key m
keyval = (key, value lv)
in case next lv of
Nothing -> [keyval]
Just nk -> keyval : listLinks m nk
insert :: Ord key => key -> val -> LRU key val -> LRU key val
insert key val lru = maybe emptyCase nonEmptyCase $ first lru
where
contents = content lru
full = maybe False (fromIntegral (Map.size contents) ==) $ maxSize lru
present = key `Map.member` contents
emptyCase = LRU fl fl (maxSize lru) m'
where
fl = Just key
lv = Link val Nothing Nothing
m' = Map.insert key lv contents
nonEmptyCase firstKey = if present then hitSet else add firstKey
hitSet = hit' key lru'
where lru' = lru { content = contents' }
contents' = adjust' (\v -> v {value = val}) key contents
add firstKey = if full then lru'' else lru'
where
firstLV' = Link val Nothing $ Just firstKey
contents' = Map.insert key firstLV' .
adjust' (\v -> v { prev = Just key }) firstKey $
contents
lru' = lru { first = Just key, content = contents' }
Just lastKey = last lru'
Just lastLV = Map.lookup lastKey contents'
contents'' = Map.delete lastKey contents'
lru'' = delete' lastKey lru' contents'' lastLV
lookup :: Ord key => key -> LRU key val -> (LRU key val, Maybe val)
lookup key lru = case Map.lookup key $ content lru of
Nothing -> (lru, Nothing)
Just lv -> (hit' key lru, Just . value $ lv)
delete :: Ord key => key -> LRU key val -> (LRU key val, Maybe val)
delete key lru = maybe (lru, Nothing) delete'' mLV
where
delete'' lv = (delete' key lru cont' lv, Just $ value lv)
(mLV, cont') = Map.updateLookupWithKey (\_ _ -> Nothing) key $ content lru
pop :: Ord key => LRU key val -> (LRU key val, Maybe (key, val))
pop lru = if size lru == 0 then (lru, Nothing) else (lru', Just pair)
where
Just lastKey = last lru
(lru', Just lastVal) = delete lastKey lru
pair = (lastKey, lastVal)
size :: LRU key val -> Int
size = Map.size . content
hit' :: Ord key => key -> LRU key val -> LRU key val
hit' key lru = if key == firstKey then lru else notFirst
where Just firstKey = first lru
Just lastKey = last lru
Just lastLV = Map.lookup lastKey conts
conts = content lru
notFirst = if key == lastKey then replaceLast else replaceMiddle
adjFront = adjust' (\v -> v { prev = Just key}) firstKey .
adjust' (\v -> v { prev = Nothing
, next = first lru }) key
replaceLast = lru { first = Just key
, last = prev lastLV
, content = cLast
}
Just pKey = prev lastLV
cLast = adjust' (\v -> v { next = Nothing }) pKey . adjFront $ conts
replaceMiddle = lru { first = Just key
, content = cMid
}
Just keyLV = Map.lookup key conts
Just prevKey = prev keyLV
Just nextKey = next keyLV
cMid = adjust' (\v -> v { next = Just nextKey }) prevKey .
adjust' (\v -> v { prev = Just prevKey }) nextKey .
adjFront $ conts
delete' :: Ord key => key
-> LRU key val
-> Map key (LinkedVal key val)
-> LinkedVal key val
-> LRU key val
delete' key lru cont' lv = if Map.null cont' then deleteOnly else deleteOne
where
deleteOnly = lru { first = Nothing
, last = Nothing
, content = cont'
}
Just firstKey = first lru
deleteOne = if firstKey == key then deleteFirst else deleteNotFirst
deleteFirst = lru { first = next lv
, content = contFirst
}
Just nKey = next lv
contFirst = adjust' (\v -> v { prev = Nothing }) nKey cont'
Just lastKey = last lru
deleteNotFirst = if lastKey == key then deleteLast else deleteMid
deleteLast = lru { last = prev lv
, content = contLast
}
Just pKey = prev lv
contLast = adjust' (\v -> v { next = Nothing}) pKey cont'
deleteMid = lru { content = contMid }
contMid = adjust' (\v -> v { next = next lv }) pKey .
adjust' (\v -> v { prev = prev lv }) nKey $
cont'
adjust' :: Ord k => (a -> a) -> k -> Map k a -> Map k a
#if MIN_VERSION_containers(0,5,0)
adjust' = MapStrict.adjust
#else
adjust' f k m = Map.insertWith' (\_ o -> f o) k (error "adjust' used wrongly") m
#endif
valid :: Ord key => LRU key val -> Bool
valid lru = maybe True (fromIntegral (size lru) <=) (maxSize lru) &&
reverse orderedKeys == reverseKeys &&
size lru == length orderedKeys &&
all (`Map.member` contents) orderedKeys
where contents = content lru
orderedKeys = traverse next . first $ lru
traverse _ Nothing = []
traverse f (Just k) = let Just k' = Map.lookup k contents
in k : (traverse f . f $ k')
reverseKeys = traverse prev . last $ lru