使用给定的概率生成随机整数

8

我需要生成一个无限的随机整数流,数字范围在[1..n]之间。然而,每个数字p_i的概率提前给出,因此分布不是均匀的。

在Haskell中是否有库函数可以实现这一功能?

4个回答

12

正如其他人指出的那样,Control.Monad.Random中有一个函数,但它的复杂度相当差。这里是一些代码,今天早上我巧合地写了它。它使用了美妙的Alias算法。

module Data.Random.Distribution.NonUniform(randomsDist) where
import Data.Array
import Data.List
import System.Random

genTable :: (Num a, Ord a) => [a] -> (Array Int a, Array Int Int)
genTable ps =
    let n = length ps
        n' = fromIntegral n
        (small, large) = partition ((< 1) . snd) $ zip [0..] $ map (n' *) ps
        loop ((l, pl):ls) ((g, pg):gs) probs aliases =
            let prob = (l,pl)
                alias = (l,g)
                pg' = (pg + pl) - 1
                gpg = (g, pg')
            in  if pg' < 1 then loop (gpg:ls) gs (prob:probs) (alias:aliases)
                          else loop ls (gpg:gs) (prob:probs) (alias:aliases)
        loop ls gs probs aliases = loop' (ls ++ gs) probs aliases
        loop' [] probs aliases = (array (0,n-1) probs, array (0,n-1) aliases)
        loop' ((g,_):gs) probs aliases = loop' gs ((g,1):probs) ((g, -1):aliases)
    in  loop small large [] []

-- | Generate an infinite list of random values with the given distribution.
-- The probabilities are scaled so they do not have to add up to one.
-- 
-- Uses Vose's alias method for generating the values.
-- For /n/ values this has O(/n/) setup complexity and O(1) complexity for each
-- generated item.
randomsDist :: (RandomGen g, Random r, Fractional r, Ord r)
            => g                           -- | random number generator
            -> [(a, r)]                    -- | list of values with the probabilities
            -> [a]
randomsDist g xps =
    let (xs, ps) = unzip xps
        n = length xps
        axs = listArray (0, n-1) xs
        s = sum ps
        (probs, aliases) = genTable $ map (/ s) ps
        (g', g'') = split g
        is = randomRs (0, n-1) g'
        rs = randoms g''
        ks = zipWith (\ i r -> if r <= probs!i then i else aliases!i) is rs
    in  map (axs!) ks

我刚刚尝试了一下这个 "Total time 55.59s",并将其与这里的实现进行了比较:http://idontgetoutmuch.wordpress.com/2014/08/26/haskell-vectors-and-sampling-from-a-categorical-distribution/,该实现为 "Total time 11.09s"。在两种情况下,都对2*10^7个样本进行了抽样。也许这不是一个公平的比较,因为其中一个使用了System.Random,而另一个使用了System.Random.MWC。 - idontgetoutmuch
是的,我会假设在我的代码中生成随机数会占主导地位。它还需要专业化,这可能会通过 -O2 自动完成。 - augustss
使用不同的随机数生成器,我得到了“总时间20.31秒”的更好结果,但仍然不够好。我还没有尝试过专业化。此外,内存使用情况也不好。我希望每个表中的每个条目需要4 + 8字节,因此应该是2 * 12 * 10 ^ 7字节,因此小于1G。但我看到的是约5G。虽然我可能很天真。而且我还没有完成阅读Devroye和Vose的书。谁能想到你可以用随机数玩得这么开心。 - idontgetoutmuch

5
< p > Control.Monad.Random 提供了函数fromList:: MonadRandom m => [(a, Rational)] -> m a,可以用来生成由给定列表中的元素随机生成一个单一的值。

IO Monad中使用它的方法如下:

import Control.Monad.Random
-- ...
someNums <- evalRandIO . sequence . repeat . fromList $ [(1, 0.3), (2, 0.2), (3, 0.5)]
print $ take 200 someNums

在该软件包中,您可以看到运行Rand Monad的其他方式。权重不必相加为1。

编辑: 显然Rand比我想象的更懒惰,因此可以用sequence . repeat替换replicateM n,如@shang所建议。


5

扩展dflemstr的答案,您可以使用Control.Monad.Random创建一个加权值的无限列表,如下所示:

import Control.Monad.Random
import System.Random

weightedList :: RandomGen g => g -> [(a, Rational)] -> [a]
weightedList gen weights = evalRand m gen
    where m = sequence . repeat . fromList $ weights

并像这样使用它:

> let values = weightedList (mkStdGen 123) [(1, 2), (2, 5), (3, 10)]
> take 20 values
[2,1,3,2,1,2,2,3,3,3,3,3,3,2,3,3,2,2,2,3]

这不需要使用IO单子,但是您需要提供用于流的随机数生成器。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接