生成具有给定概率的随机整数

时间:2012-01-09 08:27:01

标签: haskell random

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

在Haskell中有没有库函数?

3 个答案:

答案 0 :(得分:11)

正如人们已经指出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

答案 1 :(得分:5)

Control.Monad.RandomfromList :: 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显然比我想象的更懒,所以replicateM n可以被sequence . repeat替换,就像@shang建议的那样。

答案 2 :(得分:3)

为了扩展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 monad,但是你需要提供用于流的随机数生成器。