critbit-0.1.0.0: Data/CritBit/Core.hs
{-# LANGUAGE BangPatterns, RecordWildCards, ScopedTypeVariables #-}
-- |
-- Module : Data.CritBit.Core
-- Copyright : (c) Bryan O'Sullivan 2013
-- License : BSD-style
-- Maintainer : bos@serpentine.com
-- Stability : experimental
-- Portability : GHC
--
-- "Core" functions that implement the crit-bit tree algorithms.
--
-- I plopped these functions into their own source file to demonstrate
-- just how small the core of the crit-bit tree concept is.
--
-- I have also commented this module a bit more heavily than I usually
-- do, in the hope that the comments will make the code more
-- approachable to less experienced Haskellers.
module Data.CritBit.Core
(
-- * Public functions
insertWithKey
, insertLookupWithKey
, insertLookupGen
, lookupWith
, updateLookupWithKey
, leftmost
, rightmost
-- * Internal functions
, Diff(..)
, diffOrd
, followPrefixes
, followPrefixesFrom
, followPrefixesByteFrom
, findPosition
-- ** Predicates
, onLeft
, above
-- ** Smart constructors
, setLeft
, setRight
, setLeft'
, setRight'
, internal
) where
import Data.Bits ((.|.), (.&.), complement, shiftR, xor)
import Data.CritBit.Types.Internal
-- | /O(k)/. Insert with a function, combining key, new value and old value.
-- @'insertWithKey' f key value cb@
-- will insert the pair (key, value) into cb if key does not exist in the map.
-- If the key does exist, the function will insert the pair
-- @(key,f key new_value old_value)@.
-- Note that the key passed to f is the same key passed to insertWithKey.
--
-- > let f key new_value old_value = byteCount key + new_value + old_value
-- > insertWithKey f "a" 1 (fromList [("a",5), ("b",3)]) == fromList [("a",7), ("b",3)]
-- > insertWithKey f "c" 1 (fromList [("a",5), ("b",3)]) == fromList [("a",5), ("b",3), ("c",1)]
-- > insertWithKey f "a" 1 empty == singleton "a" 1
insertWithKey :: CritBitKey k => (k -> v -> v -> v) -> k -> v -> CritBit k v
-> CritBit k v
insertWithKey f k v m = insertLookupGen (flip const) f k v m
{-# INLINABLE insertWithKey #-}
-- | /O(k)/. Combines insert operation with old value retrieval.
-- The expression (@'insertLookupWithKey' f k x map@)
-- is a pair where the first element is equal to (@'lookup' k map@)
-- and the second element equal to (@'insertWithKey' f k x map@).
--
-- > let f key new_value old_value = length key + old_value + new_value
-- > insertLookupWithKey f "a" 2 (fromList [("a",5), ("b",3)]) == (Just 5, fromList [("a",8), ("b",3)])
-- > insertLookupWithKey f "c" 2 (fromList [(5,"a"), (3,"b")]) == (Nothing, fromList [("a",5), ("b",3), ("c",2)])
-- > insertLookupWithKey f "a" 2 empty == (Nothing, singleton "a" 2)
--
-- This is how to define @insertLookup@ using @insertLookupWithKey@:
--
-- > let insertLookup kx x t = insertLookupWithKey (\_ a _ -> a) kx x t
-- > insertLookup "a" 1 (fromList [("a",5), ("b",3)]) == (Just 5, fromList [("a",1), ("b",3)])
-- > insertLookup "c" 1 (fromList [("a",5), ("b",3)]) == (Nothing, fromList [("a",5), ("b",3), ("c",1)])
insertLookupWithKey :: CritBitKey k
=> (k -> v -> v -> v)
-> k -> v -> CritBit k v
-> (Maybe v, CritBit k v)
insertLookupWithKey f k v m = insertLookupGen (,) f k v m
{-# INLINABLE insertLookupWithKey #-}
-- | General function used to implement all insert functions.
insertLookupGen :: CritBitKey k
=> (Maybe v -> CritBit k v -> a)
-> (k -> v -> v -> v)
-> k -> v -> CritBit k v -> a
insertLookupGen ret f !k v m = findPosition ret' finish setLeft setRight k m
where
finish _ Empty = Leaf k v
finish diff (Leaf _ v') | diffOrd diff == EQ = Leaf k $ f k v v'
finish diff node = internal diff node (Leaf k v)
ret' a b = ret a (CritBit b)
{-# INLINE insertLookupGen #-}
-- | Common part of key finding/insert functions
findPosition :: (CritBitKey k)
=> (Maybe v -> r -> t) -> (Diff -> Node k v -> r)
-> (Node k v -> r -> r) -> (Node k v -> r -> r)
-> k -> CritBit k v -> t
findPosition ret finish toLeft toRight k (CritBit root) = go root
where
go i@(Internal {..})
| k `onLeft` i = go ileft
| otherwise = go iright
go (Leaf lk lv)
| diffOrd diff == EQ = ret (Just lv) $ rewalk root
| otherwise = ret Nothing $ rewalk root
where
rewalk i@(Internal left right _ _)
| diff `above` i = finish diff i
| k `onLeft` i = toLeft i (rewalk left )
| otherwise = toRight i (rewalk right)
rewalk i = finish diff i
diff = followPrefixes k lk
go Empty = ret Nothing $ finish undefined Empty
{-# INLINE findPosition #-}
data Diff = Diff {-# UNPACK #-} !Int
{-# UNPACK #-} !BitMask
{-# UNPACK #-} !BitMask
-- | Smart consturctor for Internal nodes
internal :: Diff -> Node k v -> Node k v -> Node k v
internal diff@(Diff byte bits _) child1 child2 = case diffOrd diff of
LT -> Internal child1 child2 byte bits
GT -> Internal child2 child1 byte bits
EQ -> error "Data.CritBit.Cord.internal: Equal."
{-# INLINE internal #-}
setLeft :: Node k v -> Node k v -> Node k v
setLeft i@(Internal{}) node = i { ileft = node }
setLeft _ _ = error "Data.CritBit.Core.setLeft: Non-Internal node"
{-# INLINE setLeft #-}
setRight :: Node k v -> Node k v -> Node k v
setRight i@(Internal{}) node = i { iright = node }
setRight _ _ = error "Data.CritBit.Core.setRight: Non-Internal node"
{-# INLINE setRight #-}
setLeft' :: Node k v -> Node k v -> Node k v
setLeft' i@(Internal{}) Empty = iright i
setLeft' i@(Internal{}) child = i { ileft = child }
setLeft' _ _ = error "Data.CritBit.Core.setLeft': Non-internal node"
{-# INLINE setLeft' #-}
setRight' :: Node k v -> Node k v -> Node k v
setRight' i@(Internal{}) Empty = ileft i
setRight' i@(Internal{}) child = i { iright = child }
setRight' _ _ = error "Data.CritBit.Core.alter.setRight': Non-internal node"
{-# INLINE setRight' #-}
above :: Diff -> Node k v -> Bool
above (Diff dbyte dbits _) (Internal _ _ byte bits) =
dbyte < byte || dbyte == byte && dbits < bits
above _ _ = error "Data.CritBit.Core.above: Non-Internal node"
{-# INLINE above #-}
lookupWith :: (CritBitKey k) =>
a -- ^ Failure continuation
-> (v -> a) -- ^ Success continuation
-> k
-> CritBit k v -> a
-- We use continuations here to avoid reimplementing the lookup
-- algorithm with trivial variations.
lookupWith notFound found k (CritBit root) = go root
where
go i@(Internal {..})
| k `onLeft` i = go ileft
| otherwise = go iright
go (Leaf lk v) | k == lk = found v
go _ = notFound
{-# INLINE lookupWith #-}
-- | /O(k)/. Lookup and update; see also 'updateWithKey'.
-- This function returns the changed value if it is updated, or
-- the original value if the entry is deleted.
--
-- > let f k x = if x == 5 then Just (x + fromEnum (k < "d")) else Nothing
-- > updateLookupWithKey f "a" (fromList [("b",3), ("a",5)]) == (Just 6, fromList [("a", 6), ("b",3)])
-- > updateLookupWithKey f "c" (fromList [("a",5), ("b",3)]) == (Nothing, fromList [("a",5), ("b",3)])
-- > updateLookupWithKey f "b" (fromList [("a",5), ("b",3)]) == (Just 3, singleton "a" 5)
updateLookupWithKey :: (CritBitKey k) => (k -> v -> Maybe v) -> k
-> CritBit k v -> (Maybe v, CritBit k v)
-- Once again with the continuations! It's somewhat faster to do
-- things this way than to expicitly unwind our recursion once we've
-- found the leaf to delete. It's also a ton less code.
--
-- (If you want a good little exercise, rewrite this function without
-- using continuations, and benchmark the two versions.)
updateLookupWithKey f k t@(CritBit root) = go root (CritBit Empty) CritBit
where
go i@(Internal left right _ _) _ cont = dispatch i left right cont
go (Leaf lk lv) other cont
| k == lk = case f lk lv of
Just lv' -> (Just lv', cont $! Leaf lk lv')
Nothing -> (Just lv, other)
| otherwise = (Nothing, t)
go Empty _ _ = (Nothing, t)
{-# INLINE go #-}
dispatch i left right cont
| k `onLeft` i = go left (cont right) $ (cont $!) . setLeft' i
| otherwise = go right (cont left) $ (cont $!) . setRight' i
{-# INLINABLE updateLookupWithKey #-}
-- | Determine whether specified key is on the left subtree of the
-- 'Internal' node.
onLeft :: (CritBitKey k) => k -> Node k v -> Bool
onLeft k (Internal _ _ byte bits) =
(1 + (bits .|. getByte k byte)) `shiftR` 9 == 0
onLeft _ _ = error "Data.CritBit.Core.onLeft: Non-Internal node"
{-# INLINE onLeft #-}
-- | Given a diff of two keys determines result of comparison of them.
diffOrd :: Diff -> Ordering
diffOrd (Diff _ bits c)
| bits == 0x1ff = EQ
| (1 + (bits .|. c)) `shiftR` 9 == 0 = LT
| otherwise = GT
{-# INLINE diffOrd #-}
-- | Figure out the byte offset at which the key we are interested in
-- differs from the leaf we reached when we initially walked the tree.
--
-- We return some auxiliary stuff that we'll bang on to help us figure
-- out which direction to go in to insert a new node.
followPrefixes :: (CritBitKey k) =>
k -- ^ The key from "outside" the tree.
-> k -- ^ Key from the leaf we reached.
-> Diff
followPrefixes = followPrefixesFrom 0
{-# INLINE followPrefixes #-}
-- | Figure out the offset of the first different byte in two keys,
-- starting from specified position.
--
-- We return some auxiliary stuff that we'll bang on to help us figure
-- out which direction to go in to insert a new node.
followPrefixesFrom :: (CritBitKey k) =>
Int -- ^ Positition to start from
-> k -- ^ First key.
-> k -- ^ Second key.
-> Diff
followPrefixesFrom !position !k !l = Diff n (maskLowerBits (b `xor` c)) c
where
n = followPrefixesByteFrom position k l
b = getByte k n
c = getByte l n
maskLowerBits v = (n3 .&. complement (n3 `shiftR` 1)) `xor` 0x1FF
where
n3 = n2 .|. (n2 `shiftR` 8)
n2 = n1 .|. (n1 `shiftR` 4)
n1 = n0 .|. (n0 `shiftR` 2)
n0 = v .|. (v `shiftR` 1)
{-# INLINE followPrefixesFrom #-}
-- | Figure out the offset of the first different byte in two keys,
-- starting from specified position.
followPrefixesByteFrom :: (CritBitKey k) =>
Int -- ^ Positition to start from
-> k -- ^ First key.
-> k -- ^ Second key.
-> Int
followPrefixesByteFrom !position !k !l = go position
where
go !n | b /= c || b == 0 || c == 0 = n
| otherwise = go (n + 1)
where b = getByte k n
c = getByte l n
{-# INLINE followPrefixesByteFrom #-}
leftmost, rightmost :: a -> (k -> v -> a) -> Node k v -> a
leftmost = extremity ileft
{-# INLINE leftmost #-}
rightmost = extremity iright
{-# INLINE rightmost #-}
-- | Generic function so we can easily implement 'leftmost' and 'rightmost'.
extremity :: (Node k v -> Node k v) -- ^ Either 'ileft' or 'iright'.
-> a -- ^ 'Empty' continuation.
-> (k -> v -> a) -- ^ 'Leaf' continuation.
-> Node k v
-> a
extremity direct onEmpty onLeaf node = go node
where
go i@(Internal{}) = go $ direct i
go (Leaf k v) = onLeaf k v
go _ = onEmpty
{-# INLINE go #-}
{-# INLINE extremity #-}