用 Python3 做一个可’作弊’随机数生成器,并对结果进行图表分析


前一段时间,因为班上都比较懒的原因,很多活动,都因为同学抵不住游戏的诱惑,而出现没有人参加的尴尬场面。而每个班又有必须要去人。这时候,就出现需求,要给班上的同学进行按照学号的抽取。于是,就应运而生了,**第一代的随机数生成器**。
import random
import os
Start = int(input("请输入最小值:"))   #
End = int(input("请输入最大值:"))
Num = int(input("请输入生成数值的个数:"))
count = 1
print("随机生成数值:\n")
while count <= Num:
	RandNum = random.randint(Start, End)
      # 用random.randint()模块,生成一个Start到End的一个随机数
	print("    第",count, "个随机数值:", RandNum)
	count = count + 1
print("\n")
os.system("pause")

这个就是第一代的随机数生成器,但后来发现,这个有个很 **重大的缺陷**
就是,数字是可以不断重复的,比如我一个室友在抽取一周晚自习卫生的安排中,5次中枪3次。
在一顿暴打后,我决定要改进。

最开始,我就打算用一个 ==dict字典== 去存储这些信息,==key== 为数字,==value== 为该数字出现的次数。 每一次生成一个随机数,都去==dict字典==中,查询是该数字是否超过了重复的上限。
于是,==第二代的随机数生成器==,出现了
while count <= Num:			# 检测是否超过了所需要的最大数 数量的个数
    RandNum = random.randint(minLim, maxLim)			# 生成一个随机整数(数的极限为minList和maxList)
    if RandNum in reNumberDict.keys():			# 如果随机数在重复字典的键中存在
        if reNumberDict[RandNum] == reLimit:       # 如果该键出现次数等于reLimit(重复最大次数),则放弃该值
            continue
        else:
            reNumberDict[RandNum] = reNumberDict[RandNum] + 1     # 如果以上条件都满足,则给该键的值加1,表示出现次数加1
    else:					# 如果该随机数没有在重复字典的键中出现过,则添加该键,并给其值为1,表示该键出现一次
        reNumberDict[RandNum] = 1
    randomNumberlist.append(RandNum)  # 将一个随机数加入到randomNumberList(最后随机数结果的存储list)
    count = count + 1				# count计数器加1

然后,这个版本,解决了,重复的问题。
在一个夜深人静的晚上,我室友突然爬到我的床边,对我说

“你的这个小程序,可以做个小功能嘛?就是可以让一些数字不出现。这样,就抽不到我了。”

伴随着室友, 满脸奸笑的脸,躺在床上的我,突然有了一个大胆的想法。
想偷懒不搞卫生的念头愈来愈大,于是,我飞速爬起床。去完成这个功能。

我的想法是,用去封装一系列的功能:

  • 用一个方法,将我要作弊的号码储存在一个json文件中
  • 用一个方法,读取json文件中的号码,并且储存在一个变量list中
  • 用一个方法,生成随机数,并且每一次生成一个随机数,就去这个数字判断是否存在于那个要作弊的变量中,如果存在,就跳过,不做处理,跳过本次循环。

然后,在一段时间后,就出现了第三代随机数生成器

class randomNumber:
    """
    在设定的范围内,随机生成设定个数的随机数,并且可以设置每个数的重复次数
    设有backDoor机制
    """
    def __init__(self, minLim, maxLim, Num, reLimit=0, backDoor=False, backDoorJsonPath=""):
        self.minLim = minLim			# 随机数的起始值(包含)
        self.maxLim = maxLim			# 随机数的最大值(包含)
        self.Num = Num					# 生成随机数的个数
        self.reLimit = reLimit			# 每个数的生成次数上限,默认值为 0,表示不限次数
        self.backDoor = backDoor	    # backdoor开关

        self.backDoorList = []			# backDoor列表
        self.randomNumberlist = []		# 生成的随机数的list
        self.reNumberDict = {}			# 随机数的重复次数dict
        self.randomStatus = True       # random()运行标识符
        self.backDoorJsonPath = backDoorJsonPath        # backDoorJson的path

        if backDoor == True:			# 如果backdoor是打开的
            self.backDoor = backDoor		# 则设置类内backDoor开关为打开
            if os.path.exists(backDoorJsonPath):		# 检查是否存在backDoorJsonPath文件
                self.backDoorJsonPath = backDoorJsonPath		# 如果存在则设置类全局backDoorJsonPath
            else:										# 如果不存在,则设置backDoor开关为关闭
                self.backDoor = False
        else:
            pass
        if (((maxLim - minLim) - self.backDoorJsonReader()) + 1) * reLimit < Num:
            self.randomStatus = False       # 改变random()运行运行标识符为False
            print("Init_Error: 初始化错误,请保证 最大可能数字出现总次数 大于 您所需要随机数个数!!!!")


    def backDoorJsonReader(self):
        """
        读取backDoorJson的数据,并赋值给self.backDoorList
        :return:返回self.backDoorList的长度
        """
        if self.backDoor:				# 如果backDoor开关为打开的,则将json文件内容(list)读取到self.backDoorList
            jsonManager = JsonM(self.backDoorJsonPath)
            self.backDoorList = jsonManager.readerContent
            return len(self.backDoorList)          # 返回self.backDoorList的长度
        else:
            return 0

    def backDoorJsonWriter(self, inBackDoorList):
        """
        写入backDoorJson的数据
        :param inBackDoorList: 需要写入的backDoor数据
        :return:无返回值
        """
        if os.path.exists(self.backDoorJsonPath):            # 如果存在backDoorJsonPath文件
            jsonManager = JsonM(self.backDoorJsonPath)          # 实例化JsonM,初始化里会读取backDoorJsonPath文件的json
            jsonManager.jsonContent = jsonManager.readerContent + inBackDoorList
                    # 将json文件的list和inBackDoorList拼接,并赋值给jsonContent
            jsonManager.jsonContent = list(set(jsonManager.jsonContent))           # 清除列表中的重复元素
            jsonManager.writer()    # 将jsonContent写入文件
        else:
            jsonManager = JsonM(self.backDoorJsonPath, inBackDoorList)

    def randomCount(self):
        """
        生成随机数的次数统计,并写入到reNumberDict.json
        :return:无返回值
        """
        if os.path.exists("reNumberDict.json"):            # 如果存在backDoorJsonPath文件
            jsonManager = JsonM("reNumberDict.json")          # 实例化JsonM,初始化里会读取backDoorJsonPath文件的json
            jsonManager.jsonContent = self.reNumberDict
                    # 将json文件的list和inBackDoorList拼接,并赋值给jsonContent
            jsonManager.writer()    # 将jsonContent写入文件
        else:
            jsonManager = JsonM("reNumberDict.json", self.reNumberDict)

    def random(self):
        """
        内置random版(相对性能低一点)用于按条件生成随机数,并把符合条件的随机数赋值给self.randomNumberlist
        :return:无返回值
        """
        self.backDoorJsonReader()
        if self.randomStatus:       # 检测是否达成运行条件
            pass
        else:               # 不达到就强制退出
            return
        count = 1			# 设置计数器,这个是为了计算生成了几个数
        while count <= self.Num:			# 检测是否超过了所需要的最大数 数量的个数
            RandNum = random.randint(self.minLim, self.maxLim)			# 生成一个随机整数(数的极限为minList和maxList)
            if RandNum in self.backDoorList:            # 如果该随机数出现在backDoorList之中,则跳出本次循环
                continue
            else:
                pass
            if RandNum in self.reNumberDict.keys():			# 如果随机数在重复字典的键中存在
                if self.reNumberDict[RandNum] == self.reLimit:       # 如果该键出现次数等于reLimit(重复最大次数),则放弃该值
                    continue
                else:
                    self.reNumberDict[RandNum] = self.reNumberDict[RandNum] + 1     # 如果以上条件都满足,则给该键的值加1,表示出现次数加1
            else:					# 如果该随机数没有在重复字典的键中出现过,则添加该键,并给其值为1,表示该键出现一次

                self.reNumberDict[RandNum] = 1
            self.randomNumberlist.append(RandNum)  # 将一个随机数加入到randomNumberList
            count = count + 1				# count计数器加1

    def numpyRandom(self):
        """
        高性能版Random,使用了numpy库。
        用于按条件生成随机数,并把符合条件的随机数赋值给self.randomNumberlist
        :return:无返回值
        """
        self.backDoorJsonReader()
        if self.randomStatus:       # 检测是否达成循环运行条件
            print("成功")
            if numpyStatus:         # 检测是否成功引入了numpy
                pass
            else:
                print("未成功引用Numpy模块,已使用内置备用随机数生成器")     # 弹出提示
                self.random()           # 运行内置random的随机数生成器
        else:               # 不达到就强制退出
            return
        count = 1			# 设置计数器,这个是为了计算生成了几个数
        while count <= self.Num:			# 检测是否超过了所需要的最大数 数量的个数
            RandNum = numpy.random.randint(self.minLim, self.maxLim + 1, size=1)			# 生成一个随机整数(数的极限为minList和maxList)
            if RandNum in self.backDoorList:            # 如果该随机数出现在backDoorList之中,则跳出本次循环
                continue
            else:
                pass
            if RandNum.tolist()[0] in self.reNumberDict.keys():			# 如果随机数在重复字典的键中存在
                if self.reNumberDict[RandNum.tolist()[0]] == self.reLimit:       # 如果该键出现次数等于reLimit(重复最大次数),则放弃该值
                    continue
                else:
                    self.reNumberDict[RandNum.tolist()[0]] = self.reNumberDict[RandNum] + 1     # 如果以上条件都满足,则给该键的值加1,表示出现次数加1
            else:					# 如果该随机数没有在重复字典的键中出现过,则添加该键,并给其值为1,表示该键出现一次

                self.reNumberDict[RandNum.tolist()[0]] = 1
            self.randomNumberlist.append(RandNum.tolist()[0])  # 将一个随机数加入到randomNumberList
            count = count + 1				# count计数器加1

在这个代码中,我还用numpy的random.randint()模块写了个高性能版的random函数,具体性能高了
多少我也不知道,但是据说,应该在生成数很多的时候,会有体现。



然后,之后,又在班上用了这个版本,我和室友成功,再也没有被抽取去搞卫生了。

有一天,隔壁班长要用它抽取一下班上的一个值日安排。一次就随机了300次,据他说是多按了一下0
不过,在我嘲笑他眼瞎手残的时候,他突然问了我个问题。

如果我们要抽取的数据次数很多,并且可重复次数也多,怎么去统计呢?

我告诉他,我的类里,有一个方法储存了一个json,里面有数据的重复次数。然后我们打开,瞬间就懵了

因为,数据太多了,我们这2个四眼仔没法承受这样密密麻麻数据的冲击。

于是,我想到一个库 Matplotlib

我可以用它配合那个json的内容,生成一个柱状图。这样数据不可视化了吗?

于是,就在类中又加入了一个方法。

def reNumberDictViewer(self):
    """
    用numpy和matplotlib.pyplot对reNumberDict(数字的重复次数统计),并绘制二维柱状图
    :return: 无返回值
    """
    if numpyStatus:         # 检测是否成功引入了numpy
        if pltStatus:           # 检测是否成功引入了matplotlib.pyplot
            if len(self.randomNumberlist) == 0 or len(self.randomNumberlist) != self.Num:
                # 如果self.randomNumberlist 为空,或者长度不为需求所需,则不运行
                return
            else:
                pass
        else:
            print("未成功引入matplotlib绘图模块,未完成绘图功能")
            return
    else:
        print("为成功引入Numpy模块,未完成绘图功能")
        return

    plt.rcParams['font.sans-serif'] = ['SimHei']        # 用来正常显示中文
    x = numpy.array(list(self.reNumberDict.keys()))     # 把self.reNumberDict的 键 依次存入有序列表 x 中
    y = numpy.array(list(self.reNumberDict.values()))       # 把self.reNumberDict的 值 依次存入有序列表 y 中
    count = 1           # 定义一个循环控制器
    xy = []             # 定义一个装有 数据柱 标注 的坐标的list
    while (count < len(x)):         # 用循环将x和y的值,一一对应的放入temp,然后装到xy中
        temp = []
        temp.append(x[count - 1])
        temp.append(y[count - 1])
        xy.append(temp)
        count = count + 1           # 计数器自加一
    title = str(self.minLim) + " 号到 " + str(self.maxLim) + " 号的共 " + str(self.Num) + " 次随机抽取情况"
        # 动态赋值title的内容
    plt.figure(title, figsize=(self.maxLim / 2, 4))     # 设置figure的宽度和高度,以及figure的标题
    plt.title(title)        # 设置图表的标题
    plt.bar(x, y, 0.5, alpha=1, color='b')         # 设置柱状图的x,y信息,柱子的宽度,透明度,颜色
    plt.xticks(numpy.arange(self.minLim, self.maxLim + 1, 1))       # 设置x轴显示的信息
    plt.yticks(numpy.arange(1, self.reLimit + 1, 1))                # 设置y轴显示的信息
    for i in xy:
        plt.annotate("%s" % i[1], xy=i, xytext=(i[0] - 0.1, i[1] + 0.025))       # 用循环,依次标记柱状图的柱子的值
    plt.show()


这样,就用 Matplotlib 实现了数据的可视化。



这是我写的第一篇博客,有问题还希望各位大神指教,谢谢!

更多推荐

用 Python3 做一个可'作弊'随机数生成器,并对结果进行图表分析