cpmonad-0.1.0.0: lib/Cpmonad/Gen.hs
module Cpmonad.Gen (
Gen,
GenST,
runGen,
runGenST,
liftg,
runIOGen,
genr,
genri,
Indexable (..),
choose,
genpair,
distribute,
shuffle,
genperm,
gendistinct,
state,
) where
import Control.Monad.ST
import Control.Monad.State.Strict (State, StateT, lift, runState, runStateT, state)
import Data.List
import Data.Set qualified as Set
import Data.Vector (Vector, (!))
import Data.Vector qualified as V
import Data.Vector.Algorithms.Merge qualified as VA
import Data.Vector.Mutable qualified as VM
import Data.Vector.Strict qualified as VS
import System.Random (StdGen, newStdGen, uniformR)
-- | Monad where usual generators live in
type Gen = State StdGen
-- | Monad when you want to do mutations for faster generation algorithms
type GenST s = StateT StdGen (ST s)
runGen :: Gen a -> StdGen -> (a, StdGen)
runGen = runState
-- | Convert 'GenST' to 'Gen'
runGenST :: (forall s. GenST s a) -> Gen a
runGenST g = state \s -> runST $ runStateT g s
liftg :: Gen a -> GenST s a
liftg = state . runState
-- | Seed the generator with 'newStdGen'
runIOGen :: Gen a -> IO a
runIOGen g = fst . runGen g <$> newStdGen
-- | Int between [a, b)
genr :: Int -> Int -> Gen Int
genr a b = state $ uniformR (a, b - 1)
-- | Int between [a, b]
genri :: Int -> Int -> Gen Int
genri a b = genr a (b - 1)
-- | Class of types that can be used with 'choose'
class Indexable c where
index :: c a -> Int -> a
size :: c a -> Int
instance Indexable [] where
index = (Data.List.!!)
size = Data.List.length
instance Indexable V.Vector where
index = (V.!)
size = V.length
instance Indexable VS.Vector where
index = (VS.!)
size = VS.length
-- | Choose an element of a collection
choose :: (Indexable c) => c a -> Gen a
choose xs = let n = size xs in index xs <$> genr 0 n
-- | Keep generating until predicate returns True. Stops after one million tries
genuntil :: (a -> Bool) -> Gen a -> Gen a
genuntil p g = go (1000_000 :: Int)
where
go 0 = error "genuntil: not found after a million tries"
go i = do
x <- g
if p x
then pure x
else go (i - 1)
{- | Generate a pair of integers satisfying the binary predicate f.
Useful as @genpair (<=)@
-}
genpair :: (Int -> Int -> Bool) -> Gen Int -> Gen (Int, Int)
genpair f g = genuntil (uncurry f) $ liftA2 (,) g g
{- | Given @s@, generate @n@ numbers such that their sum adds up to @s@
Can be used to generate the parameters of the testcases.
-}
distribute
:: Int
-- ^ total sum @s@
-> Int
-- ^ number of numbers @n@
-> Int
-- ^ minimum number to generate
-> Gen (Vector Int)
distribute _ n _ | n <= 0 = error "n must be positive"
distribute s n 0 = runGenST do
v <- VM.generateM (n + 1) \i ->
if
| i == 0 -> pure 0
| i == n -> pure s
| otherwise -> liftg (genr 0 s)
VA.sort v
delimeters <- V.unsafeFreeze v
pure $ V.zipWith (-) (V.drop 1 delimeters) delimeters
distribute s n low = V.map (+ low) <$> distribute (s - low * n) n 0
-- | Generate a shuffled version of the vector
shuffle :: Vector Int -> Gen (Vector Int)
shuffle v' = runGenST do
let n = V.length v'
let go v i
| i == n = V.unsafeFreeze v
| otherwise = do
j <- liftg $ genr i n
VM.swap v i j
go v (i + 1)
v <- V.thaw v'
go v 0
-- | Generate a permutation
genperm :: Int -> Gen (Vector Int)
genperm n = shuffle $ V.enumFromN 0 n
-- | Generate n distinct @a@'s
gendistinct :: (Ord a) => Int -> Gen a -> Gen (Vector a)
gendistinct n g = runGenST do
v <- VM.new n
let go i seen v
| i == n = V.unsafeFreeze v
| otherwise = do
x <- liftg $ genuntil (not . flip Set.member seen) g
VM.write v i x
go (i + 1) (Set.insert x seen) v
go 0 Set.empty v