ac-library-hs-1.5.2.1: test/Tests/Util.hs
module Tests.Util
( myForAllShrink,
laws,
intervalGen,
intervalGen',
Dag (..),
genDag,
Tree (..),
genTree,
)
where
import AtCoder.Extra.Graph qualified as Gr
import AtCoder.Internal.Assert qualified as ACIA
import Data.Foldable (for_)
import Data.Maybe (fromJust)
import Data.Proxy (Proxy (..))
import Data.Typeable (Typeable, typeRep)
import Data.Vector.Generic qualified as VG
import Data.Vector.Generic.Mutable qualified as VGM
import Data.Vector.Unboxed qualified as VU
import Data.Vector.Unboxed.Mutable qualified as VUM
import Test.QuickCheck.Classes qualified as QCC
import Test.QuickCheck.Property qualified as QC
import Test.Tasty
import Test.Tasty.QuickCheck qualified as QC
-- | Taken from `quickcheck-classes-base`.
myForAllShrink ::
(QC.Arbitrary a, Show b, Eq b) =>
Bool -> -- Should we show the RHS. It's better not to show it if the RHS is equal to the input.
(a -> Bool) -> -- is the value a valid input
(a -> [String]) -> -- show the 'a' values
String -> -- show the LHS
(a -> b) -> -- the function that makes the LHS
String -> -- show the RHS
(a -> b) -> -- the function that makes the RHS
QC.Property
myForAllShrink displayRhs isValid showInputs name1 calc1 name2 calc2 =
QC.MkProperty $
QC.arbitrary >>= \x ->
QC.unProperty $
QC.shrinking QC.shrink x $ \x' ->
let b1 = calc1 x'
b2 = calc2 x'
sb1 = show b1
sb2 = show b2
description = " Description: " ++ name1 ++ " = " ++ name2
err = description ++ "\n" ++ unlines (map (" " ++) (showInputs x')) ++ " " ++ name1 ++ " = " ++ sb1 ++ (if displayRhs then "\n " ++ name2 ++ " = " ++ sb2 else "")
in isValid x' QC.==> QC.counterexample err (b1 == b2)
-- | Taken from `quickcheck-classes-base`.
laws :: forall a. (Typeable a) => [Proxy a -> QCC.Laws] -> TestTree
laws =
testGroup (show (typeRep (Proxy @a)))
. map
( \f ->
let QCC.Laws name pairs = f (Proxy @a)
in testGroup name (map (uncurry QC.testProperty) pairs)
)
-- | Returns an interval [l, r) in [0, n)
intervalGen :: Int -> QC.Gen (Int, Int)
intervalGen = intervalGen' 0
-- | Returns an interval [l, r) in [0, n)
intervalGen' :: Int -> Int -> QC.Gen (Int, Int)
intervalGen' xl xr = do
l <- QC.chooseInt (xl, xr)
r <- QC.chooseInt (l, xr)
pure (l, r)
genDag :: forall w. (QC.Arbitrary w, VU.Unbox w) => Int -> QC.Gen (Gr.Csr w)
genDag n = do
edges <- VU.fromList <$> QC.sublistOf [(u, v) | u <- [0 .. n - 1], v <- [u + 1 .. n - 1]]
verts <- VU.fromList <$> QC.shuffle [0 .. n - 1]
ws <- VU.fromList <$> QC.vectorOf n (QC.arbitrary @w)
let es = VU.zipWith (\(!u, !v) !w -> (verts VG.! u, verts VG.! v, w)) edges ws
pure $ Gr.build n es
-- | Newtype for generating a random DAG.
newtype Dag w = Dag (Gr.Csr w)
instance (QC.Arbitrary w, VU.Unbox w) => QC.Arbitrary (Dag w) where
arbitrary = do
QC.Positive n <- QC.arbitrary
Dag <$> genDag n
-- TODO: commit the upper code first
-- | Prüfer sequence: https://en.wikipedia.org/wiki/Pr%C3%BCfer_sequence
genTreeEdge :: Int -> QC.Gen (VU.Vector (Int, Int))
genTreeEdge n = case n of
1 -> pure VU.empty
2 -> pure $ VU.singleton (0, 1)
_ -> do
prufer <- QC.vectorOf (n - 2) (QC.chooseInt (0, n - 1))
pure $ VU.create $ do
-- sum(deg) = n + (n - 2) at the beginning
deg <- VUM.replicate n (1 :: Int)
for_ prufer $ \v -> do
VGM.modify deg (+ 1) v
edges <- VUM.unsafeNew (n - 1)
-- sum(deg) will be 2(n - 1) - 2(n - 2) = 2
for_ (zip [0 :: Int ..] prufer) $ \(!i, !u) -> do
-- NOTE: At the beginning, deg[u] > 1, and deg[i] is never decreased until iterated
!v <- fromJust . VU.findIndex (== 1) <$> VU.unsafeFreeze deg
-- So we're sure u /= v
let !_ = ACIA.runtimeAssert (u /= v) "u /= v"
VGM.write edges i (u, v)
VGM.modify deg (subtract 1) u
VGM.modify deg (subtract 1) v
-- The last two vertices with degree one make up the last edge
uv <- VU.findIndices (== 1) <$> VU.unsafeFreeze deg
let !_ = ACIA.runtimeAssert (VU.length uv == 2) "not found uv?"
VGM.write edges (n - 1) (uv VG.! 0, uv VG.! 1)
-- done
pure edges
genTree :: forall w. (QC.Arbitrary w, VU.Unbox w) => Int -> QC.Gen (Gr.Csr w)
genTree n = do
es <- genTreeEdge n
ws <- VU.fromList <$> QC.vectorOf (n - 1) (QC.arbitrary @w)
pure . Gr.build n . Gr.swapDupe $ VU.zipWith (\(!u, !v) !w -> (u, v, w)) es ws
-- | Newtype for generating a random tree.
newtype Tree w = Tree (Gr.Csr w)
instance (QC.Arbitrary w, VU.Unbox w) => QC.Arbitrary (Tree w) where
arbitrary = do
QC.Positive n <- QC.arbitrary
Tree <$> genTree n