实现原则
尽可能提高在高频调用前提下的记录和查询性能。
实现逻辑
在查询性能方面,实现在 O ( 1 ) O(1) O(1) 的时间复杂度内计算任意时间段内的平均速度,我们记录每一条记录数量的前缀和,从而使计算任意时间段内的完成数量时,只需要将两个元素相减即可。
在记录性能方面,我们有两种方案:
- 构造包含每一秒都前缀和序列,然后二分查找目标元素,查询性能为 O ( s ) O(s) O(s),其中 s s s 为运行总时长(单位为秒),插入性能为 O ( 1 ) O(1) O(1)
- 构造仅包含有记录的秒的前缀和序列,查询性能为 O ( 1 ) O(1) O(1),插入性能为 O ( t ) O(t) O(t),其中 t t t 为距离上一次记录的时间间隔(单位为秒)
因为我们主要优化高频调用前提下的性能,所以采用方案 2。
综上所述,总体时间、空间复杂度如下:
- 时间复杂度:初始化、启动定时器、停止定时器、查询速度均为 O ( 1 ) O(1) O(1),插入记录的时间复杂度为 O ( t ) O(t) O(t),其中 t t t 为距离上一次记录的时间间隔(单位为秒);
- 空间复杂度: O ( s ) O(s) O(s),其中 s s s 为运行总时长(单位为秒)。
具体实现如下:
from typing import Optional, List
class TimerError(Exception):
"""计时器异常"""
class SpeedTracker:
"""支持计算平均速度和最近速度的计时器
空间复杂度:O(s),其中 s 为计时器总运行时间
"""
def __init__(self):
"""实例化计时器
时间复杂度:O(1)
"""
self._t_start: Optional[int] = None # 开始时间的时间戳
self._t_end: Optional[int] = None # 结束时间的时间戳
self._record: List[int] = [0, 0] # 每一秒记录数量的前缀和(开头包含额外的一个 0)
def start(self) -> None:
"""启动计时器(根据调用此方法的时间计算平均速度,调用此方法前,不允许调用其他方法)
时间复杂度:O(1)
"""
self._t_start = time.time()
def end(self) -> None:
"""停止计时器(调用此方法后,不允许调用 record 方法)
时间复杂度:O(1)
"""
if self._t_start is None:
raise TimerError("未启动定时器,无法停止计时器")
self._t_end = time.time()
def record(self) -> None:
"""记录当前时间(通常每完成一次任务调用一次)
时间复杂度:O(t),其中 t 为距离上一次记录的时间间隔(单位为秒)
"""
if self._t_start is None:
raise TimerError("未启动定时器,无法记录时间")
if self._t_end is not None:
raise TimerError("已停止定时器,无法记录时间")
idx = int(time.time() - self._t_start) + 1 # 计算下标
while len(self._record) <= idx:
self._record.append(self._record[-1]) # 补齐前缀和的长度
self._record[-1] += 1 # 累加前缀和
def get_run_time(self) -> float:
"""获取总运行时间(单位:秒)
时间复杂度:O(1)
Returns
-------
run_time : float
总运行时间
"""
if self._t_start is None:
return 0.0 # 如果计时器没有启动,那么总运行时间为 0 秒
if self._t_end is None:
t_end = time.time() # 如果计时器暂未停止,那么总运行时间为从开启计时器到当前时间之间的时间
else:
t_end = self._t_end # 如果计时器已经停止,那么总运行时间为从开启计时器到停止计时器之间的时间
return t_end - self._t_start
def get_average_speed(self) -> float:
"""获取总平均速度(单位:次/秒)
时间复杂度:O(1)
Returns
-------
speed : float
平均速度
"""
if self._t_start is None:
return float("NaN") # 如果计时器暂未启动,那么总平均速度为 NaN
if self._t_end is not None:
run_time = self.get_run_time()
if run_time > 0:
return self._record[-1] / self.get_run_time() # 如果计时器已经停止,那么总平均速度为 总任务数 / 总运行时间
else:
return float("NaN") # 如果计时器已经停止,且总运行时间为 0,那么总平均速度为 NaN
# 如果计时器暂未停止,那么总平均速度就是所有已结束秒的平均速度
return self._get_speed(idx_start=0, idx_end=max(len(self._record) - 2, 0))
def get_recent_speed(self, n: int) -> float:
"""获取最近 n 秒(已结束秒)的平均速度(单位:次/秒)
如果启动时间不足 1 秒,则平均速度为 NaN
时间复杂度:O(1)
Parameters
----------
n : int
计算平均速度的秒数范围
Returns
-------
speed : float
平均速度
"""
if self._t_start is None:
return float("NaN") # 如果计时器暂未启动,那么平均速度为 NaN
# 计算最近 n 个已结束秒的平均速度
return self._get_speed(idx_start=max(len(self._record) - 2 - n, 0), idx_end=max(len(self._record) - 2, 0))
def _get_speed(self, idx_start: int, idx_end: int) -> float:
"""获取从 idx_start 到 idx_end 之间的平均速度(单位:次/秒)
如果开始下标等于截止下标,则平均速度为 NaN
时间复杂度:O(1)
Parameters
----------
idx_start : int
开始下标(包含)
idx_end : int
截止下标(不包含)
Returns
-------
speed : float
平均速度
"""
if idx_start < idx_end: # 已经至少完整地运行过 1 秒
return (self._record[idx_end] - self._record[idx_start]) / (idx_end - idx_start)
else: # 暂未完整地运行过 1 秒
return float("NaN")
使用样例
if __name__ == "__main__":
import time
speed_tracker = SpeedTracker()
speed_tracker.start()
for _ in range(100):
time.sleep(0.1) # 模拟任务执行时间
speed_tracker.record() # 记录任务时间
print("recent_speed:", speed_tracker.get_recent_speed(1)) # 查看当前平均速度
print("run_time:", speed_tracker.get_run_time()) # 查看总运行时间
print("average_speed:", speed_tracker.get_average_speed()) # 查看总平均速度
speed_tracker.end()
print("run_time:", speed_tracker.get_run_time()) # 查看总运行时间
print("average_speed:", speed_tracker.get_average_speed()) # 查看总平均速度