packages feed

ac-library-hs-1.2.2.0: test/Tests/FenwickTree.hs

-- | Fenwick tree tests.
module Tests.FenwickTree (tests) where

import AtCoder.FenwickTree qualified as FT
import Control.Monad.ST (runST)
import Data.Foldable
import Data.Vector.Unboxed qualified as VU
import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec
import Test.QuickCheck qualified as QC
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.Hspec
import Test.Tasty.QuickCheck qualified as QC

-- empty
-- assign

unit_zero :: TestTree
unit_zero = testCase "zero" $ do
  ft <- FT.new @_ @Int 0
  (@?= 0) =<< FT.sum ft 0 0

-- TODO: add modint test

-- FIXME: it's uint test and not works with Int
-- overFlowInt :: TestTree
-- overFlowInt = testCase "overFlowInt" $ do
--   ft <- FT.new @_ @Int 10
--   for_ [0 .. 10 - 1] $ \i -> do
--     FT.add ft i (bit 63 + i)
--   for_ [0 .. 10] $ \i -> do
--     for_ [0 .. 10] $ \j -> do
--       let s = sum [i .. j - 1]
--       let expected = if (j - i) `mod` 2 == 1 then bit 63 + s else s
--       result <- FT.sum ft i j
--       result @?= expected

unit_naive :: TestTree
unit_naive = testCase "naive" $ do
  for_ [0 .. 50] $ \n -> do
    ft <- FT.new @_ @Int n
    for_ [0 .. n - 1] $ \i -> do
      FT.add ft i (i * i)
    for_ [0 .. n] $ \l -> do
      for_ [l .. n] $ \r -> do
        let s = sum [i * i | i <- [l .. r - 1]]
        (@?= s) =<< FT.sum ft l r

-- TODO: smint
-- TODO: mint

unit_sumMaybeBounds :: TestTree
unit_sumMaybeBounds = testCase "sumMaybeBounds" $ do
  ft <- FT.build @_ @Int (VU.generate 4 id)
  (@?= Just 0) =<< FT.sumMaybe ft 0 0
  (@?= Just 3) =<< FT.sumMaybe ft 3 4
  (@?= Just 6) =<< FT.sumMaybe ft 0 4
  (@?= Nothing) =<< FT.sumMaybe ft (-1) 4
  (@?= Nothing) =<< FT.sumMaybe ft 0 5
  (@?= Nothing) =<< FT.sumMaybe ft 0 (-1)
  (@?= Nothing) =<< FT.sumMaybe ft (-1) (-1)
  (@?= Nothing) =<< FT.sumMaybe ft (-1) 0
  (@?= Nothing) =<< FT.sumMaybe ft 4 5
  (@?= Nothing) =<< FT.sumMaybe ft 5 5

spec_invalid :: IO TestTree
spec_invalid = testSpec "invalid" $ do
  it "throws error" $ do
    FT.new @_ @Int (-1) `shouldThrow` anyException
  s <- runIO $ FT.new @_ @Int 10

  it "throws error" $ do
    FT.add s (-1) 0 `shouldThrow` anyException
  it "throws error" $ do
    FT.add s 10 0 `shouldThrow` anyException

  it "throws error" $ do
    FT.sum s (-1) 3 `shouldThrow` anyException
  it "throws error" $ do
    FT.sum s 3 11 `shouldThrow` anyException
  it "throws error" $ do
    FT.sum s 5 3 `shouldThrow` anyException

prop_maxRight :: QC.NonNegative Int -> QC.NonEmptyList (QC.NonNegative Int) -> QC.Gen QC.Property
prop_maxRight (QC.NonNegative xRef) (QC.NonEmpty xs_) = do
  l0 <- QC.chooseInt (0, length xs_)
  let xs = VU.fromList $ map (\(QC.NonNegative x) -> x) xs_
      expected = (l0 +) . VU.length . VU.takeWhile (<= xRef) $ VU.scanl1' (+) $ VU.drop l0 xs
      res = runST $ do
        ft <- FT.build xs
        FT.maxRight ft l0 (<= xRef)
  pure $ expected QC.=== res

prop_minLeft :: QC.NonNegative Int -> QC.NonEmptyList (QC.NonNegative Int) -> QC.Gen QC.Property
prop_minLeft (QC.NonNegative xRef) (QC.NonEmpty xs_) = do
  r0 <- QC.chooseInt (0, length xs_)
  let xs = VU.fromList $ map (\(QC.NonNegative x) -> x) xs_
      expected = (r0 -) . VU.length . VU.takeWhile (<= xRef) $ VU.scanl1' (+) $ VU.reverse $ VU.take r0 xs
      res = runST $ do
        ft <- FT.build xs
        FT.minLeft ft r0 (<= xRef)
  pure $ expected QC.=== res

tests :: [TestTree]
tests =
  [ unit_zero {- overFlowInt -},
    unit_naive,
    unsafePerformIO spec_invalid,
    unit_sumMaybeBounds,
    QC.testProperty "maxRight" prop_maxRight,
    QC.testProperty "minLeft" prop_minLeft
  ]