erlang生成N个不重复的随机数_方案比较

生成N个不成重复的随机数

方案一:最简单,用tuple来组织,将一个有序的整数列表通过shuffle方法打乱即可,

比如生成100个无重复随机数, 用四进制8位表示,可以组织0~99,然后打乱即可。

{RandNo, 99}, {RandNo,98}, {RandNo,97},  ......, {RandNo, 3}, {RandNo, 2}, {RandNo,1}, {RandNo, 0},

其中RandNo代表某个随机数,不一定相等,这里简单提一下,没有用下标区分。

Seed: "0123", Width: 8, No: 100

代码如下:

random_numbers(Seed, Width, No) ->
    random_numbers(Seed, Width, No, 0).
random_numbers(Seed, Width, No, Offset) ->
    L = sub_random_nums(No, Offset),
    format(Seed, Width, shuffle(L)).
    
sub_random_nums(No, Offset) ->
    sub_random_numbers(No, No, Offset,[]).
sub_random_numbers(_Upper, 0, _Offset, L)  -> L;
sub_random_numbers(Upper, No, Offset, L)  ->
    Rand = get_random(Upper),
    KV = {Rand, No-1+Offset},
    sub_random_numbers(Upper, No-1, Offset, [KV|L]).

方案二:跟方案一雷同,用tuple来组织,将一个有序的整数通过shuffle方法打乱即可,

比如生成100个无重复随机数, 用四进制8位表示,可以在0~65535(65535=4^8-1)中

选择其中100个数字组成一个整数列表,然后打乱即可。但是为了避免生成整个列表,所以

需要设置一个步长,进行采样。最简单的采样方式是等步长采样。比如100里面取10个数字,

那么就从0~9取一个,10~19取第二个,...,90~99取第10个。

而本方案是采取动态步长,还是100里面取10个数字。那么第一次步长是10,就是从前10个里面取一个,

假如取到9,那么第二次取时,就是从剩余90个里面取9个,步长还是10,就从剩下的里面前10个取一个,

依此类推。假如第一次取到的不是9而是0,那么就从剩下的99个里面取剩余9个,第二次取时也先计算步长,

步长就是11,第二次就从剩余99个里面取一个,也就是从1~12里面取一个,其它的也依此类推。

这样就能保证取到10个相对随机的数字,然后经过shuffle,那么就能够生成N个不重复的伪随机数。

{RandNo, 0}, {RandNo,5}, {RandNo,8},  ......, {RandNo, 66}, {RandNo, 67}, {RandNo,76}, {RandNo, 89},

其中RandNo代表某个随机数,不一定相等,这里简单提一下,没有用下标区分。

Seed: "0123", Width: 8, No: 100

代码如下:

random_numbers(Seed, Width, No) ->
    random_numbers(Seed, Width, No, 0).
random_numbers(Seed, Width, No, Offset) ->
    Max = trunc(math:pow(length(Seed), Width))-1,
    L = sub_random_nums(No, Max, Offset),
    format(Seed, Width, shuffle(L)).
    
sub_random_nums(No, Max, Offset) ->
    sub_random_numbers(No, Max, Offset,[]).
sub_random_numbers(0, _, _, L)  -> L;
sub_random_numbers(No, Max, Offset, L) ->
    StepRange = get_step_range(No, Max+1),
    Step = get_random(StepRange),
    Rand = get_random(Max+Offset),
    KV = {Rand, Offset+Step},
    sub_random_numbers(No-1, Max-Step-1, Offset+Step+1, [KV|L]).
get_step_range(No, Max) ->
    trunc(Max/No).

注:从下往上取时需要动态平衡一下,代码Max-Step-1, Offset+Step+1比较难以理解,为了方便理解,就有了方案三。


方案三:综合了方案一和二,用tuple来组织,将一个有序的整数通过shuffle方法打乱即可,

而本方案还是采取动态步长,比如100里面取10个数字。那么第一次步长是10,那么就从1~10里面取一个,

假如取到10,那么第一次得到89(100-1-10),那么第二次取时,就是从剩余90个里面取9个,步长还是10,

那么再从1~10个取一个,假如取到2,那么第二次得到87(90-1-2),依此类推。假如第一次取到的不是10而是1,

那么第一次取到的是98(100-1-1),接下来就从剩下的99个里面取剩余9个,第二次取时也先计算步长,

步长就是11,第二次就从1~11里面取一个,假如取到3,那么第二次得到95(99-1-3),其它的也依此类推。

这样就能保证取到10个相对随机的数字,然后经过shuffle,那么就能够生成N个不重复的伪随机数。

{RandNo, 0}, {RandNo,5}, {RandNo,8},  ......, {RandNo, 66}, {RandNo, 67}, {RandNo,76}, {RandNo, 89},

其中RandNo代表某个随机数,不一定相等,这里简单提一下,没有用下标区分。

Seed: "0123", Width: 8, No: 100

代码如下:

random_numbers(Seed, Width, No) ->
    random_numbers(Seed, Width, No, 0).
random_numbers(Seed, Width, No, Offset) ->
    Max = trunc(math:pow(length(Seed), Width)),
    L = sub_random_nums(No, Max+Offset, Max),
    format(Seed, Width, shuffle(L)).
    
sub_random_nums(No, Max, MaxRange) ->
    sub_random_numbers(No, Max, MaxRange,[]).
sub_random_numbers(0, _, _, L)  -> L;
sub_random_numbers(No, Max, MaxRange, L) ->
    StepRange = get_step_range(No, MaxRange),
    Step = get_random(StepRange)+1,
    Rand = get_random(),
    KV = {Rand, Max-Step},
    sub_random_numbers(No-1, Max-Step, MaxRange-Step, [KV|L]).
get_step_range(No, MaxRange) ->
    trunc(MaxRange/No).

shuffle:

shuffle(L) when is_list(L) ->
    {_, NL} = lists:unzip(lists:keysort(1, L)),
    NL.

方案比较:

方案一实质上是等步长采样,步长为1,生成N个数字就将0~N-1数字打乱即可,简单方便,但是对于特殊场合不适用。

方案二动态步长,如果输入数据特定,也能达到等步长的效果,但是正常情况下,都是动态步长,除非需要生成的数据

跟最大数据量相等或极其相近。但是在代码理解上有丁点难度。

方案三具备方案二的优点,且代码理解更加简单。固暂时选用方案三。

以上生成随机数的方案均是分布式生成随机数任务中的一个相对核心的算法。至于随机数生成部分get_random方法,这边

不做详细介绍,采用的是python/erlang等最常用的最便捷的随机数生成算法,只是种子是获取本地系统一些相对随机的数据,

比如pid等。

这里只是做个记录,也分享给其他需要的朋友或者同仁。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值