packages feed

ac-library-hs-1.2.1.0: test/Tests/Extra/Seq/Map.hs

{-# LANGUAGE RecordWildCards #-}

module Tests.Extra.Seq.Map (tests) where

import AtCoder.Extra.Monoid.Affine1 (Affine1 (..))
import AtCoder.Extra.Monoid.Affine1 qualified as Affine1
import AtCoder.Extra.Seq.Map qualified as Map
import AtCoder.LazySegTree (SegAct (..))
import Control.Monad (foldM_)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Control.Monad.ST (RealWorld, runST)
import Data.List qualified as L
import Data.Map qualified as M
import Data.Semigroup (Sum (..))
import Data.Vector.Algorithms.Intro qualified as VAI
import Data.Vector.Unboxed qualified as VU
import Test.Hspec
import Test.QuickCheck.Monadic as QCM
import Test.Tasty
import Test.Tasty.QuickCheck as QC
import Tests.Util (intervalGen)
import Prelude hiding (seq)

data Init = Init
  { q :: {-# UNPACK #-} !Int,
    ref0 :: !(M.Map Int (Sum Int)),
    mapM :: !(IO (Map.Map RealWorld (Affine1 Int) Int (Sum Int)))
  }

instance Show Init where
  show Init {..} = show ref0

instance QC.Arbitrary Init where
  arbitrary = do
    q <- QC.chooseInt (1, 640)
    pure $ Init q M.empty $ Map.new q

data Query
  = Reset
  | Capacity
  | Size
  | Member !Int
  | Adjust !Int !(Sum Int)
  | Insert !Int !(Sum Int)
  | InsertWith !Int !(Sum Int)
  | Delete !Int
  | Delete_ !Int
  | Prod !(Int, Int)
  | ProdMaybe !(Int, Int)
  | ProdAll
  | ApplyIn !(Int, Int) !(Affine1 Int)
  | ApplyAll !(Affine1 Int)
  | LookupLE !Int
  | LookupLT !Int
  | LookupGE !Int
  | LookupGT !Int
  | ReadAt !Int
  | ReadMaybeAt !Int
  | WriteAt !Int !(Sum Int)
  | ExchangeAt !Int !(Sum Int)
  | ProdInInterval !(Int, Int)
  | ApplyInInterval !(Int, Int) !(Affine1 Int)
  -- | ILowerBound !(Sum Int)
  -- | ILowerBoundM !(Sum Int)
  | ILowerBoundProd !(Sum Int)
  | ILowerBoundProdM !(Sum Int)
  | Freeze
  deriving (Show)

-- | Arbitrary return type for the `Query` result.
data Result
  = None
  | B !Bool
  | I !Int
  | S !(Sum Int)
  | MKV !(Maybe (Int, Sum Int))
  | MS !(Maybe (Sum Int))
  | F !(VU.Vector (Int, Sum Int))
  deriving (Show, Eq)

queryGen :: Int -> QC.Gen Query
queryGen n = do
  QC.frequency $
    [ (rare, pure Reset),
      (rare, pure Capacity),
      (rare, pure Size),
      (often, Member <$> keyGen),
      (often, Adjust <$> keyGen <*> valGen),
      (half, Insert <$> keyGen <*> valGen),
      (half, InsertWith <$> keyGen <*> valGen),
      (half, Delete <$> keyGen),
      (half, Delete_ <$> keyGen),
      (half, Prod <$> keyGen2),
      (half, (ProdMaybe .) . (,) <$> keyGen <*> keyGen),
      (half, pure ProdAll),
      (often, ApplyIn <$> keyGen2 <*> fGen),
      (often, ApplyAll <$> fGen),
      (half, LookupLE <$> keyGen),
      (half, LookupLT <$> keyGen),
      (half, LookupGE <$> keyGen),
      (half, LookupGT <$> keyGen),
      -- (half, ReadAt <$> iGen),
      (half, ReadMaybeAt <$> maybeIGen),
      -- (half, WriteAt <$> iGen <*> valGen),
      -- (half, ExchangeAt <$> iGen <*> valGen),
      (half, ProdInInterval <$> intervalGen n),
      (often, ApplyInInterval <$> intervalGen n <*> fGen),
      (half, ILowerBoundProd <$> valGen),
      (half, ILowerBoundProdM <$> valGen),
      (rare, pure Freeze)
    ]
      ++ if n == 0
        then []
        else
          [ (half, ReadAt <$> iGen),
            (half, WriteAt <$> iGen <*> valGen),
            (half, ExchangeAt <$> iGen <*> valGen)
          ]
  where
    rare = 1
    often = 10
    half = 5
    keyGen = QC.chooseInt (-30, 30)
    keyGen2 = do
      l <- QC.chooseInt (-30, 30)
      r <- QC.chooseInt (l, 30)
      pure (l, r)
    -- use non-negative values for monotoniously increasing sum
    valGen = Sum <$> QC.chooseInt (0, 10)
    -- NOTE: it might throw an error on overflow:
    fGen = Affine1.new <$> QC.chooseInt (0, 4) <*> QC.chooseInt (0, 4)
    iGen = QC.chooseInt (0, n - 1)
    maybeIGen = QC.chooseInt (-1, n)

-- | containers. (referencial implementation)
handleRef :: Int -> M.Map Int (Sum Int) -> Query -> (M.Map Int (Sum Int), Result)
handleRef capacity m q = case q of
  Reset -> (M.empty, None)
  Capacity -> (m, I capacity)
  Size -> (m, I (M.size m))
  Member k -> (m, B (M.member k m))
  Adjust k v -> (M.adjust (+ v) k m, None)
  Insert k v -> (M.insert k v m, None)
  InsertWith k v -> (M.insertWith (+) k v m, None)
  Delete k -> (M.delete k m, MS $ M.lookup k m)
  Delete_ k -> (M.delete k m, None)
  Prod (!l, !r) -> (m, S $ prod l r)
  ProdMaybe (!l, !r)
    | l > r -> (m, MS Nothing)
    | otherwise -> (m, MS . Just $ prod l r)
  ProdAll -> (m, S $ M.foldl' (<>) mempty m)
  ApplyIn (!l, !r) f -> (apply l r f, None)
  ApplyAll f -> (M.map (segAct f) m, None)
  LookupLE k -> (m, MKV $ M.lookupLE k m)
  LookupLT k -> (m, MKV $ M.lookupLT k m)
  LookupGE k -> (m, MKV $ M.lookupGE k m)
  LookupGT k -> (m, MKV $ M.lookupGT k m)
  ReadAt i -> (m, S . snd $ M.elemAt i m)
  ReadMaybeAt i
    | 0 <= i && i < M.size m -> (m, MS $ snd <$> M.lookupMin (M.drop i m))
    | otherwise -> (m, MS Nothing)
  WriteAt i x -> (M.updateAt (\_ _ -> Just x) i m, None)
  ExchangeAt i x -> (M.updateAt (\_ _ -> Just x) i m, S (snd (M.elemAt i m)))
  ProdInInterval (!l, !r) -> (m, S (prodIn l r))
  ApplyInInterval (!l, !r) f -> (applyIn l r f, None)
  ILowerBoundProd v -> (m, lbProd v)
  ILowerBoundProdM v -> (m, lbProd v)
  Freeze -> (m, F (VU.fromList (M.assocs m)))
  where
    slice l r = M.takeWhileAntitone (< r) . M.dropWhileAntitone (< l)
    prod l r = M.foldl' (<>) mempty $ slice l r m
    apply l r f = M.mapWithKey (\k x -> if l <= k && k < r then segAct f x else x) m
    prodIn l r =
      L.foldl' (<>) mempty
        . map snd
        . filter (\(!i, !_) -> l <= i && i < r)
        . zip [0 :: Int ..]
        $ M.elems m
    applyIn l r f =
      M.fromList
        . zipWith
          (\i (!k, !x) -> if l <= i && i < r then (k, segAct f x) else (k, x))
          [0 :: Int ..]
        $ M.assocs m
    -- lb x = I . length . takeWhile (<= x) $ M.elems m
    lbProd x = I . length . takeWhile (<= x) . tail . L.scanl' (<>) mempty $ M.elems m

-- | ac-library-hs.
handleAcl :: (HasCallStack, PrimMonad m) => Map.Map (PrimState m) (Affine1 Int) Int (Sum Int) -> Query -> m Result
handleAcl m q = case q of
  Reset -> do
    Map.reset m
    pure None
  Capacity -> do
    pure . I $ Map.capacity m
  Size -> do
    I <$> Map.size m
  Insert k v -> do
    Map.insert m k v
    pure None
  InsertWith k v -> do
    Map.insertWith m (+) k v
    pure None
  Adjust k v -> do
    Map.adjust m (+ v) k
    pure None
  Delete k -> do
    MS <$> Map.delete m k
  Delete_ k -> do
    Map.delete_ m k
    pure None
  Prod (!l, !r) -> do
    S <$> Map.prod m l r
  ProdMaybe (!l, !r) -> do
    MS <$> Map.prodMaybe m l r
  ProdAll -> do
    S <$> Map.allProd m
  ApplyIn (!l, !r) f -> do
    Map.applyIn m l r f
    pure None
  ApplyAll f -> do
    Map.applyAll m f
    pure None
  Member k -> do
    B <$> Map.member m k
  LookupLE v -> do
    MKV <$> Map.lookupLE m v
  LookupLT v -> do
    MKV <$> Map.lookupLT m v
  LookupGE v -> do
    MKV <$> Map.lookupGE m v
  LookupGT v -> do
    MKV <$> Map.lookupGT m v
  ReadAt i -> do
    S <$> Map.readAt m i
  ReadMaybeAt i -> do
    MS <$> Map.readMaybeAt m i
  WriteAt i x -> do
    Map.writeAt m i x
    pure None
  ExchangeAt i x -> do
    S <$> Map.exchangeAt m i x
  ProdInInterval (!l, !r) -> do
    S <$> Map.prodInInterval m l r
  ApplyInInterval (!l, !r) f -> do
    Map.applyInInterval m l r f
    pure None
  ILowerBoundProd xRef -> do
    I <$> Map.ilowerBoundProd m (\_ x -> x <= xRef)
  ILowerBoundProdM xRef -> do
    I <$> Map.ilowerBoundProdM m (\_ x -> pure (x <= xRef))
  Freeze -> do
    F <$> Map.freeze m

prop_randomTest :: Init -> QC.Property
prop_randomTest Init {..} = QCM.monadicIO $ do
  m <- QCM.run mapM
  foldM_
    ( \ref _ -> do
        query <- QCM.pick (queryGen (M.size ref))
        -- run the query
        let (!ref', !expected) = handleRef q ref query
        res <- QCM.run $ handleAcl m query
        QCM.assertWith (expected == res) $ show (query, expected, res)
        pure ref'
    )
    ref0
    [0 .. q - 1]

prop_build :: [Int] -> QC.Property
prop_build ks_ =
  let ks = VU.fromList ks_
      expected = VU.modify VAI.sort ks
      res = runST $ do
        m <- Map.build @_ @() @Int @() (VU.length ks) $ VU.map (,()) ks
        VU.map fst <$> Map.freeze m
   in expected QC.=== res

tests :: [TestTree]
tests =
  [ QC.testProperty "random test" prop_randomTest,
    QC.testProperty "build" prop_build
  ]