packages feed

ac-library-hs-1.1.0.0: test/Tests/Extra/Bisect.hs

module Tests.Extra.Bisect where

import AtCoder.Extra.Bisect
import Data.List qualified as L
import Data.Vector.Generic qualified as VG
import Data.Vector.Unboxed qualified as VU
import Test.Tasty
import Test.Tasty.QuickCheck as QC

-- | Takes half-open interval [l, r).
naivePartition :: Int -> Int -> (Int -> Bool) -> VU.Vector Int -> (Maybe Int, Maybe Int)
naivePartition l r p xs
  | l >= r = (Nothing, Nothing)
  | otherwise = case (VU.null ls, VU.null rs) of
      (True, True) -> error "unreachable"
      (False, True) -> (Just l', Nothing)
      (True, False) -> (Nothing, Just r')
      _ -> (Just l', Just r')
  where
    xs' = VU.take (r - l) . VU.drop l $ xs
    (!ls, !rs) = VU.partition p xs'
    l' = l + VU.length ls - 1
    r' = l' + 1

naiveLowerBound :: VU.Vector Int -> Int -> Maybe Int
naiveLowerBound xs = naiveLowerBoundIn 0 (VU.length xs) xs

naiveLowerBoundIn :: Int -> Int -> VU.Vector Int -> Int -> Maybe Int
naiveLowerBoundIn l r xs target = case naivePartition l r (< target) xs of
  (!_, Just i) -> Just i
  _ -> Nothing

naiveUpperBound :: VU.Vector Int -> Int -> Maybe Int
naiveUpperBound xs = naiveUpperBoundIn 0 (VU.length xs) xs

naiveUpperBoundIn :: Int -> Int -> VU.Vector Int -> Int -> Maybe Int
naiveUpperBoundIn l r xs target = case naivePartition l r (<= target) xs of
  (!_, Just i) -> Just i
  _ -> Nothing

boundsQueryGen :: Gen (Int, Int, VU.Vector Int)
boundsQueryGen = do
  n <- QC.chooseInt (1, 100)
  p <- QC.chooseInt (-25, 25)
  xs <- VU.fromList . L.sort <$> QC.vectorOf n (QC.chooseInt (-20, 20))
  pure (n, p, xs)

bisectQueryGen :: Gen (Int, Int, VU.Vector Int, [(Int, Int)])
bisectQueryGen = do
  n <- QC.chooseInt (1, 100)
  p <- QC.chooseInt (-25, 25)
  xs <- VU.fromList . L.sort <$> QC.vectorOf n (QC.chooseInt (-20, 20))
  let lrs = [(l, r) | l <- [0 .. n], r <- [l .. n]]
  pure (n, p, xs, lrs)

prop_lowerBound :: TestTree
prop_lowerBound = QC.testProperty "lowerBound" $ do
  (!_, !target, !xs) <- boundsQueryGen
  pure $ naiveLowerBound xs target QC.=== lowerBound xs target

prop_lowerBoundIn :: TestTree
prop_lowerBoundIn = QC.testProperty "lowerBoundIn" $ do
  (!_, !target, !xs, !lrs) <- bisectQueryGen
  pure . QC.conjoin $
    map
      ( \(!l, !r) ->
          naiveLowerBoundIn l r xs target == lowerBoundIn l r xs target
      )
      lrs

prop_upperBound :: TestTree
prop_upperBound = QC.testProperty "upperBound" $ do
  (!_, !target, !xs) <- boundsQueryGen
  pure $ naiveUpperBound xs target QC.=== upperBound xs target

prop_upperBoundIn :: TestTree
prop_upperBoundIn = QC.testProperty "upperBoundIn" $ do
  (!_, !target, !xs, !lrs) <- bisectQueryGen
  pure . QC.conjoin $
    map
      ( \(!l, !r) ->
          naiveUpperBoundIn l r xs target == upperBoundIn l r xs target
      )
      lrs

prop_bisectL :: TestTree
prop_bisectL = QC.testProperty "bisectL" $ do
  (!_, !boundary, !xs, !lrs) <- bisectQueryGen
  pure . QC.conjoin $
    map
      ( \(!l, !r) ->
          fst (naivePartition l r (<= boundary) xs) == bisectL l r (\i -> xs VG.! i <= boundary)
      )
      lrs

prop_bisectR :: TestTree
prop_bisectR = QC.testProperty "bisectR" $ do
  (!_, !boundary, !xs, !lrs) <- bisectQueryGen
  pure . QC.conjoin $
    map
      ( \(!l, !r) ->
          snd (naivePartition l r (<= boundary) xs) == bisectR l r (\i -> xs VG.! i <= boundary)
      )
      lrs

tests :: [TestTree]
tests =
  [ prop_lowerBound,
    prop_upperBound,
    prop_lowerBoundIn,
    prop_upperBoundIn,
    prop_bisectL,
    prop_bisectR
  ]