dawg-ord-0.5.0.1: tests/Ord.hs
{-# LANGUAGE FlexibleContexts #-}
-- | Tests for the "Data.DAWG.Ord" module.
module Ord where
import qualified Control.Monad.State.Strict as E
import qualified Data.Set as S
import qualified Data.Map.Strict as M
import Test.Tasty
import qualified Test.Tasty.SmallCheck as SC
import qualified Test.SmallCheck.Series as SC
import qualified Test.Tasty.QuickCheck as QC
import Test.Tasty.HUnit
import qualified Data.DAWG.Ord as D
tests :: TestTree
tests = testGroup "Tests" [properties, unitTests]
properties :: TestTree
properties = testGroup "Properties" [scProps, qcProps]
scProps = testGroup "(checked by SmallCheck)"
[ SC.testProperty "id == assocs . fromList (up to order)" $
\xs -> M.fromList (D.assocs (D.fromList xs))
== M.fromList (xs :: [(String, Int)])
, SC.testProperty
"Number of states and transitions in a \"full\" langauge" $
SC.changeDepth (+1) $ \n k ->
let dawg = D.fromLang (genFull n k)
in D.numStates dawg == SC.getNonNegative k + 1 &&
D.numEdges dawg ==
SC.getPositive n * SC.getNonNegative k
, SC.testProperty "Actual number of transitions == D.numEdges" $
\xs -> let dawg = D.fromList (xs :: [(String, Int)])
in S.size (walk dawg) == D.numEdges dawg
, SC.testProperty "Actual number of states == D.numStates" $
\xs -> let dawg = D.fromList (xs :: [(String, Int)])
in D.numStates dawg ==
S.size (states (D.root dawg) (walk dawg))
]
qcProps = testGroup "(checked by QuickCheck)"
[ QC.testProperty "id == assocs . fromList (up to order)" $
\xs -> M.fromList (D.assocs (D.fromList xs))
== M.fromList (xs :: [(String, Int)])
, QC.testProperty "Actual number of transitions == D.numEdges" $
\xs -> let dawg = D.fromList (xs :: [(String, Int)])
in S.size (walk dawg) == D.numEdges dawg
, QC.testProperty "Actual number of states == D.numStates" $
\xs -> let dawg = D.fromList (xs :: [(String, Int)])
in D.numStates dawg ==
S.size (states (D.root dawg) (walk dawg))
]
unitTests = testGroup "Unit tests"
[ testCase "Size of a DAWG build from sample data" $ do
let dawg = D.fromList dataSet1
D.numStates dawg @?= 11
D.numEdges dawg @?= 12
]
---------------------------------------------------------------------
-- Sample Data
---------------------------------------------------------------------
-- | Sample dataset no. 1. See also `unitTests`.
dataSet1 :: [(String, Int)]
dataSet1 =
[ ("asdf", 1)
, ("asd", 1)
, ("adf", 1)
, ("df", 1)
, ("asdfg", 3)
, ("sdfg", 3) ]
---------------------------------------------------------------------
-- Utils
---------------------------------------------------------------------
-- | Generate a \"full\" language of words of length `k` over
-- an alphabet of size `n`.
genFull :: SC.Positive Int -> SC.NonNegative Int -> [[Int]]
genFull (SC.Positive n) (SC.NonNegative k) =
genLang n k
where
genLang n 0 = [[]]
genLang n k =
[ x:xs
| xs <- genLang n (k - 1)
, x <- [1 .. n]]
-- | Traverse the automaton and collect all the transitions.
walk :: Ord a => D.DAWG a b -> S.Set (D.ID, a, D.ID)
walk dawg =
flip E.execState S.empty $
flip E.evalStateT S.empty $
doit (D.root dawg)
where
-- The embedded state serves to store the resulting set of
-- transitions; the surface state serves to keep track of
-- already visited nodes.
doit i = do
b <- E.gets $ S.member i
E.when (not b) $ do
E.modify $ S.insert i
E.forM_ (D.edges i dawg) $ \(x, j) -> do
E.lift . E.modify $ S.insert (i, x, j)
-- E.lift . E.modify $ S.insert (i, j)
doit j
-- | Compute the set of states given the set of transitions and
-- the root ID. It works under the assumption that all states
-- are reachable from the start state.
states :: Ord a => a -> S.Set (a, b, a) -> S.Set a
states rootID edgeSet = S.fromList $ rootID : concat
[[i, j] | (i, _, j) <- S.toList edgeSet]