这是一道 Python 面向对象编程的实例,包含面向对象编程、Class 类相关知识,请读者先自行掌握。本文分析已有代码,主要理解其中逻辑,学习编程方法。代码乍看之下稍微复杂,拆解之后,弄通逻辑,还是很清晰明了的。
前部分为代码分析,可以重点看后面的程序运行部分哈,配合图文,便于理解。
这是笔者第一次写实例分析,内容详细,篇幅较长,仅用于学习与交流。若有疏漏,请多包涵,欢迎各位指点和讨论啊~
【需求分析】
假设银行有 1 台 ATM 机,共 n 位客户来银行操作 ATM 机器,求客户平均的排队等候时间。
【逻辑梳理】
ATM 机操作时间由客人选择的办理业务决定,符合随机分布。
客户流到达银行时间不定,符合随机分布。
累加每位客户排队等候的时间,再除以总客户数,即可得出客户的平均等候时间。
【前提假设】
实际应用中,ATM 机和客户流时间均为随机数,在本实例需做出一定条件限制以方便计算。
ATM 机操作时间:1-5 分钟
下位客户到达时间:1-10 分钟
时间以整数为单位。
【代码总览】
class ATM():
def __init__(self, maxtime = 5):
self.t_max = maxtime
def getServCompleteTime(self, start = 0):
return start + random.randint(1, self.t_max)
class Customers():
def __init__(self, n):
self.count = n
self.left = n
def getNextArrvTime(self, start = 0, arrvtime = 10):
if self.left != 0:
self.left -= 1
return start + random.randint(1, arrvtime)
else:
return 0
def isOver(self):
return True if self.left == 0 else False
c = Customers(100)
a = ATM()
wait_list = []
wait_time = 0
cur_time = 0
cur_time += c.getNextArrvTime()
wait_list.append(cur_time)
while len(wait_list) != 0 or not c.isOver():
if wait_list[0] <= cur_time:
next_time = a.getServCompleteTime(cur_time)
del wait_list[0]
else:
next_time = cur_time + 1
if not c.isOver() and len(wait_list) == 0:
next_arrv = c.getNextArrvTime(cur_time)
wait_list.append(next_arrv)
if not c.isOver() and wait_list[-1] < next_time:
next_arrv = c.getNextArrvTime(wait_list[-1])
wait_list.append(next_arrv)
while next_arrv < next_time and not c.isOver():
next_arrv = c.getNextArrvTime(next_arrv)
wait_list.append(next_arrv)
for i in wait_list:
if i <= cur_time:
wait_time += next_time - cur_time
elif cur_time < i < next_time:
wait_time += next_time - i
else:
pass
cur_time = next_time
print(wait_time/c.count)
代码可以分为 7 个部分:
设定 ATM 机对象,设定客户流对象,设定初始数据,主程序(判断客户出场条件,统计客户排队等候时间,更新当前时间),输出最终结果
【模块拆解】
设定ATM机对象
class ATM():
def __init__(self, maxtime = 5):
self.t_max = maxtime
def getServCompleteTime(self, start = 0):
return start + random.randint(1, self.t_max)
用类方法设置 ATM 对象,其中限定最长操作时间 maxtime 为 5 分钟。
定义实例方法 getServCompleteTime(),作用是使用整数随机函 random.randint(),返回在 ATM 当次操作结束的时间,累加下次 ATM 操作时长,范围在 [1, maxtime] 之间。
设定客户流对象
class Customers():
def __init__(self, n):
self.count = n
self.left = n
def getNextArrvTime(self, start = 0, arrvtime = 10):
if self.left != 0:
self.left -= 1
return start + random.randint(1, arrvtime)
else:
return 0
def isOver(self):
return True if self.left == 0 else False
用类方法设置 Customers 对象,代表客户流,设置客户库中总数 self.count 为 n,初始时剩余客户数 self.left 相同。
定义实例方法 getNextArrvTime() ,作用是根据客户库存情况,提取客户至银行,返回从 某个时间点 start,累加下位客户到达银行需要的时间(可以理解为客户使用步行、骑行或开车的方式从库存转移到银行…),范围在[1, arrvtime]之间。
定义实例方法 isOver(),作用是判断客户库存是否清零。
设定初始数据
c = Customers(100)
a = ATM()
wait_list = []
wait_time = 0
cur_time = 0
cur_time += c.getNextArrvTime()
wait_list.append(cur_time)
设置 c 表示客户,a 表示 ATM,wait_list 是等待列表,wait_time 是客户总排队等候时间,cur_time 为当前时间,时间初始时皆为 0。
提取用户触发程序,用 getNextArrvTime() 提取第一位来银行的客户,返回客户到达时间,更新 cur_time。第一位客户到达银行后,默认自动加入排队列表,实际等候时间为 0。
主程序
while len(wait_list) != 0 or not c.isOver():
当排队列表不为空,或者客户库存未清零时执行操作。
初始时排队列表有 1 位客人,而且有库存。
*为何不只用库存未清零做判断条件呢?
当全部客户提取值排队列表后,库存为零,可是排队列表还有客户,若只用库存判断,则不执行主程序,无法累计列表剩余客户的等候时间。
判断客户出场条件
这一块包括 3 个 if 语句,分别进行这些操作:
-
将排第一的客户移除排队列表去操作 ATM ;
-
当没人排队并且还有客户库存时提取客户至列表;
-
当列表中排最后的客户到达时间与预计 ATM 操作完毕(闲置)时间之间有时间空档,而这时客户库存仍有客户,则提取客户排队,直至排最后的客户到达时间比预计 ATM 操作完毕时间迟。(这一步就是避免 ATM 机处于闲置状态,一直有人排队…)
if wait_list[0] <= cur_time:
next_time = a.getServCompleteTime(cur_time) del wait_list[0]
else:
next_time = cur_time + 1
如果排第一的客户到达时间比当前时间早,或者等于当前时间,就将客户移除列表去操作 ATM。调用 a.getServCompleteTime(),返回在 cur_time 上累加操作 ATM 时长,即 ATM 在 next_time 才闲置。
如果排第一的客户在当前时间之后到达,那就是客户还在路上,ATM 闲置,时间逐步 +1 推进进程,直至客户抵达。
if not c.isOver() and len(wait_list) == 0:
next_arrv = c.getNextArrvTime(cur_time)
wait_list.append(next_arrv)
经过上一步 if 操作,排队列表可能清零,这时如果还有客户库存,则将客户提取至排队列表。
if not c.isOver() and wait_list[-1] < next_time:
next_arrv = c.getNextArrvTime(wait_list[-1])
wait_list.append(next_arrv)
while next_arrv < next_time and not c.isOver():
next_arrv = c.getNextArrvTime(next_arrv)
wait_list.append(next_arrv)
当列表排最后的客户到达时间比当次 ATM 操作完毕的时间早,而且还有客户库存时,提取客户至队列,直至排最后的客户到达时间比当次 ATM 操作完毕的时间迟。这个做法是在还有客户库存时,让排队列表不为空,也避免了 ATM 闲置的情况。
*为什么选择当次ATM操作完毕的时间 next_time 为参照?
笔者思考是因为在真实情况中,在 ATM 被占用而且有人排队的情况下,路过的人看到可能会选别的时间再来办事,从而节省等候时间。选用 next_time 参照,可能是模拟这种情况,并且推进整个进程。
统计客户排队等候时间
for i in wait_list:
if i <= cur_time:
wait_time += next_time - cur_time
elif cur_time < i < next_time:
wait_time += next_time - i
else:
pass
用遍历循环统计排队列表中每位客户的等待时间。分段来统计客户等候时间,后面会画图说明,更加直观。
更新当前时间
cur_time = next_time #将 ATM 机器下次执行完毕时间 赋给 当前时间
将当次ATM操作完毕时间赋值给当前时间,下一位客户由当前时间开始进行 ATM 操作。
输出最终结果
print(wait_time/c.count) #当库存清零,计算平均等待时间
库存清零后,用总等候时间处以客户总数得出平均等待时间。
【程序运行】
接下来模拟程序运行过程,预设客户总数为 5 人,分别记为 A-E 并按顺序出场。
可以用两条平行的时间轴分别代表客户流和 ATM 使用情况。
【1】
初始时 cur_time 为 0,通过调用 c.getNextArrvTime() 从库存提取出第一位客户 A,添加至排队列表。
第 1 个 if 语句:由于 A 到达时间即是当前时间,也无其他客户,此时 A 无需排队,直接使用 ATM 机器。用 a.getServCompleteTime(cur_time) 获取 A 操作完 ATM 的时间,即 next_time。将 A 移除队列。
第 2 个 if 语句:当前排队列表为空,从库存提取客户 B,用 c.getNextArrvTime(cur_time) 获取 B 到达时间,即 next_arrv,并增加至排队列表。
第 3 个 if 语句:根据判断条件 wait_list[-1] < next_time,而队列中 B 到达时间比当前 next_time 迟,故不执行操作。
for 循环:此时排队列表只有客户 B,且是在当前 next_time 之后达到,不满足条件,故不执行操作。
更新当前时间,当前 next_time 即为 cur_time。
【2】
还有客户库存,继续执行主程序。
第 1 个 if 语句:此时客户 B 仍未到达,ATM 机器闲置,在当前时间基础上 +1 分钟,更新 next_time 以驱动进程。
第 2 个 if 语句:由于排队列表不为空,不满足条件,故不执行操作。
第 3 个 if 语句:判断条件 wait_list[-1] < next_time,就算客户 B 下一分钟就到达,也只满足 wait_list[-1] = next_time,不满足条件,故不执行操作。
for 循环:此时排队列表只有客户 B,且是在当前 next_time 之后达到,不满足条件,故不执行操作。
更新当前时间,当前 next_time 即为 cur_time。
此时重复执行至客户 B 到达。上述情况如(图 1)所示。
【3】
排队列表不为空,且还有客户库存,继续执行主程序。
第 1 个 if 语句:客户 B 到达后无需等候,直接使用 ATM。获取 B 操作完 ATM 的时间,即 next_time。将 B 移除队列。
第 2 个 if 语句:排队列表为空,提取库存中客户 C,获取 C 到达时间 next_arrv,并加入排队列表。
第 3 个 if 语句:排队列表中最后一位客户为 C,由图可知 C 到达时间早于 next_time,满足条件,执行操作。从库存提取客户 D,加入排队列表。若客户 D 到达时间仍早于 next_time,继续提取客户,直至最后一位客户到达时间晚于 next_time 或者客户库存清零为止。情况如(图2)所示。
本次预设总客户数为 5 人,即再提取一位客户 E 库存清零。添加完毕后,情况如(图 3)所示。
for 循环:排队列表有 C、D、E 这 3 位 客户。此时时间 cur_time 与 next_time 为绿色标识。
-
C 客户到达时间满足 elif 语句,设 C 等候时间 wait_time 为 C1,等于 next_time - C 到达时间。
-
D 客户同理,设 D 等候时间 wait_time 为 D1,等于 next_time - D 到达时间。
-
而 E 客户到达时间在 next_time 之后,不满足条件,无等候时间。
更新当前时间,当前 next_time 即为 cur_time。
【4】
此时库存已清零,但排队列表不为空,仍然执行主程序。
第 1 个 if 语句:排第一的客户是 C,获取 C 操作完 ATM 的时间,即 next_time。将 C 移除队列。
第 2 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
第 3 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
for 循环:此时时间 cur_time 与 next_time 为橙色标识。
-
D 客户到达时间满足 if 语句,设 D 等候时间 wait_time 为 D2,等于 next_time - cur_time。
-
E 客户到达时间满足 elif 语句,设 E 等候时间 wait_time 为 E1,等于 next_time - E 到达时间。
更新当前时间,当前 next_time 即为 cur_time。
【5】
此时库存已清零,但排队列表不为空,仍然执行主程序。
第 1 个 if 语句:排第一的客户是 D,获取 D 操作完 ATM 的时间,即 next_time。将 D 移除队列。
第 2 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
第 3 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
for 循环:此时时间 cur_time 与 next_time 为玫红标识。
E 客户到达时间满足 if 语句,设 E 等候时间 wait_time 为 E2,等于 next_time - cur_time。
更新当前时间,当前 next_time 即为 cur_time。
【6】
此时库存已清零,但排队列表仍有客户 E,仍然执行主程序。
第 1 个 if 语句:剩余客户 E,获取 E 操作完 ATM 的时间,即 next_time。将 E 移除队列。
第 2 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
第 3 个 if 语句:此时库存清零,无法新增客户,故不执行操作。
for 循环:排队列表清零,故不执行操作。
更新当前时间,当前 next_time 即为 cur_time。
【7】
此时库存已清零,排队列表已清零,故不执行主程序。
跳到输出语句 print,此时如(图4)所示,总等候时间 wait_time = C1 + D1 + D2 + E1 + E2,总客户数为 5,由 wait_time/c.count 即可算出平均等候时间。
程序结束。
【程序结果】
ATM: maxtime = 5
Customers: n = 100, arrvtime = 10
运行结果:
0.43
0.44
0.91
…
ATM: maxtime = 10
Customers: n = 100, arrvtime = 10
运行结果:
62.16
36.1
33.93
…
可以发现当ATM操作时间时长范围扩大后,客户平均等候时间更久。可以拷贝代码,修改参数运行,看看还能总结出什么规律呢?