falsify-0.1.0: src/Data/Falsify/Tree.hs
module Data.Falsify.Tree (
Tree(Leaf, Branch)
-- * Dealing with marks
, propagate
, genKept
, keepAtLeast
-- * Binary search trees
, Interval(..)
, Endpoint(..)
, inclusiveBounds
, lookup
-- * Debugging
, drawTree
) where
import Prelude hiding (drop, lookup)
import Control.Selective (Selective, ifS)
import Control.Monad.State
import GHC.Show
import qualified Data.Tree as Rose
import Data.Falsify.Marked
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
data Tree a =
Leaf
-- 'Branch_' caches the size of the tree
| Branch_ {-# UNPACK #-} !Word a (Tree a) (Tree a)
deriving stock (Eq, Functor, Foldable, Traversable)
{-------------------------------------------------------------------------------
Tree stats
-------------------------------------------------------------------------------}
-- | Size of the tree
--
-- @O(1)@
size :: Tree a -> Word
size Leaf = 0
size (Branch_ s _ _ _) = s
{-------------------------------------------------------------------------------
Pattern synonyms that hide the size argument
-------------------------------------------------------------------------------}
viewBranch :: Tree a -> Maybe (a, Tree a, Tree a)
viewBranch Leaf = Nothing
viewBranch (Branch_ _ x l r) = Just (x, l, r)
branch :: a -> Tree a -> Tree a -> Tree a
branch x l r = Branch_ (1 + size l + size r) x l r
pattern Branch :: a -> Tree a -> Tree a -> Tree a
pattern Branch x l r <- (viewBranch -> Just (x, l, r))
where
Branch = branch
{-# COMPLETE Leaf, Branch #-}
{-------------------------------------------------------------------------------
'Show' instance that depends on the pattern synonyms
-------------------------------------------------------------------------------}
instance Show a => Show (Tree a) where
showsPrec _ Leaf = showString "Leaf"
showsPrec a (Branch x l r) = showParen (a > appPrec) $
showString "Branch "
. showsPrec appPrec1 x
. showSpace
. showsPrec appPrec1 l
. showSpace
. showsPrec appPrec1 r
{-------------------------------------------------------------------------------
Dealing with marks
-------------------------------------------------------------------------------}
-- | Propagate 'Drop' marker down the tree
--
-- This is useful in conjunction with 'genKept', which truncates entire
-- subtrees.
propagate :: Tree (Marked f a) -> Tree (Marked f a)
propagate = keep
where
keep :: Tree (Marked f a) -> Tree (Marked f a)
keep Leaf = Leaf
keep (Branch (Marked Keep x) l r) = Branch (Marked Keep x) (keep l) (keep r)
keep (Branch (Marked Drop x) l r) = Branch (Marked Drop x) (drop l) (drop r)
drop :: Tree (Marked f a) -> Tree (Marked f a)
drop = fmap $ \(Marked _ x) -> Marked Drop x
-- | Generate those values we want to keep
--
-- Whenever we meet an element marked 'Drop', that entire subtree is dropped.
genKept :: forall f a. Selective f => Tree (Marked f a) -> f (Tree a)
genKept = go
where
go :: Tree (Marked f a) -> f (Tree a)
go Leaf = pure Leaf
go (Branch (Marked m g) l r) = ifS (pure $ m == Keep)
(Branch <$> g <*> go l <*> go r)
(pure Leaf)
-- | Change enough nodes currently marked as 'Drop' to 'Keep' to ensure at
-- least @n@ nodes are marked 'Keep'.
--
-- Precondition: any 'Drop' marks must have been propagated; see 'propagate'.
-- Postcondition: this property is preserved.
keepAtLeast :: Word -> Tree (Marked f a) -> Tree (Marked f a)
keepAtLeast = \n t ->
let kept = countKept t
in if kept >= n
then t
else evalState (go t) (n - kept)
where
go :: Tree (Marked f a) -> State Word (Tree (Marked f a))
go Leaf = return Leaf
go (Branch (Marked Keep x) l r) = Branch (Marked Keep x) <$> go l <*> go r
go t@(Branch (Marked Drop x) l r) = get >>= \case
0 ->
-- Nothing left to drop
return t
n | size t <= n -> do
-- We can keep the entire subtree
put $ n - size t
return $ fmap (Marked Keep . unmark) t
n -> do
-- We cannot delete the entire subtree. In order to preserve the
-- "drop property", we /must/ mark this node as 'Keep'
put $ n - 1
Branch (Marked Keep x) <$> go l <*> go r
{-------------------------------------------------------------------------------
BST
-------------------------------------------------------------------------------}
data Endpoint a = Inclusive a | Exclusive a
data Interval a = Interval (Endpoint a) (Endpoint a)
-- | Compute interval with inclusive bounds, without exceeding range
--
-- Returns 'Nothing' if the interval is empty, and @Just@ the inclusive
-- lower and upper bound otherwise.
inclusiveBounds :: forall a. (Ord a, Enum a) => Interval a -> Maybe (a, a)
inclusiveBounds = \(Interval lo hi) -> go lo hi
where
-- The inequality checks in @go@ justify the use of @pred@ or @succ@
go :: Endpoint a -> Endpoint a -> Maybe (a, a)
go (Inclusive lo) (Inclusive hi)
| lo <= hi = Just (lo, hi)
| otherwise = Nothing
go (Exclusive lo) (Inclusive hi)
| lo < hi = Just (succ lo, hi)
| otherwise = Nothing
go (Inclusive lo) (Exclusive hi)
| lo < hi = Just (lo, pred hi)
| otherwise = Nothing
go (Exclusive lo) (Exclusive hi)
| lo < hi = if succ lo > pred hi
then Nothing
else Just (succ lo, pred hi)
| otherwise = Nothing
-- | Look value up in BST
--
-- NOTE: The 'Tree' datatype itself does /NOT/ guarantee that the tree is in
-- fact a BST. It is the responsibility of the caller to ensure this.
lookup :: Ord a => a -> Tree (a, b) -> Maybe b
lookup a' (Branch (a, b) l r)
| a' < a = lookup a' l
| a' > a = lookup a' r
| otherwise = Just b
lookup _ Leaf = Nothing
{-------------------------------------------------------------------------------
Debugging
-------------------------------------------------------------------------------}
drawTree :: Tree String -> String
drawTree = Rose.drawTree . conv
where
conv :: Tree String -> Rose.Tree String
conv Leaf = Rose.Node "*" []
conv (Branch x l r) = Rose.Node x [conv l, conv r]