Python 实例详解:银行 ATM 等待时间分析

这是一道 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 语句,分别进行这些操作:

  1. 将排第一的客户移除排队列表去操作 ATM ;

  2. 当没人排队并且还有客户库存时提取客户至列表;

  3. 当列表中排最后的客户到达时间与预计 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 使用情况。

Python 实例详解:银行 ATM 等待时间分析

(图 1)

【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)所示。

Python 实例详解:银行 ATM 等待时间分析

(图 2)

【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)所示。

Python 实例详解:银行 ATM 等待时间分析

(图 3)

本次预设总客户数为 5 人,即再提取一位客户 E 库存清零。添加完毕后,情况如(图 3)所示。

for 循环:排队列表有 C、D、E 这 3 位 客户。此时时间 cur_time 与 next_time 为绿色标识。

  1. C 客户到达时间满足 elif 语句,设 C 等候时间 wait_time 为 C1,等于 next_time - C 到达时间。

  2. D 客户同理,设 D 等候时间 wait_time 为 D1,等于 next_time - D 到达时间。

  3. 而 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 为橙色标识。

  1. D 客户到达时间满足 if 语句,设 D 等候时间 wait_time 为 D2,等于 next_time - cur_time。

  2. E 客户到达时间满足 elif 语句,设 E 等候时间 wait_time 为 E1,等于 next_time - E 到达时间。

更新当前时间,当前 next_time 即为 cur_time。

Python 实例详解:银行 ATM 等待时间分析

(图 4)

【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操作时间时长范围扩大后,客户平均等候时间更久。可以拷贝代码,修改参数运行,看看还能总结出什么规律呢?