packages feed

LRU (empty) → 0.1

raw patch · 3 files changed

+160/−0 lines, 3 filesdep +QuickCheckdep +basebuild-type:Customsetup-changed

Dependencies added: QuickCheck, base

Files

+ Data/LRU.hs view
@@ -0,0 +1,147 @@+-- | This module implements a least-recently-used structure. Conceptually,+--   it's a list of values where the head of the list is the most recently used+--   value. When a value is used, it's moved from its place in the list to the+--   head of the list. The last element in the list is thus the+--   least-recently-used value.+--+--   This structure is often used in caches to decide which values to evict when+--   the cache becomes full.+--+--   This module uses a Map to implement the LRU efficiently and thus there's the+--   requirement that the elements of the LRU be instances of Ord, which a more+--   general (but slower) LRU implementation could avoid.++module Data.LRU (LRU, empty, null, size, hit, delete, toList, member, pop, last) where++import Prelude hiding (last, null)+import qualified Data.Map as Map+import Data.Maybe (fromJust, isNothing)++-- Impementation notes:+--+-- The LRU is a Map mapping the element to an Item. An Item is a link in a+-- double-linked list. We also hold the values of the elements at the head and+-- tail of the list. So, to get the list you can take the head value and look+-- that up in the map. That will give you the prev and next pointers (the prev+-- pointer will be Nothing in the case of the head element). Taking the next+-- pointer value, you can lookup again to get the next element in the list+-- and so on.++-- | A double-linked list element. Contains the values of the elements before+-- and after this element (resp)+data (Ord a) => Item a = Item (Maybe a) (Maybe a)++-- | An LRU. Contains the head element, last element and the map from elements+-- to their Items+data (Ord a) => LRU a = LRU (Maybe a) (Maybe a) (Map.Map a (Item a))++instance (Ord a, Show a) => Show (LRU a) where+  show x = "LRU" ++ show (toList x)++-- | Returns an empty LRU. O(1)+empty :: (Ord a) => LRU a+empty = LRU Nothing Nothing Map.empty++-- | Returns True iff the LRU is empty. O(1)+null :: (Ord a) => LRU a -> Bool+null (LRU Nothing _ _) = True+null _ = False++-- | Returns the number of elements in the LRU. O(1)+size :: (Ord a) => LRU a -> Int+size (LRU _ _ map) = Map.size map++-- | Utility function. Updates a map entry so that the prev pointer points to+--   a new value+updatePrev :: (Ord a)+              => a  -- ^ the entry to update+              -> (Maybe a)  -- ^ the new prev pointer value+              -> Map.Map a (Item a)  -- ^ the map to update+              -> Map.Map a (Item a)+updatePrev toupdate newvalue =+  Map.adjust (\(Item _ next) -> Item newvalue next) toupdate++-- | Utility function. Same as updatePrev, but with the other pointer+updateNext :: (Ord a) => a -> (Maybe a) -> Map.Map a (Item a) -> Map.Map a (Item a)+updateNext toupdate newvalue =+  Map.adjust (\(Item prev _) -> Item prev newvalue) toupdate++-- | Inserts a new element into an LRU. O(log n) (not exported, see hit)+insert :: (Ord a) => a -> LRU a -> LRU a+insert value (LRU head tail map) = LRU newhead newtail newmap where+  (newmap, newhead, newtail) =+    case tail of+         -- We are inserting the first element+         Nothing -> (Map.singleton value (Item Nothing Nothing), Just value, Just value)+         -- We are inserting into a non-empty LRU. We change the prev pointer+         -- of the current head to point to the new head and insert the new+         -- head, with a next value pointing to the previous head.+         (Just x) -> (Map.insert value (Item Nothing head) $+                      updatePrev (fromJust head) (Just value) map,+                      Just value, tail)++-- | Insert a value into an LRU. If the value is already in the LRU, it's+--   moved to the head of the list. O(log n)+hit :: (Ord a) => a -> LRU a -> LRU a+hit value lru@(LRU head tail map) = r where+  r = case Map.lookup value map of+           Just (Item (Just prev) (Just next)) -> -- it's in the middle+             LRU (Just value) tail $ updateHit $ updateNext prev (Just next) $+             updatePrev next (Just prev) $ updatePrev (fromJust head) (Just value) map+           Just (Item (Just prev) Nothing) ->  -- it's the very last element+             LRU (Just value) (Just prev) $ updateHit $ updateNext prev Nothing $ updatePrev (fromJust head) (Just value) map+           Just (Item Nothing _) -> lru  -- it's already at the top+           Nothing -> insert value lru+  -- | Set the item for a given value so that it's the head of the list, pointing to the old head+  updateHit = Map.insert value (Item Nothing head)++-- | Returns a list of the members of the LRU in order, newest first. O(n(log n))+toList :: (Ord a) => LRU a -> [a]+toList (LRU head tail map) =+  if Map.null map+    then []+    else (fromJust head) : (inner (fromJust head)) where+         inner current = case next of+                              (Just value) -> value : (inner value)+                              Nothing -> []+                         where+                         Just (Item prev next) = Map.lookup current map++-- | Returns true iff the given element is in the LRU. O(log n)+member :: (Ord a) => a -> LRU a -> Bool+member value (LRU _ _ map) = Map.member value map++-- | Removes an element from the LRU, if it exists. O(log n)+delete :: (Ord a) => a -> LRU a -> LRU a+delete value lru@(LRU head tail map) =+  case head of+    Nothing -> lru  -- empty case+    (Just x)+      | x == value ->+        -- it's the head item+        let (Just (Item _ next), map') = Map.updateLookupWithKey (const $ const Nothing) value map in+        LRU next (if isNothing next then Nothing else tail) map'+      | otherwise ->+          case tail of+            (Just x)+              | x == value ->  -- removing the tail+                pop lru+              | otherwise ->+                  -- removing a middle element+                  LRU head tail $ updateNext (fromJust prev) next $ updatePrev (fromJust next) prev map' where+                  (Just (Item prev next), map') = Map.updateLookupWithKey (const $ const Nothing) value map++-- | Return the last element of the LRU. O(1)+last :: (Ord a, Monad m) => LRU a -> m a+last (LRU _ tail _) = if isNothing tail then fail "Empty LRU" else return $ fromJust tail++-- | Remove the last element of the LRU. Errors out if the LRU is empty. O(log n)+pop :: (Ord a) => LRU a -> LRU a+pop (LRU head (Just tailvalue) map) = newlru where+  newlru = if isOnlyElement+             then empty+             else LRU head newtail newmap+  Just (Item prev _) = Map.lookup tailvalue map+  isOnlyElement = isNothing prev+  newtail = prev+  newmap = updateNext (fromJust prev) Nothing $ Map.delete tailvalue map
+ LRU.cabal view
@@ -0,0 +1,9 @@+Name: LRU+Version: 0.1+License: BSD3+Author: Adam Langley+Homepage: http://www.imperialviolet.org/lru+Stability: experimental+Synopsis: Implements an LRU data structure+Build-Depends: base, QuickCheck+Exposed-modules: Data.LRU
+ Setup.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++import Distribution.Simple+main = defaultMain