你不能有一个纯函数每次都返回一个不同的数字。它的输出取决于它以前的调用,而不仅仅是它的参数。但是,您可以创建一个monad,它携带目前为止生成的一组数字,然后重试生成一个随机数,直到找到一个尚未生成的数为止。对于以下示例,我使用了MonadRandom包。
import Control.Monad
import Control.Monad.Random
import Control.Monad.State
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import System.Random (getStdGen)
type RandDistinct g a = StateT IntSet (Rand g) a
evalDistinct :: RandomGen g => RandDistinct g a -> g -> a
evalDistinct k = evalRand (evalStateT k IS.empty)
上述类型使用StateT
以增强随机数发生器记住集生成的数字为止。当我们想评估这个monad中的一个计算时,我们从一个空集合开始,并用evalRand
来评估内部计算。
现在我们可以写,每次返回不同的数的函数:
nextDistinct :: RandomGen g => (Int,Int) -> RandDistinct g Int
nextDistinct range = loop
where
-- Loop until we find a number not in the set
loop = do
set <- get
r <- getRandomR range
if IS.member r set
then loop -- repeat
else put (IS.insert r set) >> return r
和测试它是如何工作的:
main = getStdGen >>= print . evalDistinct (replicateM 50 $ nextDistinct (10, 99))
注意nextDistinct
使用一个简单的策略 - 重试生成新数字,如果它已经存在于集合中。只要碰撞次数很少,这就可以正常工作。
“因此,我可以期望函数每次都返回不同的数字” - 野生假设。如果您只是生成随机数字,则有重复的机会。事实上,这个概率比你的直觉所说的要糟糕。检查生日悖论。 –
@KarolyHorvath谢谢。修正了语言。 – Dilawar
Karoly,虽然你说的是真的,但我不喜欢使用“悖论”这个词,因为它不是不合逻辑的或类似的东西:P就像说“蒙蒂霍尔悖论”,我更喜欢“蒙蒂霍尔问题”。 – Wes