dyckword-0.1.0.1: tests/Suite.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad ( forM_ )
import Data.Either ( isLeft )
import Data.Monoid ( (<>) )
import System.Console.ANSI
import System.Exit ( exitFailure )
import System.IO
import Test.Hspec
import Math.DyckWord.Binary
import Math.DyckWord.Binary.Internal
import qualified Data.Text as T
lastN :: Int -> [a] -> [a]
lastN n xs = drop (length xs - n) xs
showN :: Integer -> String
showN n
| n > 10^30 = "..." ++ lastN 20 (show n) ++ " (huge number)"
| otherwise = show n
saysThat :: String -> IO a -> IO a
saysThat s action = do
setCursorColumn 2
putStr s
clearFromCursorToLineEnd
hFlush stdout
action
resetCursor :: IO ()
resetCursor = do
setCursorColumn 0
clearFromCursorToLineEnd
infixl 0 `saysThat`
makeSure :: Bool -> Expectation
makeSure = shouldBe True
rankAfterUnrank :: Integer -> IO ()
rankAfterUnrank n = showN n `saysThat` rank (unrank n) `shouldBe` n
checkSize :: Integer -> Integer -> IO ()
checkSize s n = show (s, n) `saysThat` size (unrankRelative' s n) `shouldBe` s
sizeShouldBeHomomorphic :: T.Text -> T.Text -> Expectation
sizeShouldBeHomomorphic a b = size (u <> v) `shouldBe` (size u + size v)
where
u = fromText' a
v = fromText' b
checkIdentity :: Integer -> IO ()
checkIdentity n = show n `saysThat` do
empty <> unrank n `shouldBe` (unrank n)
unrank n <> empty `shouldBe` (unrank n)
checkAssociativity :: (Integer, Integer, Integer) -> IO ()
checkAssociativity triple@(a, b, c) = show triple `saysThat` p
where
p = ((u <> v) <> w) `shouldBe` (u <> (v <> w))
u = unrank a
v = unrank b
w = unrank c
checkWordsOfSizeCatalan :: Integer -> IO ()
checkWordsOfSizeCatalan n =
show c_n ++ " words of size " ++ show n `saysThat`
length (wordsOfSize n) `shouldBe` c_n
where
c_n = catalan n
checkWordsOfSizeAgainstUnrank :: Integer -> Integer -> IO ()
checkWordsOfSizeAgainstUnrank i j =
show (i, j) `saysThat`
wordsOfSize i !! (fromIntegral j) `shouldBe` unrankRelative' i j
main :: IO ()
main = hspec $ do
describe "unrankRelative" $ do
it "returns a dyck word with the expected size" $ do
forM_ [10, 12, 22, 123, 425, 1028] $
forM_ [1..22] . checkSize
resetCursor
describe "rank" $ do
it "is the inverse of unrank" $ do
forM_ ( [catalan 31..catalan 31+80]
++ [catalan 131..catalan 131+192]
++ [catalan 431..catalan 431+95]
++ [catalan 21..catalan 21+284]
++ [catalan 11..catalan 11+989]
++ [0..1000] ) rankAfterUnrank
resetCursor
describe "unrankRelative" $ do
it "returns Nothing for invalid relative rank" $ do
unrankRelative 0 2 `shouldBe` Nothing
unrankRelative 1 8 `shouldBe` Nothing
unrankRelative 3 5 `shouldBe` Nothing
unrankRelative 1111 (catalan 1111) `shouldBe` Nothing
unrankRelative 0 (-1) `shouldBe` Nothing
unrankRelative 5 (-1) `shouldBe` Nothing
unrankRelative 5 1231231238837431321283123981237123 `shouldBe` Nothing
describe "empty" $ do
it "has size 0" $
size empty `shouldBe` 0
it "matches the empty Text value" $
toText empty `shouldBe` T.empty
it "has rank 0, and relative rank 0" $ do
rank empty `shouldBe` 0
rankRelative empty `shouldBe` 0
describe "Ord" $ do
it "works as expected" $ do
makeSure (unrank 100 <= unrank 101)
makeSure (unrank 0 <= unrank 1)
makeSure (unrank 1 > unrank 0)
makeSure (unrank (10^16) > unrank (10^15))
makeSure $ not (unrank 1235 > unrank 2000)
describe "Eq" $ do
it "works with unrank" $ do
makeSure (unrank 0 == empty)
makeSure (unrank 1 == fromText' "()")
makeSure (unrank 2 == fromText' "(())")
makeSure (unrank 3 == fromText' "()()")
makeSure (unrank 123 == fromText' "(()(())()())")
makeSure (unrank 123 == fromText' "001001101011")
it "works with different alphabets" $ do
makeSure (fromText' "(()(())()())" == fromText' "001001101011")
describe "setAlphabet" $ do
it "replaces the characters in a word's Text value" $ do
toText (setAlphabet '0' '1' $ unrank 10) `shouldBe` "00010111"
toText (setAlphabet '(' ')' $ fromText' "0000111101") `shouldBe` "(((())))()"
toText (setAlphabet '1' '0' $ fromText' "0000111101") `shouldBe` "1111000010"
toText (setAlphabet 'a' 'b' $ fromText' "xyxyxxyy") `shouldBe` "ababaabb"
describe "concatWords" $ do
it "returns a word with size equal to the sum of the sizes of the inputs" $ do
sizeShouldBeHomomorphic "(()())" "(((())))"
sizeShouldBeHomomorphic "ababaaaabbbb" "(((())))"
sizeShouldBeHomomorphic "" "(((())))"
it "works with different alphabets" $ do
concatWords (fromText' "()()") (fromText' "aaaabbbb") `shouldBe` (fromText' "()()(((())))")
concatWords (fromText' "()()") (fromText' "aaaabbbb") `shouldBe` (fromText' "010100001111")
it "satisfies the identity law" $ do
forM_ [0, 20..33350] checkIdentity
resetCursor
it "is associative" $ do
forM_ [(a, b, c) | a <- [0, 20, 1000], b <- [0..300], c <- [0, 50..200]] checkAssociativity
resetCursor
describe "wordsOfSize" $ do
it "matches the Catalan numbers" $ do
forM_ [0..17] checkWordsOfSizeCatalan
resetCursor
it "matches the result of unrankRelative'" $ do
forM_ [8..12] $ forM_ [8..1125] . checkWordsOfSizeAgainstUnrank
resetCursor
describe "fromText" $ do
it "should return a Left value on bad input" $ do
fromText "(((" `shouldSatisfy` isLeft
fromText "((()" `shouldSatisfy` isLeft
fromText "((())" `shouldSatisfy` isLeft
fromText "))(" `shouldSatisfy` isLeft
fromText "))()" `shouldSatisfy` isLeft
fromText "(()))" `shouldSatisfy` isLeft
fromText "((x)" `shouldSatisfy` isLeft