seqn-0.1.0.0: src/Data/Seqn/Internal/Tree.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_HADDOCK not-home #-}
-- |
-- This is an internal module. You probably don't need to import this. Use
-- "Data.Seqn.Seq" instead.
--
-- = WARNING
--
-- Definitions in this module allow violating invariants that would otherwise be
-- guaranteed by "Data.Seqn.Seq". Use at your own risk!
--
module Data.Seqn.Internal.Tree
(
-- * Tree
Tree(..)
-- * Basic
, size
, bin
-- * Folds
, foldl'
, ifoldl'
, foldr'
, ifoldr'
, traverse
, itraverse
-- * Construct
, generateA
-- * Index
, adjustF
, insertAt
, deleteAt
-- * Slice
, cons
, snoc
, uncons
, unsnoc
, splitAtF
-- * Transform
, mapMaybeA
, mapEitherA
-- * Zip and unzip
, zipWithStreamM
, unzipWithA
, unzipWith3A
-- * Tree helpers
, fold
, foldSimple
, link
, glue
, merge
, balanceL
, balanceR
-- * Testing
, valid
, debugShowsPrec
) where
import Prelude hiding (concatMap, break, drop, dropWhile, filter, foldl', lookup, map, replicate, reverse, scanl, scanr, span, splitAt, take, takeWhile, traverse, unzip, unzip3, zip, zip3, zipWith, zipWith3)
import qualified Control.Applicative as Ap
import Control.DeepSeq (NFData(..), NFData1(..))
import Data.Bifunctor (Bifunctor(..))
import qualified Data.Seqn.Internal.Util as U
import Data.Seqn.Internal.Stream (Stream(..), Step(..))
data Tree a
= Bin {-# UNPACK #-} !Int !a !(Tree a) !(Tree a)
| Tip
--------------
-- Instances
--------------
instance NFData a => NFData (Tree a) where
rnf = \case
Bin _ x l r -> rnf x `seq` rnf l `seq` rnf r
Tip -> ()
{-# INLINABLE rnf #-}
instance NFData1 Tree where
liftRnf f = go
where
go (Bin _ x l r) = f x `seq` go l `seq` go r
go Tip = ()
{-# INLINE liftRnf #-}
-------------------
-- Basic Tree ops
-------------------
singleton :: a -> Tree a
singleton x = Bin 1 x Tip Tip
{-# INLINE singleton #-}
size :: Tree a -> Int
size (Bin n _ _ _) = n
size Tip = 0
{-# INLINE size #-}
-- O(1). Link two trees with a value in between. Precondition: The trees are
-- balanced wrt each other.
bin :: a -> Tree a -> Tree a -> Tree a
bin x l r = Bin (size l + size r + 1) x l r
{-# INLINE bin #-}
----------
-- Folds
----------
-- Note [Folds]
-- ~~~~~~~~~~~~
-- Certain functions, such as folds, are implemented recursively on non-empty
-- trees, i.e. `go :: Int -> a -> Tree a -> Tree a -> b` instead of
-- `go :: Tree a -> b`. This is simply because benchmarks show this to be
-- faster.
foldl' :: (b -> a -> b) -> b -> Tree a -> b
foldl' f !z0 = \case
Bin _ x l r -> go z0 x l r
Tip -> z0
where
go !z !x l r = case l of
Bin _ lx ll lr -> case r of
Bin _ rx rl rr ->
let !z' = go z lx ll lr
in go (f z' x) rx rl rr
Tip ->
let !z' = go z lx ll lr
in f z' x
Tip -> case r of
Bin _ rx rl rr -> go (f z x) rx rl rr
Tip -> f z x
{-# INLINE foldl' #-}
ifoldl' :: (Int -> b -> a -> b) -> b -> Int -> Tree a -> b
ifoldl' f !z0 !i0 = \case
Bin _ x l r -> go z0 i0 x l r
Tip -> z0
where
go !z !i !x l r = case l of
Bin lsz lx ll lr -> case r of
Bin _ rx rl rr ->
let !z' = go z i lx ll lr
in go (f (i+lsz) z' x) (i+lsz+1) rx rl rr
Tip ->
let !z' = go z i lx ll lr
in f (i+lsz) z' x
Tip -> case r of
Bin _ rx rl rr -> go (f i z x) (i+1) rx rl rr
Tip -> f i z x
{-# INLINE ifoldl' #-}
foldr' :: (a -> b -> b) -> b -> Tree a -> b
foldr' f !z0 = \case
Bin _ x l r -> go z0 x l r
Tip -> z0
where
go !z !x l r = case l of
Bin _ lx ll lr -> case r of
Bin _ rx rl rr ->
let !z' = go z rx rl rr
in go (f x z') lx ll lr
Tip -> go (f x z) lx ll lr
Tip -> case r of
Bin _ rx rl rr -> f x $! go z rx rl rr
Tip -> f x z
{-# INLINE foldr' #-}
ifoldr' :: (Int -> a -> b -> b) -> b -> Int -> Tree a -> b
ifoldr' f !z0 !i0 = \case
Bin _ x l r -> go z0 i0 x l r
Tip -> z0
where
go !z !i !x l r = case l of
Bin _ lx ll lr -> case r of
Bin rsz rx rl rr ->
let !z' = go z i rx rl rr
in go (f (i-rsz) x z') (i-rsz-1) lx ll lr
Tip -> go (f i x z) (i-1) lx ll lr
Tip -> case r of
Bin rsz rx rl rr -> f (i-rsz) x $! go z i rx rl rr
Tip -> f i x z
{-# INLINE ifoldr' #-}
fold
:: b
-> (Int -> a -> b -> b -> b)
-> (Int -> a -> b -> b)
-> (Int -> a -> b -> b)
-> (a -> b)
-> Tree a
-> b
fold tip glr gl gr g = \case
Bin sz x l r -> go sz x l r
Tip -> tip
where
go !sz !x l r = case l of
Bin lsz lx ll lr -> case r of
Bin rsz rx rl rr -> glr sz x (go lsz lx ll lr) (go rsz rx rl rr)
Tip -> gl sz x (go lsz lx ll lr)
Tip -> case r of
Bin rsz rx rl rr -> gr sz x (go rsz rx rl rr)
Tip -> g x
{-# INLINE fold #-}
foldSimple :: b -> (Int -> a -> b -> b -> b) -> Tree a -> b
foldSimple tip f = fold tip f gl gr g
where
gl !sz x ml = f sz x ml tip
{-# INLINE gl #-}
gr !sz x mr = f sz x tip mr
{-# INLINE gr #-}
g x = f 1 x tip tip
{-# INLINE g #-}
{-# INLINE foldSimple #-}
traverse :: Applicative f => (a -> f b) -> Tree a -> f (Tree b)
traverse f = fold (pure Tip) glr gl gr g
where
glr !sz x ml mr = liftA3R' (flip (Bin sz)) ml (f x) mr
-- See Note [Traverse liftA3R']
{-# INLINE glr #-}
gl !sz x ml = Ap.liftA2 (\l' x' -> Bin sz x' l' Tip) ml (f x)
{-# INLINE gl #-}
gr !sz x mr = Ap.liftA2 (\x' r' -> Bin sz x' Tip r') (f x) mr
{-# INLINE gr #-}
g x = fmap singleton (f x)
{-# INLINE g #-}
{-# INLINE traverse #-}
itraverse :: Applicative f => (Int -> a -> f b) -> Int -> Tree a -> f (Tree b)
itraverse f !i0 = \case
Bin sz x l r -> go i0 sz x l r
Tip -> pure Tip
where
go !i !sz x l r = case l of
Bin lsz lx ll lr -> case r of
Bin rsz rx rl rr ->
liftA3R'
(flip (Bin sz))
(go i lsz lx ll lr)
(f (i+lsz) x)
(go (i+lsz+1) rsz rx rl rr)
-- See Note [Traverse liftA3R']
Tip ->
Ap.liftA2
(\l' x' -> Bin sz x' l' Tip)
(go i lsz lx ll lr)
(f (i+lsz) x)
Tip -> case r of
Bin rsz rx rl rr ->
Ap.liftA2 (\x' r' -> Bin sz x' Tip r') (f i x) (go (i+1) rsz rx rl rr)
Tip ->
fmap (\x' -> Bin sz x' Tip Tip) (f i x)
-- See Note [Traverse]
{-# INLINE itraverse #-}
-- Note [Traverse liftA3R']
-- ~~~~~~~~~~~~~~~~~~~~~~~~
-- We want to associate to the right because we define foldMap using traverse
-- and ifoldMap using itraverse. It is more appropriate to be right-associative
-- for <>.
-- Right associative and strict
liftA3R' :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
liftA3R' f mx my mz =
Ap.liftA2
(\x (U.S2 y z) -> f x y z)
mx
(Ap.liftA2 U.S2 my mz)
{-# INLINE liftA3R' #-}
--------------
-- Construct
--------------
generateA :: Applicative f => (Int -> f a) -> Int -> Int -> f (Tree a)
generateA f = go
where
go !i n
| n <= 0 = pure Tip
| otherwise =
Ap.liftA3
(flip (Bin n))
(go i lsz)
(f (i+lsz))
(go (i+lsz+1) (n-lsz-1))
where
lsz = (n-1) `div` 2
{-# INLINE generateA #-}
----------
-- Index
----------
-- Precondition: 0 <= i < size xs
adjustF :: Functor f => (a -> f a) -> Int -> Tree a -> f (Tree a)
adjustF f = go
where
go !i = \case
Bin sz x l r -> case compare i szl of
LT -> fmap (\l' -> Bin sz x l' r) (go i l)
EQ -> fmap (\x' -> Bin sz x' l r) (f x)
GT -> fmap (Bin sz x l) (go (i-szl-1) r)
where
szl = size l
Tip -> errorOutOfBounds "Tree.adjustF"
{-# INLINE adjustF #-}
-- Inserts at ends if not in bounds
insertAt :: Int -> a -> Tree a -> Tree a
insertAt !i x (Bin _ y l r)
| i <= szl = balanceL y (insertAt i x l) r
| otherwise = balanceR y l (insertAt (i-szl-1) x r)
where
szl = size l
insertAt _ x Tip = singleton x
-- Precondition: 0 <= i < size xs
deleteAt :: Int -> Tree a -> Tree a
deleteAt !i (Bin _ x l r) = case compare i szl of
LT -> balanceR x (deleteAt i l) r
EQ -> glue l r
GT -> balanceL x l (deleteAt (i-szl-1) r)
where
szl = size l
deleteAt _ Tip = errorOutOfBounds "Tree.deleteAt"
----------
-- Slice
----------
cons :: a -> Tree a -> Tree a
cons x Tip = singleton x
cons x (Bin _ y l r) = balanceL y (cons x l) r
snoc :: Tree a -> a -> Tree a
snoc Tip x = singleton x
snoc (Bin _ y l r) x = balanceR y l (snoc r x)
uncons :: Tree a -> U.SMaybe (U.S2 a (Tree a))
uncons (Bin _ x l r) = U.SJust (unconsSure x l r)
uncons Tip = U.SNothing
{-# INLINE uncons #-}
unconsSure :: a -> Tree a -> Tree a -> U.S2 a (Tree a)
unconsSure x (Bin _ lx ll lr) r = case unconsSure lx ll lr of
U.S2 y l' -> U.S2 y (balanceR x l' r)
unconsSure x Tip r = U.S2 x r
unsnoc :: Tree a -> U.SMaybe (U.S2 (Tree a) a)
unsnoc (Bin _ x l r) = U.SJust $ unsnocSure x l r
unsnoc Tip = U.SNothing
{-# INLINE unsnoc #-}
unsnocSure :: a -> Tree a -> Tree a -> U.S2 (Tree a) a
unsnocSure x l (Bin _ rx rl rr) = case unsnocSure rx rl rr of
U.S2 r' y -> U.S2 (balanceL x l r') y
unsnocSure x l Tip = U.S2 l x
-- Precondition: 0 <= i < size xs
splitAtF
:: U.Biapplicative f
=> Int -> Tree a -> f (Tree a) (U.S2 a (Tree a))
splitAtF = go
where
go !i (Bin _ x l r) = case compare i szl of
LT -> second (second (\lr -> link x lr r)) (go i l)
EQ -> U.bipure l (U.S2 x r)
GT -> first (link x l) (go (i-szl-1) r)
where
szl = size l
go _ Tip = errorOutOfBounds "Tree.splitAtF"
{-# INLINE splitAtF #-}
--------------
-- Transform
--------------
mapMaybeA :: Applicative f => (a -> f (Maybe b)) -> Tree a -> f (Tree b)
mapMaybeA f = foldSimple tip g
where
tip = pure Tip
{-# INLINE tip #-}
g _ x ml mr = (\h -> Ap.liftA3 h ml (f x) mr) $ \l my r ->
case my of
Nothing -> merge l r
Just y -> link y l r
{-# INLINE g #-}
{-# INLINE mapMaybeA #-}
mapEitherA
:: Applicative f
=> (a -> f (Either b c)) -> Tree a -> f (U.S2 (Tree b) (Tree c))
mapEitherA f = foldSimple tip g
where
tip = pure (U.bipure Tip Tip)
{-# INLINE tip #-}
g _ x ml mr = (\h -> Ap.liftA3 h ml (f x) mr) $ \l my r ->
case my of
Left y -> U.biliftA2 (link y) merge l r
Right y -> U.biliftA2 merge (link y) l r
{-# INLINE g #-}
{-# INLINE mapEitherA #-}
------------------
-- Zip and unzip
------------------
zipWithStreamM :: Monad m => (a -> b -> m c) -> Tree a -> Stream b -> m (Tree c)
zipWithStreamM f t (Stream step s) = U.evalSStateT (foldSimple tip g t) s
where
tip = pure Tip
{-# INLINE tip #-}
g _ x ml mr = U.SStateT $ \s2 -> do
U.S2 s3 l <- U.runSStateT ml s2
case step s3 of
Done -> pure $ U.S2 s3 l
Yield y s4 -> do
z <- f x y
U.S2 s5 r <- U.runSStateT mr s4
pure $! U.S2 s5 (link z l r)
{-# INLINE g #-}
{-# INLINE zipWithStreamM #-}
unzipWithA
:: Applicative f => (a -> f (b, c)) -> Tree a -> f (U.S2 (Tree b) (Tree c))
unzipWithA f = foldSimple tip g
where
tip = pure (U.S2 Tip Tip)
{-# INLINE tip #-}
g !sz x ml mr = (\h -> Ap.liftA3 h ml (f x) mr) $
\(U.S2 l1 l2) (x1,x2) (U.S2 r1 r2) ->
U.S2 (Bin sz x1 l1 r1) (Bin sz x2 l2 r2)
{-# INLINE g #-}
{-# INLINE unzipWithA #-}
unzipWith3A
:: Applicative f
=> (a -> f (b, c, d))
-> Tree a
-> f (U.S3 (Tree b) (Tree c) (Tree d))
unzipWith3A f = foldSimple tip g
where
tip = pure (U.S3 Tip Tip Tip)
{-# INLINE tip #-}
g !sz x ml mr = (\h -> Ap.liftA3 h ml (f x) mr) $
\(U.S3 l1 l2 l3) (x1,x2,x3) (U.S3 r1 r2 r3) ->
U.S3 (Bin sz x1 l1 r1) (Bin sz x2 l2 r2) (Bin sz x3 l3 r3)
{-# INLINE g #-}
{-# INLINE unzipWith3A #-}
-----------
-- Errors
-----------
errorOutOfBounds :: String -> a
errorOutOfBounds name = error (name ++ ": out of bounds")
------------
-- Balance
------------
-- O(|log n1 - log n2|). Link two trees with a value in between.
link :: a -> Tree a -> Tree a -> Tree a
link !x Tip r = cons x r
link x l Tip = snoc l x
link x l@(Bin ls lx ll lr) r@(Bin rs rx rl rr)
| delta*ls < rs = balanceL rx (linkL x ls l rl) rr
| delta*rs < ls = balanceR lx ll (linkR x lr rs r)
| otherwise = Bin (1+ls+rs) x l r
{-# INLINE link #-}
linkL :: a -> Int -> Tree a -> Tree a -> Tree a
linkL !x !ls !l r = case r of
Bin rs rx rl rr
| delta*ls < rs -> balanceL rx (linkL x ls l rl) rr
| otherwise -> Bin (1+ls+rs) x l r
Tip -> error "Tree.linkL: impossible"
linkR :: a -> Tree a -> Int -> Tree a -> Tree a
linkR !x l !rs !r = case l of
Bin ls lx ll lr
| delta*rs < ls -> balanceR lx ll (linkR x lr rs r)
| otherwise -> Bin (1+ls+rs) x l r
Tip -> error "Tree.linkR: impossible"
-- O(log (n1 + n2)). Link two trees.
merge :: Tree a -> Tree a -> Tree a
merge Tip r = r
merge l Tip = l
merge l@(Bin ls lx ll lr) r@(Bin rs rx rl rr)
| ls < rs = case unsnocSure lx ll lr of U.S2 l' mx -> link mx l' r
| otherwise = case unconsSure rx rl rr of U.S2 mx r' -> link mx l r'
{-# INLINE merge #-}
-- O(log (n1 + n2)). Link two trees. Precondition: The trees must be balanced
-- wrt each other.
glue :: Tree a -> Tree a -> Tree a
glue Tip r = r
glue l Tip = l
glue l@(Bin ls lx ll lr) r@(Bin rs rx rl rr)
| ls > rs = case unsnocSure lx ll lr of U.S2 l' m -> balanceR m l' r
| otherwise = case unconsSure rx rl rr of U.S2 m r' -> balanceL m l r'
{-# INLINE glue #-}
-- Note [Balance]
-- ~~~~~~~~~~~~~~
-- The balancing code here is largely influenced by the implementation of
-- for the Set type in containers: https://hackage.haskell.org/package/containers
-- The linked papers in Data.Seqn.Seq describe the structure in greater detail.
--
-- To summarize:
-- * A tree is balanced if size(left child) < delta*size(right child) and vice
-- versa, which a special case for Tips. See `balanceOk` in `valid`, which is
-- used to check that balance holds in tests.
-- * Rebalancing involves rotations. The rotation can be single or double. The
-- constant `ratio` determines whether a double rotation is performed.
delta, ratio :: Int
delta = 3
ratio = 2
-- O(1). Restores balance with at most one right rotation. Precondition: One
-- right rotation must be enough to restore balance. This is the case when the
-- left tree might have been inserted to or the right tree deleted from.
balanceL :: a -> Tree a -> Tree a -> Tree a
balanceL !x l r = case r of
Tip -> case l of
Tip -> Bin 1 x Tip Tip
Bin _ lx ll lr -> case lr of
Tip -> case ll of
Tip -> Bin 2 x l Tip
Bin _ _ _ _ -> Bin 3 lx ll (Bin 1 x Tip Tip)
Bin _ lrx _ _ -> case ll of
Tip -> Bin 3 lrx (Bin 1 lx Tip Tip) (Bin 1 x Tip Tip)
Bin _ _ _ _ -> Bin 4 lx ll (Bin 2 x lr Tip)
Bin rs _ _ _ -> case l of
Tip -> Bin (1+rs) x Tip r
Bin ls lx ll lr
| ls > delta*rs -> case (ll, lr) of
(Bin lls _ _ _, Bin lrs lrx lrl lrr)
| lrs < ratio*lls -> Bin (1+ls+rs) lx ll (Bin (1+rs+lrs) x lr r)
| otherwise -> Bin (1+ls+rs) lrx (Bin (1+lls+size lrl) lx ll lrl) (Bin (1+rs+size lrr) x lrr r)
_ -> error "Tree.balanceL: impossible"
| otherwise -> Bin (1+ls+rs) x l r
{-# NOINLINE balanceL #-}
-- O(1). Restores balance with at most one left rotation. Precondition: One left
-- rotation must be enough to restore balance. This is the case when the right
-- tree might have been inserted to or the left tree deleted from.
balanceR :: a -> Tree a -> Tree a -> Tree a
balanceR !x l r = case l of
Tip -> case r of
Tip -> Bin 1 x Tip Tip
Bin _ rx rl rr -> case rl of
Tip -> case rr of
Tip -> Bin 2 x Tip r
Bin _ _ _ _ -> Bin 3 rx (Bin 1 x Tip Tip) rr
Bin _ rlx _ _ -> case rr of
Tip -> Bin 3 rlx (Bin 1 x Tip Tip) (Bin 1 rx Tip Tip)
Bin _ _ _ _ -> Bin 4 rx (Bin 2 x Tip rl) rr
Bin ls _ _ _ -> case r of
Tip -> Bin (1+ls) x l Tip
Bin rs rx rl rr
| rs > delta*ls -> case (rl, rr) of
(Bin rls rlx rll rlr, Bin rrs _ _ _)
| rls < ratio*rrs -> Bin (1+ls+rs) rx (Bin (1+ls+rls) x l rl) rr
| otherwise -> Bin (1+ls+rs) rlx (Bin (1+ls+size rll) x l rll) (Bin (1+rrs+size rlr) rx rlr rr)
_ -> error "Tree.balanceR: impossible"
| otherwise -> Bin (1+ls+rs) x l r
{-# NOINLINE balanceR #-}
------------
-- Testing
------------
valid :: Tree a -> Bool
valid s = balanceOk s && sizeOk s
where
balanceOk = \case
Bin _ _ l r -> ok && balanceOk l && balanceOk r
where
ok = size l + size r <= 1 ||
(size l <= delta * size r && size r <= delta * size l)
Tip -> True
sizeOk = \case
Bin sz _ l r -> sizeOk l && sizeOk r && size l + size r + 1 == sz
Tip -> True
debugShowsPrec :: Show a => Int -> Tree a -> ShowS
debugShowsPrec p = \case
Bin sz x l r ->
showParen (p > 10) $
showString "Bin " .
shows sz .
showString " " .
showsPrec 11 x .
showString " " .
debugShowsPrec 11 l .
showString " " .
debugShowsPrec 11 r
Tip -> showString "Tip"