ac-library-hs-1.5.2.1: test/Tests/Extra/HashMap.hs
{-# LANGUAGE RecordWildCards #-}
module Tests.Extra.HashMap where
import AtCoder.Extra.HashMap qualified as HM
import Control.Monad (foldM_)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Control.Monad.ST (RealWorld)
import Data.HashMap.Strict qualified as HMR -- R: referential implementation
import Data.Vector.Algorithms.Intro qualified as VAI
import Data.Vector.Unboxed qualified as VU
import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec
import Test.QuickCheck.Monadic as QCM
import Test.Tasty
import Test.Tasty.Hspec
import Test.Tasty.QuickCheck as QC
spec_invalid :: IO TestTree
spec_invalid = testSpec "capacity limit" $ do
it "throws error 1" $ do
hm <- HM.new @_ @Int 1
HM.insert hm 0 0
HM.insert hm 0 1
HM.insert hm 1 2 `shouldThrow` anyException
it "throws error 2" $ do
hm <- HM.new @_ @Int 2
HM.insert hm 0 0
HM.insert hm 1 1
HM.insert hm 1 2
HM.insert hm 2 2 `shouldThrow` anyException
data Init = Init
{ capacity :: {-# UNPACK #-} !Int,
ref0 :: !(HMR.HashMap Int Int),
hmM :: !(IO (HM.HashMap RealWorld Int))
}
instance Show Init where
show Init {..} = show (capacity, ref0)
instance QC.Arbitrary Init where
arbitrary = do
capacity <- QC.chooseInt (1, 10)
pure $ Init capacity HMR.empty (HM.new capacity)
data Query
= Size
| Member Int
| NotMember Int
| Lookup Int
| Insert Int Int
| InsertWithAdd Int Int
| Exchange Int Int
| ModifyAdd Int Int
| Clear
deriving (Show)
instance QC.Arbitrary Query where
arbitrary = do
QC.frequency
[ (rare, pure Clear),
(often, pure Size),
(often, Member <$> keyGen),
(often, NotMember <$> keyGen),
(often, Lookup <$> keyGen),
(often, Insert <$> keyGen <*> valGen),
(often, InsertWithAdd <$> keyGen <*> valGen),
(often, Exchange <$> keyGen <*> valGen),
(often, ModifyAdd <$> keyGen <*> valGen)
]
where
rare = 1
often = 10
keyGen = QC.chooseInt (-5, 5)
valGen = QC.arbitrary @Int
-- | Arbitrary return type for the `Query` result.
data Result
= None
| B Bool
| I Int
| M (Maybe Int)
deriving (Show, Eq)
-- | containers. (referential implementation)
handleRef :: HMR.HashMap Int Int -> Query -> (HMR.HashMap Int Int, Result)
handleRef hm q = case q of
Size -> (hm, I $ HMR.size hm)
Member k -> (hm, B $ HMR.member k hm)
NotMember k -> (hm, B . not $ HMR.member k hm)
Lookup k -> (hm, M $ HMR.lookup k hm)
Insert k v -> (HMR.insert k v hm, None)
InsertWithAdd k v -> (HMR.insertWith (+) k v hm, None)
Exchange k v -> (HMR.insert k v hm, M $ HMR.lookup k hm)
ModifyAdd k v -> (HMR.adjust (+ v) k hm, None)
-- Delete k -> (HMR.delete k hm, HMR.lookup k hm)
Clear -> (HMR.empty, None)
-- | ac-library-hs.
handleAcl :: (PrimMonad m) => HM.HashMap (PrimState m) Int -> Query -> m Result
handleAcl hm q = case q of
Size -> I <$> HM.size hm
Member k -> B <$> HM.member hm k
NotMember k -> B <$> HM.notMember hm k
Lookup k -> M <$> HM.lookup hm k
Insert k v -> do
HM.insert hm k v
pure None
InsertWithAdd k v -> do
HM.insertWith hm (+) k v
pure None
Exchange k v -> M <$> HM.exchange hm k v
ModifyAdd k v -> do
HM.modify hm (+ v) k
pure None
-- Delete k -> HM.delete hm k
Clear -> do
HM.clear hm
pure None
-- | Ensures the capacity limit.
passQuery :: Int -> HMR.HashMap Int Int -> Query -> Bool
passQuery limit is (Insert k _) = HMR.member k is || HMR.size is < limit
passQuery limit is (InsertWithAdd k _) = HMR.member k is || HMR.size is < limit
passQuery limit is (Exchange k _) = HMR.member k is || HMR.size is < limit
passQuery _ _ _ = True
prop_randomTest :: Init -> QC.Property
prop_randomTest Init {..} = QCM.monadicIO $ do
hm <- QCM.run hmM
q <- QCM.pick $ QC.chooseInt (1, 5 * capacity)
qs <- QCM.pick $ QC.vectorOf q (QC.arbitrary @Query)
foldM_
( \ref query -> do
if passQuery capacity ref query
then do
let (!ref', !expected) = handleRef ref query
res <- QCM.run $ handleAcl hm query
QCM.assertWith (expected == res) $ show (query, expected, res)
-- check the map contents:
let assocsE = VU.modify (VAI.sortBy compare) . VU.fromList $ HMR.toList ref'
assocs <- QCM.run $ VU.modify (VAI.sortBy compare) <$> HM.unsafeAssocs hm
QCM.assertWith (assocsE == assocs) $ show ("- assocs", assocsE, assocs)
let keysE = VU.modify (VAI.sortBy compare) . VU.fromList $ HMR.keys ref'
keys <- QCM.run $ VU.modify (VAI.sortBy compare) <$> HM.unsafeKeys hm
QCM.assertWith (keysE == keys) $ show ("- keys", keysE, keys)
let elemsE = VU.modify (VAI.sortBy compare) . VU.fromList $ HMR.elems ref'
elems <- QCM.run $ VU.modify (VAI.sortBy compare) <$> HM.unsafeElems hm
QCM.assertWith (elemsE == elems) $ show ("- elems", elemsE, elems)
let sizeE = HMR.size ref'
size <- QCM.run $ HM.size hm
QCM.assertWith (sizeE == size) $ show ("- size", sizeE, size)
pure ref'
else pure ref
)
ref0
qs
tests :: [TestTree]
tests =
[ unsafePerformIO spec_invalid,
QC.testProperty "random test" prop_randomTest
]