int-interval-map-0.0.1.0: Data/IntervalIntMap/Internal/IntervalIntIntMap.hs
{-# LANGUAGE FlexibleContexts, TypeApplications #-}
module Data.IntervalIntMap.Internal.IntervalIntIntMap
( IntervalValue(..)
, Interval(..)
, IntervalIntMap
, naiveIntervalMapLookup
, lookup
, overlaps
, overlapsWithKeys
, naiveOverlaps
, naiveOverlapsWithKeys
, NaiveIntervalInt
, intervalContains
, partition
, freeze
#ifdef IS_BUILDING_TEST
, mkTree
#endif
) where
import Prelude hiding (lookup)
import qualified Data.IntervalIntMap.Internal.GrowableVector as GV
import qualified Foreign.Storable as FS
import Foreign.Ptr (castPtr, plusPtr)
import qualified Data.Set as S
import qualified Data.IntSet as IS
import qualified Data.Vector.Storable as VS
import Control.Monad.ST (runST)
import Data.Word (Word32)
import Data.Ord (comparing)
import Data.Vector.Algorithms.Tim (sortBy)
import Control.DeepSeq (NFData(..))
{- DATA STRUCTURE
-
- An IntervalValue contains the interval [ivStart, ivPast) and the value. This
- is a closed-open interval, so represents `x` such that `ivStart <= x <
- ivPast`.
-
- The simplest map is the NaiveIntervalInt, which is just a vector. It is very
- memory efficient, but needs O(N) to search. However, for small N, it is
- likely very efficient and we can use this "structure" for testing too.
-
- The Tree is very simple: At each node, there is a split value and intervals
- are completely below it, completely above it, or contain the point.
-
-
- Leafs contain NaiveIntervalInt
-}
data Interval = Interval !Int !Int
#ifdef IS_BUILDING_TEST
deriving (Eq, Show)
#endif
data IntervalValue = IntervalValue
{ ivStart :: !Word32
, ivPast :: !Word32
, ivValue :: !Word32
}
#ifdef IS_BUILDING_TEST
deriving (Show)
#endif
instance Eq IntervalValue where
(IntervalValue s0 e0 ix0) == (IntervalValue s1 e1 ix1) =
s0 == s1 && e0 == e1 && ix0 == ix1
-- This is necessary to build sets of 'IntervalValue's (e.g., in 'naiveOverlapsWithKeys')
instance Ord IntervalValue where
(IntervalValue s0 e0 ix0) `compare` (IntervalValue s1 e1 ix1)
| s0 /= s1 = s0 `compare` s1
| e0 /= e1 = e0 `compare` e1
| otherwise = ix0 `compare` ix1
instance FS.Storable IntervalValue where
sizeOf _ = 3 * 4 -- aka 12
alignment x = FS.alignment (ivStart x)
peek p = IntervalValue
<$> FS.peek (castPtr p)
<*> FS.peek (castPtr p `plusPtr` 4)
<*> FS.peek (castPtr p `plusPtr` 8)
poke ptr (IntervalValue s p v) = do
let ptr' = castPtr ptr
FS.pokeElemOff @Word32 ptr' 0 s
FS.pokeElemOff @Word32 ptr' 1 p
FS.pokeElemOff @Word32 ptr' 2 v
intervalContains :: Int -> IntervalValue -> Bool
intervalContains p (IntervalValue s e _) =
let p' = toEnum p
in s <= p' && p' < e
type NaiveIntervalInt = VS.Vector IntervalValue
data IntervalIntMapNode = Leaf NaiveIntervalInt
| InnerNode
{ _nodeSplitValue :: !Int
, _leftSplit :: !IntervalIntMapNode
, _centerSplit :: !IntervalIntMapNode
, _rightSplit :: !IntervalIntMapNode
}
#ifdef IS_BUILDING_TEST
deriving (Show)
#endif
instance NFData IntervalIntMapNode where
rnf (Leaf v) = rnf v
rnf (InnerNode !_ left center right) = rnf left `seq` rnf center `seq` rnf right
newtype IntervalIntMap = IntervalIntMap { _imapRoot :: IntervalIntMapNode }
#ifdef IS_BUILDING_TEST
deriving (Show)
#endif
instance NFData IntervalIntMap where
rnf (IntervalIntMap !n) = rnf n
partition :: Int -> NaiveIntervalInt -> (NaiveIntervalInt, NaiveIntervalInt, NaiveIntervalInt)
partition p vec = runST $ do
left <- GV.new
center <- GV.new
right <- GV.new
VS.forM_ vec $ \val ->
let target
| ivPast val <= toEnum p = left
| ivStart val > toEnum p = right
| otherwise = center
in GV.pushBack val target
(,,)
<$> GV.unsafeFreeze left
<*> GV.unsafeFreeze center
<*> GV.unsafeFreeze right
sortedByEnd :: NaiveIntervalInt -> NaiveIntervalInt
sortedByEnd vec = VS.create $ do
vec' <- VS.thaw vec
sortBy (comparing ivPast) vec'
return vec'
{-|
Turn a 'NaiveIntervalInt' into an 'IntervalIntMap'
-}
freeze :: NaiveIntervalInt -> IntervalIntMap
freeze = mkTree 16
mkTree :: Int -> NaiveIntervalInt -> IntervalIntMap
mkTree maxSplit vec = IntervalIntMap $ mkTree' 0 maxSplit (sortedByEnd vec)
maxSplitIters :: Int
maxSplitIters = 8
mkTree' nIters maxSplit vec
| VS.length vec <= maxSplit = Leaf vec
| nIters > maxSplitIters = Leaf vec
| otherwise = trySplit nIters maxSplit vec
trySplit nIters maxSplit vec = InnerNode (fromEnum p) (r left) (r center) (r right)
where
r = mkTree' nIters' maxSplit
(left, center, right) = partition (fromEnum p) vec
nIters'
| successful = 0
| otherwise = nIters + 1
-- The criterion for calling it a successful split is a bit random, but seems to work:
-- If after splitting the largest component is at least maxSplit
-- smaller than the input, that was a successful split
successful = VS.length vec - maximum (map VS.length [left, center, right]) >= maxSplit
-- Choosing a pivot will probably have a big impact on the performance.
-- We pick the median end-point one, which is probably a decent heuristic
p = ivPast $ (VS.!) vec (VS.length vec `div` 2)
lookup :: Int -> IntervalIntMap -> IS.IntSet
lookup x (IntervalIntMap root) = lookup' root
where
lookup' (Leaf vec) = naiveIntervalMapLookup x vec
lookup' (InnerNode p left center right)
| x < p = lookup' left `IS.union` lookup' center
| x == p = lookup' center
| otherwise = lookup' center `IS.union` lookup' right
naiveIntervalMapLookup :: Int -> NaiveIntervalInt -> IS.IntSet
naiveIntervalMapLookup x = IS.fromList . VS.toList . VS.map (fromEnum . ivValue) . VS.filter (intervalContains x)
naiveOverlaps :: Interval -> NaiveIntervalInt -> IS.IntSet
naiveOverlaps i = IS.fromList . map snd . naiveOverlapsWithKeys i
naiveOverlapsWithKeys :: Interval -> NaiveIntervalInt -> [(Interval, Int)]
naiveOverlapsWithKeys i = map asPair . S.toList . naiveOverlapsWithKeys' i
asPair (IntervalValue s e ix) = (Interval (fromEnum s) (fromEnum e), fromEnum ix)
naiveOverlapsWithKeys' :: Interval -> NaiveIntervalInt -> S.Set IntervalValue
naiveOverlapsWithKeys' (Interval s0 e0) = S.fromList . VS.toList . VS.filter overlap1
where
overlap1 (IntervalValue s1' e1' _)
| s0 == e0 = False
| s1' == e1' = False
| otherwise =
let
s1 = fromEnum s1'
e1 = fromEnum e1'
in (s0 <= s1 && s1 < e0) || (s1 <= s0 && s0 < e1)
overlaps :: Interval -> IntervalIntMap -> IS.IntSet
overlaps i (IntervalIntMap root) = overlaps' i root
overlaps' i (Leaf vec) = naiveOverlaps i vec
overlaps' i (InnerNode p left centre right)
| i `intervalAbove` p = overlaps' i right `IS.union` overlaps' i centre
| i `intervalBelow` p = overlaps' i left `IS.union` overlaps' i centre
| otherwise = overlaps' i left `IS.union` overlaps' i centre `IS.union` overlaps' i right
overlapsWithKeys :: Interval -> IntervalIntMap -> [(Interval, Int)]
overlapsWithKeys i (IntervalIntMap root) = map asPair . S.toList $ overlapsWithKeys' i root
overlapsWithKeys' :: Interval -> IntervalIntMapNode -> S.Set IntervalValue
overlapsWithKeys' i (Leaf vec) = naiveOverlapsWithKeys' i vec
overlapsWithKeys' i (InnerNode p left centre right)
| i `intervalAbove` p = overlapsWithKeys' i right `S.union` overlapsWithKeys' i centre
| i `intervalBelow` p = overlapsWithKeys' i left `S.union` overlapsWithKeys' i centre
| otherwise = overlapsWithKeys' i left `S.union` overlapsWithKeys' i centre `S.union` overlapsWithKeys' i right
intervalAbove (Interval s _) p = s > p
intervalBelow (Interval _ e) p = e <= p