packages feed

ac-library-hs-1.3.0.0: test/Tests/Extra/Math.hs

module Tests.Extra.Math (tests) where

import AtCoder.Extra.Math qualified as ACEM
import Data.List qualified as L
import Data.Proxy (Proxy (..))
import Data.Semigroup (Max (..), Min (..), Sum (..), mtimesDefault, stimes)
import Data.Vector.Unboxed qualified as VU
import Test.QuickCheck.Property qualified as QC
import Test.Tasty
import Test.Tasty.QuickCheck qualified as QC
import Tests.Util (myForAllShrink)

-- orphan instance
instance QC.Arbitrary (Max Int) where
  arbitrary = Max <$> QC.arbitrary

-- orphan instance
instance QC.Arbitrary (Min Int) where
  arbitrary = Min <$> QC.arbitrary

prop_stimes' :: forall a. (Semigroup a, Show a, Eq a, QC.Arbitrary a) => Proxy a -> QC.Property
prop_stimes' _ = myForAllShrink True (const True) desc lhsS lhs rhsS rhs
  where
    desc :: (QC.Positive Int, a) -> [String]
    desc (QC.Positive !n, !s) = ["n = " ++ show n ++ ", s = " ++ show s]
    lhsS = "stimes' n s"
    lhs (QC.Positive !n, !s) = ACEM.stimes' n s
    rhsS = "stimes n s"
    rhs (QC.Positive !n, !s) = stimes n s

prop_mtimes' :: forall a. (Monoid a, Show a, Eq a, QC.Arbitrary a) => Proxy a -> QC.Property
prop_mtimes' _ = myForAllShrink True (const True) desc lhsS lhs rhsS rhs
  where
    desc :: (QC.NonNegative Int, a) -> [String]
    desc (QC.NonNegative !n, !m) = ["n = " ++ show n ++ ", m = " ++ show m]
    lhsS = "mtimes' n s"
    lhs (QC.NonNegative !n, !m) = ACEM.mtimes' n m
    rhsS = "mtimes n s"
    rhs (QC.NonNegative !n, !m) = mtimesDefault n m

-- | This is a solid, fast implementation of prime number enumeration for lists.
truePrimes :: [Int]
truePrimes = 2 : 3 : minus [5, 7 ..] (unionAll [[p * p, p * p + 2 * p ..] | p <- tail truePrimes])
  where
    minus (x : xs) (y : ys) = case compare x y of
      LT -> x : minus xs (y : ys)
      EQ -> minus xs ys
      GT -> minus (x : xs) ys
    minus xs _ = xs

    union (x : xs) (y : ys) = case compare x y of
      LT -> x : union xs (y : ys)
      EQ -> x : union xs ys
      GT -> y : union (x : xs) ys
    union xs [] = xs
    union [] ys = ys

    unionAll :: (Ord a) => [[a]] -> [a]
    unionAll ((x : xs) : t) = x : union xs (unionAll $ pairs t)
      where
        pairs ((x : xs) : ys : t) = (x : union xs ys) : pairs t
        pairs _ = error "unionAll _ pairs: unreachable"
    unionAll _ = error "unionAll: unreachable"

-- | This is a solid, fast implementation of prime number enumeration for lists.
truePrimeFactors :: Int -> [(Int, Int)]
truePrimeFactors !n_ = map (\ !xs -> (head xs, length xs)) . L.group $ inner n_ input
  where
    -- TODO: reuse `primes`?
    input = 2 : 3 : [y | x <- [5, 11 ..], y <- [x, x + 2]]
    inner n pps@(p : ps)
      | n == 1 = []
      | n < p * p = [n]
      | r == 0 = p : inner q pps
      | otherwise = inner n ps
      where
        (q, r) = divMod n p
    inner _ _ = error "unreachable"

trueDivisors :: Int -> [Int]
trueDivisors n = L.sort $ inner 1
  where
    inner k
      -- no dependency to `Int` square root function
      | k * k > n = []
      -- no divisor duplication
      | k * k == n = [k]
      -- not sorted yet
      | r == 0 = k : d : inner (succ k)
      -- ignore non divisors
      | otherwise = inner (succ k)
      where
        -- This strict evaluation and unboxing takes some effect, even though they're not always
        -- used.
        (!d, !r) = n `divMod` k

-- unit_primes :: TestTree
-- unit_primes = testCase "primes" $ do
--   for_ [0 .. 10 ^ 9] $ \upper -> do
--     when (upper `mod` 10000 == 0) $ do
--       let !_ = traceShow upper ()
--       pure ()
--     let expected = VU.fromList $ takeWhile (<= upper) truePrimes
--     let result = ACEM.primes upper
--     result @?= expected

prop_primeFactors :: QC.Positive Int -> QC.Property
prop_primeFactors (QC.Positive x) =
  let expected = VU.fromList $ truePrimeFactors x
      result = ACEM.primeFactors x
   in result QC.=== expected

prop_divisors :: QC.Positive Int -> QC.Property
prop_divisors (QC.Positive x) =
  let expected = VU.fromList $ trueDivisors x
      result = ACEM.divisors x
   in result QC.=== expected

tests :: [TestTree]
tests =
  [ testGroup
      "stimes'"
      [ QC.testProperty "Sum" (prop_stimes' (Proxy @(Sum Int))),
        QC.testProperty "Product" (prop_stimes' (Proxy @(Sum Int))),
        QC.testProperty "Max" (prop_stimes' (Proxy @(Max Int))),
        QC.testProperty "Min" (prop_stimes' (Proxy @(Min Int)))
      ],
    testGroup
      "mtimes'"
      [ QC.testProperty "Sum" (prop_mtimes' (Proxy @(Sum Int))),
        QC.testProperty "Product" (prop_mtimes' (Proxy @(Sum Int))),
        QC.testProperty "Max" (prop_mtimes' (Proxy @(Max Int))),
        QC.testProperty "Min" (prop_mtimes' (Proxy @(Min Int)))
      ],
    testGroup
      "primes"
      [ -- unit_primes
        QC.testProperty "primeFactors" prop_primeFactors,
        QC.testProperty "divisors" prop_divisors
      ]
  ]

-- unit_primes