通过Python编写模拟器对方案进行测试和分析的方法_Prj001

        在《Problem Solving with Algorithms and Data Structures》一书的第86页,作者(Brad Miller, David Ranum)提出了一个很有趣的问题并且通过Python写出的模拟程序进行了测试和分析。这种思考的方式很特别,可以说在程序执行之前,作者也不知道哪一个是最优方案,程序本身只是反映问题中的条件,而程序的运行则是对现实的模拟及对方案的测试。我觉得这种思路非常有借鉴价值,下面我就把问题和对应Python程序给出,供大家参考。(本文中Python程序的思路来源于上述这本书,但是PosPro做了一定的优化,并给出了详细的注释)


一、问题描述:
图书馆中有一台打印机,打印机有两种工作模式:每分钟10页(但打印质量较低),每分钟5页(打印效果较好)。打印机一次只能处理一个任务,其余任务可排队等候。
每个小时最多有10位学生在图书馆,他们在一小时终最多提交2次打印任务,每个打印任务的页数为1至20页不等。
问:综合分析两种打印模式,分析学生的平均等待打印时间和是否能在1小时内完成所有学生提交的打印任务


二、解决思路:
将打印机(Printer), 打印任务(Task)各自建立一个类,由于打印机对于任务的处理是逐条进行(FIFO),还可以建立一个Queue类,用于收集所有Task。
最后,建立一个模拟器(Simulator),该模拟器以一个3600次的循环来代表一小时的3600秒,通过在循环中的变化来体现一小时终可能出现的各种情况。


三、解决步骤
1. 建立一个队列数据结构Queue,只需要实现基本的enqueue, dequeue, isEmpty, size函数即可
# -*- coding=utf-8 -*-
# v150710_1 by PosPro
# please visit: http://blog.csdn.net/pospro
## 基于Python内置的list,实现先入先出(FIFO)的队列

class Queue:
	def __init__(self):
		self.qList=[]

	def enqueue(self, data):
		self.qList.append(data)
		## 向队列添加数据,复杂度为O(1)

	def dequeue(self):
		return self.qList.pop(0)
		## 删除数据复杂度O(n)

	def isEmpty(self):
		return self.qList==[]

	def size(self):
		return len(self.qList)

	def __str__(self):
		return self.qList.__str__()


'''以下部分仅作测试之用
q = Queue()
q.enqueue('hello')
print q
q.enqueue('dog')
print q
q.enqueue(3)
print q
q.dequeue()
print q
q.enqueue('cat')
print q
print str(q.size())
q.dequeue()
print q
print q.isEmpty()
q.dequeue()
print q
'''


2. 建立Printer类。显然在初始化该类时,必须告知其模式(10页/min? 5页/min?), 打印机显然需要知道自己是否当前处于工作状态,当前正在进行的任务还需多少时间才能完成。这三点就是Printer类必须具备的成员变量。打印机类的成员函数则应包括:isBusy,startTask, tick(用于和模拟时间相对应)。

由于每一个任务所对应的页数是不一样的,所以在startTask时,需要一个输入参数Task,Task本身将给出具体的页数。

# -*- coding=utf-8 -*-
# v150710_1 by PosPro
# please visit: http://blog.csdn.net/pospro
## 建立一个可以用于模拟的Printer

from Queue import Queue

class Printer:
	def __init__(self, pagesPerMinute):
		self.ppm=pagesPerMinute
		self.isWorking=False
		self.timeRemaining=0

	def isBusy(self):
		return self.isWorking

	def startTask(self, task):
		self.isWorking=True
		self.timeRemaining=task.getPages()*60/self.ppm
		## 上句得出的是新任务需要多少秒后才能完成, 60带来的是从分钟到秒的变换

	##这个函数是模拟器工作的关键,它将1次循环和1秒对应了起来
	def tick(self): 
		if self.timeRemaining>0:
			self.timeRemaining-=1
			if self.timeRemaining<=0:
				self.timeRemaining=0
				self.isWorking=False


3. 建立Task类。由于每个任务所对应的打印页数是随机的,所以在初始化该函数时需要随机生成页数。同时,为了计算任务在队列中的等待时间,Task类还应记录生成时的时间戳(由Simulator提供,Task自己记录)
# -*- coding=utf-8 -*-
# v150710_1 by PosPro
# please visit: http://blog.csdn.net/pospro
## 建立一个可以随机生成打印页数的Task, 该Task同时还记录了任务产生时的时间戳

import random

class Task:
	def __init__(self, time):
		self.pages=random.randrange(1,21) ## 生成1~20的随机页数
		self.timestamp=time

	def getPages(self):
		return self.pages

	def getTimestamp(self):
		return self.timestamp

	def waitTime(self, currentTime):
		return currentTime-self.timestamp


4. 建立测试函数Simulator。以一个3600次的循环模拟一小时中的3600秒,在每一个循环(每一秒),以此驱动打印任务的产生,打印任务的执行。在循环结束后汇总分析。
在其中有一个辅助函数isNewTaskEmerge(),其原理如下,因为题目中的描述是,每个小时最多有10位学生在图书馆,他们在一小时终最多提交2次打印任务。换而言之,一小时内最多有20次任务,也即180秒内可能产生1次任务。但是产生任务的具体时间是随机的,所以这个辅助函数在每秒钟抽取一下1~180之间的随机数,如果等于某个特定值就认为有新任务产生(这个特定值具体是多少无关紧要,只要在180以内,在某个特定秒,概率都是1/180。本例中取11)。
# -*- coding=utf-8 -*-
# v150710_1 by PosPro
# please visit: http://blog.csdn.net/pospro
## 以一个3600次的循环模拟一小时中的3600秒,在每一个循环(每一秒),
## 以此驱动打印任务的产生,打印任务的执行。在循环结束后汇总分析

import random
from Queue import Queue
from Printer import Printer
from Task import Task

'''
isNewTaskEmerge(),是一个辅助函数,其原理如下:
因为题目中的描述是,每个小时最多有10位学生在图书馆,
他们在一小时终最多提交2次打印任务。
换而言之,一小时内最多有20次任务,也即180秒内可能产生1次任务。
但是产生任务的具体时间是随机的,所以这个辅助函数在每秒钟抽取一下1~180之间的随机数,
如果等于某个特定值就认为有新任务产生
这个特定值具体是多少无关紧要,只要在180以内,在某个特定秒,概率都是1/180。
本例中取11。
'''
def isNewTaskEmerge():
	randoNum=random.randrange(1,181)
	if randoNum==11:
		return True  ## 此时将产生一个新任务
	else:
		return False

def Simulator(totalTime, pagesPerMinute):
	libraryPrinter=Printer(pagesPerMinute)
	printTaskQueue=Queue() # 保存所有生成的Task
	waitingTime=[]  #以一个队列保存所有Task的等待时间

	for t in range(totalTime): ##totalTime就是测试的总时间,循环一次是1秒
		if isNewTaskEmerge():
			newTask=Task(t) #传入当前时间t作为时间戳
			printTaskQueue.enqueue(newTask)

		if libraryPrinter.isBusy():
			libraryPrinter.tick()	#如果打印机当前有任务,则继续工作
		else: 
			if not printTaskQueue.isEmpty():
				nextTask=printTaskQueue.dequeue()
				waitingTime.append(nextTask.waitTime(t)) 
				#传入当前时间以便计算在该任务在队列中的等待时间
				libraryPrinter.startTask(nextTask)

	avgWaitTime=sum(waitingTime)/len(waitingTime)
	print "Average Wait %6.2f secs %3d tasks remaining."%(avgWaitTime, printTaskQueue.size())



## 以下代码启动测试程序
print "=== Mode 5 pages/min ====="
for i in range(10): #每种工作模式测试10次
	Simulator(3600, 5)

print
print "=== Mode 10 pages/min ====="
for i in range(10): #每种工作模式测试10次
	Simulator(3600, 10)


5. 运行
Simulator的末尾的一段程序,启动了一次测试。每种工作模式各执行10次
将以上四个文件放到同一文件夹下,执行截图中所示的命令



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值