赵 康
(商丘职业技术学院,河南 商丘 476100)
随着科技的进步,随机数的运用已经渗透社会的各行各业.学术界对随机数的讨论以及对随机参数的选取也是众说纷纭.随机数最重要的特性是前后产生的两个数没有逻辑关系,即真正的随机.但我们在日常工作和生活中,使用计算机接触的随机数只是某些工具根据一些参数在特定的计算方式下生成的具有随机数某些特征的伪随机数.VFP是一种集成了面向对象程序设计工具的小型数据库管理系统[1],由于其结构简单、功能强大、性能稳定等特点,因此,VFP在社会各行各业都有着广泛的应用基础[2].本文就VFP所提供的随机函数RAND()在生成随机数时出现的生成相同序列随机数、生成随机数重复率等问题进行探讨.
随机数产生于大量统计对象中选取的特殊样本,这些样本之间没有固定的关联关系.随机数一般分为三类.
1)伪随机数:它的判定基于统计学中所选定的特殊样本各分类的占比大致相等.
2)密码学安全的伪随机数:在满足第一类伪随机数的基础上,抽取部分样本不能有效地演算出随机样本的剩余部分的信息.
3)真随机数:在满足第一类条件和第二类条件的基础上,还必须满足随机样本不可重复的条件.
生成随机数的方法称为随机数发生器,随机数发生器分为两类[3].
1)物理性随机数发生器:它以噪声、温度等物理信息获取随机数.
2)伪随机数发生器:通过固定重复的计算得到具有随机数统计特征的伪随机数.
VFP为用户提供了300多种函数[4],用户在使用这些函数处理实际问题时,一般都需要多个函数配合使用才能解决问题.
随机函数RAND()的基本格式为:RAND([<随机参数>]),调用函数返回一个0-1的数值型随机数[5].在实际使用过程中,RAND()函数可以配合其他函数叠加使用,从而满足用户的需求.例如:生成0-999之间的随机整数,只需要将RAND()的返回值乘上1000,然后再使用取整函数INT()对数据结果处理即可.即:INT(RAND()*1000);若要生成某个指定范围内的随机整数,可做如下变形:INT((n2-n1+1)*RAND()+ n1),其中,n2是随机数范围的上限,n1是随机数范围的下限.INT((999-100+1)*RAND()+100)就是生成三位整数的随机数.
在VFP中,函数的调用需要有输出值和返回值,这相当于简单的函数式:y=f(x),当x值确定后根据对应法则f,就会有确定的y值与之对应.但随机函数RAND()可以通过循环语句反复被调用来生成一个随机数列.我们以每组输出10个随机数的5组数据为例.因为,本例的设计是为了论证在随机参数缺省时,无论代码被运行几次返回的数值都将会是同一个随机数列,因此,省略了外层循环.运行程序生成的随机数结果,如表1所示.
具体代码如下:
CLEAR
SET TALK OFF
FOR i=1 TO 10
REPLACE 测试1 WITH RAND()
SKIP
ENDFOR
SET TALK ON
RETURN
由表1可知,5次程序运行的结果得到的5组随机数数列是相同的,这和我们的预计结果是一致的.因为,当调用随机函数所使用的随机参数为省略,参数值将取默认缺省值100001.虽然随机参数缺省随机函数会返回序列中的下一个随机数,但是,这段代码中随机参数的初值是固定的,因此,无论代码被执行多少次总会返回相同的数列,这样所产生的数列不能满足我们的实际需求.为此,我们可以考虑,在进入输出随机数列循环体之前调用一次参数值为负数的随机函数RAND(-1),其目的是将系统时钟的当前秒数用作随机数的随机参数值,如此,运行程序的多次调用是有时间差的,反映到实验结果上的随机数列也必然不同.具体结果如表2所示.
表1 运行程序生成的随机数列结果
具体代码修改如下:
CLEAR
SET TALK OFF
RAND(-1)
FOR i = 1 TO 10
REPLACE 测试1 WITH RAND()
SKIP
ENDFOR
SET TALK ON
RETURN
由表2可知,经过首先调用含有负参数的随机函数RAND(-1)再使用RAND()生成的序列就是比较令人满意的随机数列了.
表2 运行修正后程序的随机数列结果
根据随机数发生器的工作原理可知,在确定随机参数初值后,每一次随机函数的调用都会将上一个生成的随机数作为下一次调用的随机参数值.由于发生器在设计时对参数值精度的选定,因此,其结果必然会出现参数值相同的现象.这反映到随机数列上就是在随机数列中的重复值问题.为了探讨随机数在使用过程中的重复值现象,我们以追加每组50个数的10组3位随机整数为例.
具体代码如下:
CLEAR
SET TALK OFF
USE "测试"
RAND(-1)
FOR i=1 TO 50
REPLACE cs1 WITH INT((999-100+1)*RAND()+100)
SKIP
ENDFOR
SET TALK ON
RETURN
如表3所示,统计每50个数的3位随机数的重复率达6%.目前,随机数产生原理一般是线性同余法或平方取中法,线性同余法产生的随机数会出现循环情况,但由于它的模为231,通过平方取中法产生的随机数会逐渐趋向于0.因此,随机值高重复率的根源在于实际问题本身.因为,我们要求是生成3位随机整数,因此必须对随机函数进行叠加才能实现“INT((999-100+1)*RAND()+100)”,但明显这样做就舍弃了小数点后的很多位,其解决重复数据的办法是通过扩大位数的手段降低重复率.在数据量较小时,对于个别重复数据的手动微调可以降低重复率.但在实际工作中,如果数据量较大,那么对数据的查重和对数据的修改就很容易出错,对随机数的使用反而不如顺序数来得直接高效.
表3 3位随机数列出现重复值的统计结果
在某些工作中,我们会遇到为某些记录追加固定长度的序号,并且出于保密角度的考虑,序号又不能使用连贯序号这一问题.下面笔者以给20 677条数据追加一个6位不重复的随机数字段为例(由于一般情况下数据表的字段多为字符型,这里需要使用数值型转字符型函数STR()和删除字符串的首部和尾部连续空格函数ALLTRIM()).
具体代码如下:
CLEAR
SET TALK OFF
USE "表名"
RAND(-1)
GO TOP
DO WHILE NOT EOF()
REPLACE ALL zp WITH ALLTRIM(STR(INT((999999-100000+1)*RAND()+100000)))
SKIP
ENDDO
SET TALK ON
RETURN
如表4所示,以20 677条记录为基础的6位的随机数重复率在2%.这个结果相对于上述问题的每50个3位随机数重复率达6%的重复率而言降低了不少,但手工调整重复值的方法依然是不可行的.根据实际工作需求,为了避免随机数的重复率问题,笔者考虑为每一个随机函数添加一个不同的参数来控制随机数的重复率.由于每一张数据表中每条记录的记录号是唯一的,笔者将每条记录的记录号设为随机函数参数“RAND(RECNO())”,经多次追加同一序列的随机数进行测试发现(每条记录的随机数是同一个值),没有重复值的6位随机数.
表4 6位随机数列出现重复值的统计结果
在一些娱乐节目中,我们经常会看到抽取幸运数字或幸运身份证号码等环节.以21 815条记录的数据库为例,利用随机函数RAND()返回值是0-1的特点,在数据库内随机抽取记录值并返回对应记录的相关信息,结果如图1所示.
图1 随机抽取5人信息运行结果
在表单的Init事件中添加以下代码:
IF ! USED("随机排序表")
USE 随机排序表
ENDIF
RAND(-1)
在timer1控件的Timer事件中添加以下代码:
FOR i=1 TO 5
texti="text"+ALLTRIM(STR(i))
GO INT(RAND()*21815)
thisform.&texti..Value=IIF(LEN(ALLTRIM(xm))==4,ALLTRIM(xm)+" ",ALLTRIM(xm)+" ")
+LEFT(ALLTRIM(sfzh),6)+"********"+RIGHT(ALLTRIM(sfzh),4)+" "+ALLTRIM(zymc)
ENDFOR
在command1控件的Click事件中添加以下代码:
thisform.timer1.Interval=270
在command2控件的Click事件中添加以下代码:
thisform.timer1.Interval=0
上述代码主要是针对在已有数据的基础上实现范围内信息随机抽取的功能实现.由于这里输出的信息是在一个文本框中进行的输出,考虑到输出的美观和信息的隐私性,笔者做了如下处理.因为姓名的字数个数不同,所以需要通过追加空格以保证输出信息的整齐“IIF(LEN(ALLTR IM(xm))==4,ALLTRIM(xm)+" ",ALLTRIM(xm)+" ")”(这里只以2个汉字或3个汉字进行处理,对于超过3个汉字的名字不做说明).对于身份证号等具有较强隐私性的信息需要对关键部分进行处理,这里笔者通过函数“LEFT(ALLTRIM(sfzh),6)+"********"+RIGHT(ALLTRIM(sfzh),4)”对身份证号码中的出生年月日进行了替换显示.这类问题在工作和生活中应用度很高,对于类似的问题只需要在本例的基础上稍加调整即可实现.
在工作和生活中,我们经常会碰到一些具体的人或物排序的问题.笔者以1-10这10个数字为例,对这10个具体数字实现随机排序.
创建一个临时表,具体代码如下:
RAND(-1)
SELECT 0
CREATE CURSOR testt(x int, y N(6,4))
FOR ii = 1 TO 10
APPEND BLANK
ENDFOR
这段代码首先为临时表的两个字段赋值,分别是记录号和随机数.临时表的10条记录对应1-10这10个数字,按照随机数的大小进行排序并重写临时表,将重写后的临时表中的记录号字段赋给一个变量并输出这个变量.反复执行下述代码,实现10个数字的随机排序.
REPLACE ALL x WITH RECNO(),y WITH RAND()
SELECT * FROM testt ORDER BY y INTO CURSOR testt READWRITE
testi=''
SCAN ALL
testi=testi+','+TRANSFORM(x)
ENDSCAN
?SUBSTR(testi,2)
运行结果如图2所示.
图2 1-10随机数排序运行结果
随机数研究与应用可以模拟、解决各活动领域中的一些实际问题.根据随机数的特性和影响随机数发生的随机函数参数的选取来控制和影响随机数的发生.从本质上看,在计算机系统中,无论任何计算机语言提供的随机函数所生成的随机数,都是通过一个参数在对应算法的基础上产生的伪随机数,这些由随机函数产生的伪随机数,基本符合随机数的特性而且实用价值很高.本文探讨了追加大量随机数时,数值重复问题的解决办法、随机抽取数据库中相关信息进行显示的问题和将10个数字随机排序这3个工作中经常出现的随机数应用问题,并从实际问题出发阐明其具体的解决思路.