packages feed

rangeset-0.1.0.0: src/ghc/Data/RangeSet/Internal.hs

{-# LANGUAGE UnboxedTuples, MultiWayIf, BangPatterns, Trustworthy #-}
module Data.RangeSet.Internal (
    module Data.RangeSet.Internal,
    RangeSet(..), E, SRangeList(..), StrictMaybeE(..),
    size, height, foldE,
    module Data.RangeSet.Internal.Enum,
    module Data.RangeSet.Internal.SmartConstructors,
    module Data.RangeSet.Internal.Inserters,
    module Data.RangeSet.Internal.Extractors,
    module Data.RangeSet.Internal.Lumpers,
    module Data.RangeSet.Internal.Splitters,
    module Data.RangeSet.Internal.Heuristics
  ) where

import Prelude

import Data.RangeSet.Internal.Types
import Data.RangeSet.Internal.Enum
import Data.RangeSet.Internal.SmartConstructors
import Data.RangeSet.Internal.Inserters
import Data.RangeSet.Internal.Extractors
import Data.RangeSet.Internal.Lumpers
import Data.RangeSet.Internal.Splitters
import Data.RangeSet.Internal.Heuristics
import Data.Bits (shiftR)

{-# INLINEABLE insertE #-}
insertE :: E -> RangeSet a -> RangeSet a
insertE !x Tip = single x x
insertE x t@(Fork h l u lt rt)
  -- Nothing happens when it's already in range
  | l <= x = if
    | x <= u -> t
  -- If it is adjacent to the upper range, it may fuse
    | x == succ u -> fuseRight h l x lt rt                                         -- we know x > u since x <= l && not x <= u
  -- Otherwise, insert and balance for right
    | otherwise -> ifStayedSame rt (insertE x rt) t (balance l u lt)               -- cannot be biased, because fusion can shrink a tree
  | {- x < l -} otherwise = if
  -- If it is adjacent to the lower, it may fuse
    x == pred l then fuseLeft h x u lt rt                                          -- the equality must be guarded by an existence check
  -- Otherwise, insert and balance for left
                else ifStayedSame lt (insertE x lt) t $ \lt' -> balance l u lt' rt -- cannot be biased, because fusion can shrink a tree
  where
    {-# INLINE fuseLeft #-}
    fuseLeft !h !x !u Tip !rt = Fork h x u Tip rt
    fuseLeft h x u lt@(Fork _ ll lu llt lrt) rt
      | (# !l, !x', lt' #) <- maxDelete ll lu llt lrt
      -- we know there exists an element larger than x'
      -- if x == x' + 1, we fuse (x != x' since that breaks disjointness, x == pred l)
      , x == succ x' = balanceR l u lt' rt
      | otherwise    = Fork h x u lt rt
    {-# INLINE fuseRight #-}
    fuseRight !h !l !x !lt Tip = Fork h l x lt Tip
    fuseRight h l x lt rt@(Fork _ rl ru rlt rrt)
      | (# !x', !u, rt' #) <- minDelete rl ru rlt rrt
      -- we know there exists an element smaller than x'
      -- if x == x' - 1, we fuse (x != x' since that breaks disjointness, as x == succ u)
      , x == pred x' = balanceL l u lt rt'
      | otherwise    = Fork h l x lt rt

{-# INLINEABLE deleteE #-}
deleteE :: E -> RangeSet a -> RangeSet a
deleteE !_ Tip = Tip
deleteE x t@(Fork h l u lt rt) =
  case compare l x of
    -- If its the only part of the range, the node is removed
    EQ | x == u    -> glue lt rt
    -- If it's at an extreme, it shrinks the range
       | otherwise -> Fork h (succ l) u lt rt
    LT -> case compare x u of
    -- If it's at an extreme, it shrinks the range
       EQ          -> Fork h l (pred u) lt rt
    -- Otherwise, if it's still in range, the range undergoes fission
       LT          -> fission l x u lt rt
    -- Otherwise delete and balance for one of the left or right
       GT          -> ifStayedSame rt (deleteE x rt) t $ balance l u lt             -- cannot be biased, because fisson can grow a tree
    GT             -> ifStayedSame lt (deleteE x lt) t $ \lt' -> balance l u lt' rt -- cannot be biased, because fisson can grow a tree
  where
    {- Fission breaks a node into two new ranges
       we'll push the range down into the smallest child, ensuring it's balanced -}
    {-# INLINE fission #-}
    fission :: E -> E -> E -> RangeSet a -> RangeSet a -> RangeSet a
    fission !l1 !x !u2 !lt !rt
      | height lt > height rt = fork l1 u1 lt (unsafeInsertL l2 u2 rt)
      | otherwise = fork l1 u1 (unsafeInsertR l2 u2 lt) rt
      where
        !u1 = pred x
        !l2 = succ x

uncheckedSubsetOf :: RangeSet a -> RangeSet a -> Bool
uncheckedSubsetOf Tip _ = True
uncheckedSubsetOf _ Tip = False
uncheckedSubsetOf (Fork _ ll lu llt lrt) (Fork _ rl ru rlt rrt) = case splitOverlapFork ll lu rl ru rlt rrt of
  (# lt', Fork 1 x y _ _, rt' #) ->
       x == ll && y == lu
    && uncheckedSubsetOf llt lt' && uncheckedSubsetOf lrt rt'
  _                              -> False

{-# INLINEABLE fromDistinctAscRangesSz #-}
fromDistinctAscRangesSz :: SRangeList -> Int -> RangeSet a
fromDistinctAscRangesSz rs !n = case go rs 0 (n - 1) of (# _, t, _ #) -> t
  where
    go :: SRangeList -> Int -> Int -> (# H, RangeSet a, SRangeList #)
    go rs !i !j
      | i > j     = (# 1, Tip, rs #)
      | otherwise =
        let !mid = (i + j) `shiftR` 1
        in case go rs i (mid - 1) of
             (# _, lt, rs' #) ->
                let !(SRangeCons l u rs'') = rs'
                in case go rs'' (mid + 1) j of
                      -- there is a height bias to the right, so the height of the right tree is all we need
                      -- perhaps this can be computed though from mid somehow instead of passing back?
                      (# !h, rt, rs''' #) -> (# h + 1, Fork h l u lt rt, rs''' #)

{-# INLINE insertRangeE #-}
-- This could be improved, but is OK
insertRangeE :: E -> E -> RangeSet a -> RangeSet a
insertRangeE !l !u Tip = single l u
insertRangeE l u (Fork _ l' u' lt rt) = let (# lt', rt' #) = splitFork l u l' u' lt rt in link l u lt' rt'