{-# LANGUAGE RankNTypes #-} -- | @SkipList@s are comprised of a list and an index on top of it -- which makes deep indexing into the list more efficient (O(log n)). -- They achieve this by essentially memoizing the @tail@ function to -- create a balanced tree. -- -- NOTE: @SkipList@s are /amortized/ efficient, see the benchmarks for -- performance results. module Data.SkipList ( SkipList , toSkipList , lookup ) where import Prelude hiding (lookup) -- | A SkipIndex stores "pointers" into the tail of a list for efficient -- deep indexing. If you have -- SkipIndex ls i -- and the quantization is `q` then the elements of `q` are "pointers" -- into every `q`th tail of the list. For example, if `q` = 2 and -- `ls = [1,2,3,4,5,6,7]`, then the frist level of `i` contains: -- [1,2,3,4,5,6,7], [3,4,5,6,7], [5,6,7], [7] -- the next level of `i` conceptually contains: -- [1,2,3,4,5,6,7], [5,6,7] -- Note however, that these are not lists, but rather references to -- @SkipIndex@s from `i` data SkipIndex a = SkipIndex ![a] (SkipIndex (SkipIndex a)) -- | @SkipList@s are lists that support amortized efficient indexing. data SkipList a = SkipList !Int (SkipIndex a) instance Functor SkipList where fmap f (SkipList q (SkipIndex raw _)) = toSkipList q $ f <$> raw instance Foldable SkipList where foldMap f (SkipList _ (SkipIndex ls _)) = foldMap f ls -- | Convert a list to a @SkipList@. toSkipList :: Int -- ^ The step size in the index. -> [a] -- ^ The list to convert. -> SkipList a toSkipList quant = SkipList quant . toSkipIndex quant -- | Build the infinite tree of skips. toSkipIndex :: Int -> [a] -> SkipIndex a toSkipIndex quant | quant > 1 = \ ls -> let self = SkipIndex ls $ toSkipIndex quant (self : rest) rest = toSkipIndex quant <$> fastTails ls in self | otherwise = error "Can not make SkipList with quantization <= 1" where fastTails ls = dropped : fastTails dropped where dropped = drop quant ls -- | Lookup in a @SkipList@. lookup :: SkipList a -> Int -> a lookup (SkipList q (SkipIndex ls index)) i | i >= q = get q q (\ (SkipIndex a _) -> (!!) a) index i | otherwise = ls !! i -- | Get an element from a @SkipIndex@. get :: Int -- ^ The step increment in the index -> Int -- ^ The current step size -> (b -> Int -> a) -- ^ Continuation to call with the result -> SkipIndex b -- ^ The index to look in -> Int -- ^ The position to look for -> a get q stepSize kont (SkipIndex here next) n | n >= q * stepSize = get q (q*stepSize) (get q stepSize kont) next n | otherwise = kont (here !! idx) rest where idx = n `div` stepSize rest = n `mod` stepSize