实现求n个随机数和为sum的haskell程序

近日看到这篇博客http://www.cnblogs.com/songsz1/archive/2012/10/31/2748098.html,写一个程序:生成26个非负随机数,要求其和是301。最近对Haskell很感兴趣,就试着写了一下,不动手则已,真写起来还是遇到点问题。在函数式编程里每个确定的输入一定会有确定的输出,而随机数就意味着相同的程序每次运行就输出不同的结果。

一开始完成的代码如下,不算import,实际就3行代码:

import System.Random
-- haskell的算法大量采用递归,如果只有一个数,要求其和为sum,那么这个列表当然就是[sum]了
f 1 sum =[sum]
 
-- 对于n个数时,就是随机选一个范围在0到sum之前的数m,然后再与(n-1)个随机数(要求其和为sum-m)合在一起即可
f n sum = m : (f (n-1) (sum-m))    
    where m = randomRIO (0,sum)

但编译出现问题,因为代码用到了IO,纯函数和带IO的函数放在一起有问题,想了许多办法也解决不了,无奈想到了stackoverflow,注册账号后把这个问题发到stackoverflow网站上,没想到高手真多,不出几个小时问题就搞定了,正确代码如下:

f 1 sum = return [sum]
f n sum = do
  x  <- randomRIO (0, sum)
  xs <- f (n -1) (sum - x)
  return (x : xs)

几个运行结果

[215,33,6,12,7,1,4,4,15,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

[284,1,1,13,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

[297,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

 

更有高手finnw发现这种算法会出现大串的0,想出了下面的改进算法(原来与博客园上的算法一是一样的):

假设要得到4个随机数,和为100

1)取3个随机数,例如[72,33,43]

2)前后分别插入0,100,排序,得到 [0,33,43,72,100]

3)计算相邻2个数的差值 [33-0, 43-33, 72-43, 100-72]

结果就出来了 [33,10,29,28],分布比原来的算法好多了。

[0,17,1,19,35,1,4,11,29,2,12,1,6,6,47,1,18,25,1,11,1,2,2,20,2,27]

[0,1,14,11,7,16,4,5,40,14,4,2,14,16,31,2,3,5,1,18,26,17,1,28,0,21]

[15,2,1,28,2,15,6,18,0,19,12,3,7,17,33,23,41,4,2,2,10,0,2,3,32,4]

 

英文原文如下:

As others have pointed out, your algorithm will not give uniformly-distributed output.

An easy way to get uniform output is:

  • Generate n-1 random numbers in the range from 0 to sum (inclusive)
  • Insert 0 and sum into the list of random numbers
  • Sort the resulting list
  • Return the list of differences between consecutive values in the sorted list

Example:

  • Suppose we want four integers with a sum of 100, we request three random values from the RNG and it gives us [72,33,43]
  • We insert 0 and 100 and sort the list, giving [0,33,43,72,100]
  • We compute the differences [33-0, 43-33, 72-43, 100-72]
  • The result would be [33,10,29,28]

In Haskell:

import System.Random
import Data.List

randomsWithSum :: (Num n, Ord n, Random n) => Int -> n -> IO [n]
randomsWithSum len sum =
    do b <- sequence $ take (len-1) $ repeat $ randomRIO (0,sum)
       let sb = sort (sum:b) in
           return $ zipWith (-) sb (0:sb)

For your example you would call this as randomsWithSum 26 (301::Int)

posted @ 2012-11-04 22:29  申龙斌的程序人生  阅读(1579)  评论(0编辑  收藏  举报