实现求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 from0
tosum
(inclusive) - Insert
0
andsum
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
and100
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)
----==== Email: slofslb (GTD) qq.com 请将(GTD)换成@ ====----
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
作者:申龙斌的程序人生
---- 魔方、桥牌、象棋、游戏人生...
---- BASIC、C++、JAVA、C#、Haskell、Objective-C、Open Inventor、程序人生...
---- GTD伴我实现人生目标
---- 区块链生存训练
---- 用欧拉计划学Rust编程
---- 申龙斌的读书笔记(2011-2019)
----

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构