packages feed

radix-tree-1.0.0.0: no/No/Set/Word.hs

{-# LANGUAGE DerivingStrategies
           , GeneralizedNewtypeDeriving
           , PatternSynonyms
           , ViewPatterns #-}

module No.Set.Word
  ( Color (..)
  , other

  , NoSet (Mono, ..)

  , No.Set.Word.lookup
  , lookupL
  , findL
  , lookupR
  , findR

  , Range (..)
  , monoL
  , monoR
  , monoRange

  , size
  , sizeL
  , sizeR
  , sizeRange

  , fillL
  , fillR
  , fillRange

  , No.Set.Word.foldl
  , No.Set.Word.foldl'
  , No.Set.Word.foldr
  , No.Set.Word.foldr'

  , foldlL
  , foldlL'
  , foldrL
  , foldrL'

  , foldlR
  , foldlR'
  , foldrR
  , foldrR'

  , foldlRange
  , foldlRange'
  , foldrRange
  , foldrRange'

  , complement

  , union
  , disjoint
  , intersection

  , difference
  , symmetricDifference

  , PartialOrdering (..)
  , No.Set.Word.compare
  ) where


import           Data.Zebra.Word (Color (..), PartialOrdering (..))
import           Data.Zebra.Word.Unsafe (Range (..))

import           Data.Foldable
import           Data.Sequence (Seq (..))
import qualified Data.Sequence as Seq
import           Numeric.Natural



other :: Color -> Color
other Black = White
other White = Black



newtype NoSet = NoSet { getNoSet :: Seq (Color, Word, Word) }
                deriving newtype Eq

instance Show NoSet where
  showsPrec _ (NoSet xs) =
    showList . fmap (\(_, kL, kR) -> (kL, kR))
             . filter (\(c, _, _) -> c == White)
             $ toList xs

pattern Mono :: Color -> NoSet
pattern Mono c <- (monotone -> Just c)
  where
    Mono c = NoSet $ Seq.singleton (c, 0, maxBound)

monotone :: NoSet -> Maybe Color
monotone (NoSet (Seq.Empty Seq.:|> (c, _, _))) = Just c
monotone _                                     = Nothing



lookup :: Word -> NoSet -> Color
lookup w no =
  let ~(NoSet l, _) = unsafeSplitL w no
  in case l of
       _ :|> (c, _, _) -> c
       Empty           ->
         error $ "No.Set.Word.lookup: out of bounds (" <> shows w ")"



lookupL :: Color -> Word -> NoSet -> Maybe Word
lookupL x w no =
  let ~(NoSet l, _) = unsafeSplitL w no
  in case l of
       _ :|> _ -> go l
       Empty   ->
         error $ "No.Set.Word.lookupL: out of bounds (" <> shows w ")"

  where
    go (l :|> (c, _, b)) | c == x    = Just b
                         | otherwise = go l
    go Empty = Nothing

findL :: Word -> Color -> Word -> NoSet -> Word
findL d x w no =
  case lookupL x w no of
    Just a  -> a
    Nothing -> d



lookupR :: Color -> Word -> NoSet -> Maybe Word
lookupR x w no =
  let ~(_, NoSet r) = unsafeSplitR w no
  in case r of
       _ :<| _ -> go r
       Empty   ->
         error $ "No.Set.Word.lookupR: out of bounds (" <> shows w ")"

  where
    go ((c, a, _) :<| r) | c == x    = Just a
                         | otherwise = go r
    go Empty = Nothing

findR :: Word -> Color -> Word -> NoSet -> Word
findR d x w no =
  case lookupR x w no of
    Just a  -> a
    Nothing -> d



monoL :: Word -> NoSet -> Maybe Color
monoL k = monotone . fst . unsafeSplitL k

monoR :: Word -> NoSet -> Maybe Color
monoR k = monotone . snd . unsafeSplitR k

monoRange :: Range -> NoSet -> Maybe Color
monoRange r = monotone . (\(_, m, _) -> m) . unsafeSplitRange r



size :: Color -> NoSet -> Natural
size x =
  let f (c, a, b) z
        | c == x    = fromIntegral (b - a) + 1 + z
        | otherwise = z

  in Data.Foldable.foldr f 0 . getNoSet

sizeL :: Color -> Word -> NoSet -> Natural
sizeL x k = size x . fst . unsafeSplitL k

sizeR :: Color -> Word -> NoSet -> Natural
sizeR x k = size x . snd . unsafeSplitR k

sizeRange :: Color -> Range -> NoSet -> Natural
sizeRange x r = size x . (\(_, m, _) -> m) . unsafeSplitRange r



fillL :: Word -> Color -> NoSet -> NoSet
fillL w x no =
  let ~(_, NoSet _r) = unsafeSplitL w no
  in case _r of
       (c, _, b) :<| r | c == x    -> NoSet $ (c, 0, b) :<| r
                       | otherwise -> NoSet $ (x, 0, w) :<| _r

       _                           -> Mono x

fillR :: Word -> Color -> NoSet -> NoSet
fillR w x no =
  let ~(NoSet _l, _) = unsafeSplitR w no
  in case _l of
       l :|> (c, a, _) | c == x    -> NoSet $ l :|> (c, a, maxBound)
                       | otherwise -> NoSet $ _l :|> (x, w, maxBound)

       _                           -> Mono x

fillRange :: Range -> Color -> NoSet -> NoSet
fillRange rng@(Range kL kR) x no =
  let ~(NoSet _l, _, NoSet _r) = unsafeSplitRange rng no
  in case (_l, _r) of
       (l :|> (cL, a, _), (cR, _, b) :<| r) ->
         case (cL == cR, cL == x) of
           (True , True ) -> NoSet $ l <> ((x, a, b) :<| r)
           (True , False) -> NoSet $ _l <> ((x, kL, kR) :<| _r)
           (False, True ) -> NoSet $ (l :|> (x, a, kR)) <> _r
           (False, False) -> NoSet $ _l <> ((x, a, b) :<| r)

       (l :|> (cL, a, _), Empty)
         | cL == x   -> NoSet $ l :|> (cL, a, maxBound)
         | otherwise -> NoSet $ _l :|> (x, kL, maxBound)

       (Empty, (cR, _, b) :<| r)
         | cR == x   -> NoSet $ (cR, 0, b) :<| r
         | otherwise -> NoSet $ (x, 0, kR) :<| _r

       (Empty, Empty) -> Mono x



unsafeSplitL :: Word -> NoSet -> (NoSet, NoSet)
unsafeSplitL k (NoSet xs) =
  let ~(_l, r) = Seq.spanl (\(_, a, _) -> a <= k) xs
  in case _l of
       l :|> (c, a, b) | b > k -> (NoSet $ l :|> (c, a, k), NoSet $ (c, k + 1, b) :<| r)
       _                       -> (NoSet _l, NoSet r)

unsafeSplitR :: Word -> NoSet -> (NoSet, NoSet)
unsafeSplitR k (NoSet xs) =
  let ~(_r, l) = Seq.spanr (\(_, _, b) -> b >= k) xs
  in case _r of
      (c, a, b) :<| r | a < k -> (NoSet $ l :|> (c, a, k - 1), NoSet $ (c, k, b) :<| r)
      _                       -> (NoSet l, NoSet _r)

unsafeSplitRange :: Range -> NoSet -> (NoSet, NoSet, NoSet)
unsafeSplitRange (Range kL kR) no =
  let ~(l, no') = unsafeSplitR kL no
      ~(m, r)   = unsafeSplitL kR no'
  in (l, m, r)



foldl, foldl' :: (a -> Range -> Color -> a) -> a -> NoSet -> a
foldl  f z0 = Data.Foldable.foldl  (\z (c, a, b) -> f z (UnsafeRange a b) c) z0 . getNoSet
foldl' f z0 = Data.Foldable.foldl' (\z (c, a, b) -> f z (UnsafeRange a b) c) z0 . getNoSet

foldr, foldr' :: (Range -> Color -> a -> a) -> a -> NoSet -> a
foldr  f z0 = Data.Foldable.foldr  (\(c, a, b) -> f (UnsafeRange a b) c) z0 . getNoSet
foldr' f z0 = Data.Foldable.foldr' (\(c, a, b) -> f (UnsafeRange a b) c) z0 . getNoSet



foldlL, foldlL' :: Word -> (a -> Range -> Color -> a) -> a -> NoSet -> a
foldlL  w f z = No.Set.Word.foldl  f z . fst . unsafeSplitL w
foldlL' w f z = No.Set.Word.foldl' f z . fst . unsafeSplitL w

foldrL, foldrL' :: Word -> (Range -> Color -> a -> a) -> a -> NoSet -> a
foldrL  w f z = No.Set.Word.foldr  f z . fst . unsafeSplitL w
foldrL' w f z = No.Set.Word.foldr' f z . fst . unsafeSplitL w



foldlR, foldlR' :: Word -> (a -> Range -> Color -> a) -> a -> NoSet -> a
foldlR  w f z = No.Set.Word.foldl  f z . snd . unsafeSplitR w
foldlR' w f z = No.Set.Word.foldl' f z . snd . unsafeSplitR w

foldrR, foldrR' :: Word -> (Range -> Color -> a -> a) -> a -> NoSet -> a
foldrR  w f z = No.Set.Word.foldr  f z . snd . unsafeSplitR w
foldrR' w f z = No.Set.Word.foldr' f z . snd . unsafeSplitR w



foldlRange, foldlRange' :: Range -> (a -> Range -> Color -> a) -> a -> NoSet -> a
foldlRange  r f z = No.Set.Word.foldl  f z . (\(_, m, _) -> m) . unsafeSplitRange r
foldlRange' r f z = No.Set.Word.foldl' f z . (\(_, m, _) -> m) . unsafeSplitRange r

foldrRange, foldrRange' :: Range -> (Range -> Color -> a -> a) -> a -> NoSet -> a
foldrRange  r f z = No.Set.Word.foldr  f z . (\(_, m, _) -> m) . unsafeSplitRange r
foldrRange' r f z = No.Set.Word.foldr' f z . (\(_, m, _) -> m) . unsafeSplitRange r



-- | Combines two sets into an ascending non-overlapping list of
--   consecutive double-colored ranges.
--
--   Both sets must be defined over the same ranges for this function to make sense.
crush :: NoSet -> NoSet -> [(Color, Range, Color)]
crush (NoSet xs) (NoSet ys) = go xs ys
  where
    go Empty                Empty                = []
    go ((cL, aL, bL) :<| l) ((cR, aR, bR) :<| r) =
      case bL `Prelude.compare` bR of
        LT -> (cL, UnsafeRange aL bL, cR) : go l ((cR, bL + 1, bR) :<| r)
        GT -> (cL, UnsafeRange aR bR, cR) : go ((cL, bR + 1, bL) :<| l) r
        EQ -> (cL, UnsafeRange aL bL, cR) : go l r

    go _ _ =
      error "No.Set.Word.crush: unequally sized sets"



complement :: NoSet -> NoSet
complement = NoSet . fmap (\(c, a, b) -> (other c, a, b)) . getNoSet



union :: Color -> NoSet -> NoSet -> NoSet
union x =
  merge $ \cL cR ->
    if cL == cR && cL /= x
      then cL
      else x

disjoint :: Color -> NoSet -> NoSet -> Bool
disjoint x a b =
  case intersection x a b of
    Mono y -> x /= y
    _      -> False

intersection :: Color -> NoSet -> NoSet -> NoSet
intersection x =
  merge $ \cL cR ->
    if cL == cR && cL == x
      then x
      else other x

difference :: Color -> NoSet -> NoSet -> NoSet
difference x =
  merge $ \cL cR ->
    if cL /= cR && cL == x
      then x
      else other x

symmetricDifference :: Color -> NoSet -> NoSet -> NoSet
symmetricDifference x =
  merge $ \cL cR ->
    if cL == cR
      then other x
      else x




data Carry = Carry Color Word
           | NoCarry

merge :: (Color -> Color -> Color) -> NoSet -> NoSet -> NoSet
merge f as bs = NoSet . Seq.fromList . unify NoCarry $ crush as bs
  where
    unify carry [] =
      case carry of
        NoCarry   -> []
        Carry c k -> (c, k, maxBound) : []

    unify carry ((cL, Range a _, cR) : rest) =
      let cM = f cL cR
      in case carry of
           NoCarry   -> unify (Carry cM a) rest
           Carry c k
             | c == cM   -> unify carry rest
             | otherwise -> (c, k, a - 1) : unify (Carry cM a) rest



compare :: Color -> NoSet -> NoSet -> PartialOrdering
compare x as bs = Data.Foldable.foldr go Equal $ crush as bs
  where
    go (cL, _, cR) p =
      case p of
        Subset
          | cL == cR || cR == x -> Subset
          | otherwise           -> Incomparable

        Superset
          | cL == cR || cL == x -> Superset
          | otherwise           -> Incomparable

        Equal
          | cL == cR  -> Equal
          | cR == x   -> Subset
          | otherwise -> Superset

        Incomparable -> Incomparable