calligraphy-0.1.0: test/Test/LexTree.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.LexTree where
import Calligraphy.Util.LexTree
import Control.Applicative
import Control.Monad
import qualified Data.Foldable as Foldable
import Data.Maybe (fromMaybe, isJust)
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
instance (Arbitrary a, Arbitrary p, Ord p, Num p) => Arbitrary (LexTree p a) where
arbitrary = sized goSized
where
goSized 0 = pure Tip
goSized n =
oneof
[ pure Tip,
do
m <- goSized (div n 2)
(l, r) <- suchThat (liftA2 (,) (goSized (div n 2)) (goSized (div n 2))) $ \(l, r) -> abs (height l - height r) < 3
a <- arbitrary
(padL, padR) <- suchThatMap arbitrary $ \(NonNegative padL, NonNegative padR) -> do
guard (padL > 0 || padL > 0)
pure (padL, padR)
NonNegative borderL <- arbitrary
NonNegative borderR <- arbitrary
let lr = maybe 0 snd (range l)
let (_ml, mr) = fromMaybe (0, 0) (range m)
pl = lr + borderL
pml = pl + padL
pmr = pml + mr
pr = pmr + padR + borderR
pure $ bin l pl a (shift pml m) (pmr + padR) (shift pr r)
]
shrink Tip = []
shrink (Bin _ ls l a ms r rs) =
concat
[ [Tip, ls, ms, rs],
(\ms' -> bin ls l a ms' r rs) <$> shrink ms,
(\a' -> bin ls l a' ms r rs) <$> shrink a
]
spec :: Spec
spec =
describe "LexTree" $ do
prop "arbitrary trees are valid" $ check @Int @()
prop "inserting increases length by 1" $ \l a r (t :: LexTree Int Int) ->
case insert l a r t of
Left _ -> discard
Right t' -> length t + 1 == length t'
describe "inserting produces valid trees" $ do
prop "inserting produces trees with valid bounds" $ \l a r (t :: LexTree Int Int) ->
case insert l a r t of
Left _ -> discard
Right t' -> checkBounds t'
prop "inserting produces trees with valid height" $ \l a r (t :: LexTree Int Int) ->
case insert l a r t of
Left _ -> discard
Right t' -> checkHeight t'
prop "inserting produces trees with valid balance" $ \l a r (t :: LexTree Int Int) ->
case insert l a r t of
Left _ -> discard
Right t' -> checkBalance t'
prop "after inserting, we can get the element back" $ \l a r (t :: LexTree Int Int) ->
case insert l a r t of
Left _ -> discard
Right t' -> elem a (lookupStack l t')
prop "inserting leaves other elements in the same order" $ \l p r (t :: LexTree Int Int) ->
let f (a : as) (b : bs) | a == b = f as bs
f (_ : as) bs = as == bs
f _ _ = False
in case insert l p r t of
Left _ -> discard
Right t' -> f (Foldable.toList t') (Foldable.toList t)
prop "listifying and back succeeds and is valid" $ \(t :: LexTree Int Int) ->
case foldM (\acc (l, a, r) -> insert l a r acc) Tip (toList t) of
Left _ -> False
Right t' -> check t'
prop "listifying and back succeeds and is the same tree" $ \(t :: LexTree Int Int) ->
case foldM (\acc (l, a, r) -> insert l a r acc) Tip (toList t) of
Left _ -> False
Right t' -> t == t'
checkBounds :: Ord p => LexTree p a -> Bool
checkBounds t = isJust $ foldLexTreeM (pure Nothing) f t
where
f ml bl _ msub br mr = do
guard (bl < br)
forM_ msub $ \(sl, sr) -> guard $ sl >= bl && sr <= br
l <- case ml of
Nothing -> pure bl
Just (ll, lr) -> do
guard $ lr <= bl
pure ll
r <- case mr of
Nothing -> pure br
Just (rl, rr) -> do
guard $ rl >= br
pure rr
pure (Just (l, r))
foldLexTreeM :: Monad m => m r -> (r -> p -> a -> r -> p -> r -> m r) -> LexTree p a -> m r
foldLexTreeM fTip fBin =
foldLexTree
fTip
(\ml l a mm r mr -> do l' <- ml; m' <- mm; r' <- mr; fBin l' l a m' r r')
check :: Ord p => LexTree p a -> Bool
check t = checkBounds t && checkHeight t && checkBalance t
checkHeight :: LexTree p a -> Bool
checkHeight = isJust . go
where
go Tip = pure 0
go (Bin h ml _ _ m _ mr) = do
l <- go ml
r <- go mr
_ <- go m
let h' = max l r + 1
guard (h == h')
pure h'
checkBalance :: LexTree p a -> Bool
checkBalance Tip = True
checkBalance (Bin _ l _ _ m _ r) =
and
[ checkBalance l,
checkBalance r,
checkBalance m,
abs (height l - height r) < 2
]
range :: LexTree p a -> Maybe (p, p)
range = foldLexTree Nothing $ \l pl _ _ pr r -> Just (maybe pl fst l, maybe pr snd r)
lookupStack :: Ord p => p -> LexTree p a -> [a]
lookupStack p = foldLexTree [] f
where
f ls l a m r rs
| p >= l && p < r = a : m
| p < l = ls
| p >= r = rs
| otherwise = error "impossible"