如何生成高效优化的HH:MM格式正则表达式以过滤日志时间?
问题
给定两个HH:MM格式的时间,需要过滤出包含这两个时间之间所有时刻的日志。例如给定09:30和15:30,可以用简化正则T(09:[345]|1[01234]|15:[123])(假设输入格式合法)。当前用Python代码生成枚举所有时刻的正则(如T(09:30|09:31|.....|15:30)),想咨询:
- 如何生成更高效的正则?
- 枚举式正则是否比优化后的正则处理速度更快?
最终将在Go正则引擎中使用该正则,也可接受Unix工具方案。
当前使用的Python代码如下:
from dataclasses import dataclass import datetime import re @dataclass class TimeFilter: start: datetime.time stop: datetime.time def timergx(self): i = self.start.hour * 60 + self.start.minute stop = self.stop.hour * 60 + self.stop.minute return "T(" + "|".join(f"{x // 60:02d}:{x % 60:02d}" for x in range(i, stop)) + ")" def HHMM2time(txt: str): return datetime.time(*[int(x) for x in txt.split(":")]) tf = TimeFilter(HHMM2time("9:30"), HHMM2time("15:30")) assert re.match(tf.timergx(), "T10:30")
一、生成高效正则的方法
要生成紧凑高效的正则,需将时间范围拆分为起始小时的剩余分钟段、中间完整小时段、结束小时的开头分钟段三部分处理,通过字符范围匹配减少分支数量:
实现代码
from dataclasses import dataclass import datetime @dataclass class TimeFilter: start: datetime.time stop: datetime.time def timergx(self): parts = [] start_hour, start_min = self.start.hour, self.start.minute stop_hour, stop_min = self.stop.hour, self.stop.minute # 处理起始小时的剩余分钟 if start_min < 60 and start_min != 0: min_part = self._get_minute_range_re(start_min, 59) parts.append(f"{start_hour:02d}:{min_part}") # 处理中间完整小时 if start_hour + 1 < stop_hour: if start_hour +1 == stop_hour -1: parts.append(f"{start_hour+1:02d}:[0-5][0-9]") else: parts.append(f"{start_hour+1:02d}[{start_hour+2}-{stop_hour-1}]:[0-5][0-9]".replace("-", "")) # 处理结束小时的分钟 if stop_hour > start_hour: if stop_min != 0: min_part = self._get_minute_range_re(0, stop_min) parts.append(f"{stop_hour:02d}:{min_part}") elif stop_hour == start_hour: min_part = self._get_minute_range_re(start_min, stop_min) parts.append(f"{start_hour:02d}:{min_part}") return f"T({'|'.join(parts)})" def _get_minute_range_re(self, start, end): # 生成分钟范围的正则表达式 if start == end: return f"{start:02d}" ranges = [] current_start = start for m in range(start, end+1): if m == end or (m //10) != ((m+1)//10): if current_start == m: ranges.append(f"{current_start:02d}") else: tens_current = current_start //10 ones_current = current_start %10 tens_m = m //10 ones_m = m %10 if tens_current == tens_m: ranges.append(f"{tens_current}[{ones_current}-{ones_m}]") else: ranges.append(f"{tens_current}[{ones_current}-9]|{tens_m}[0-{ones_m}]") current_start = m+1 return "|".join(ranges) def HHMM2time(txt: str): return datetime.time(*[int(x) for x in txt.split(":")]) tf = TimeFilter(HHMM2time("9:30"), HHMM2time("15:30")) print(tf.timergx()) # 输出:T(09:[3-5][0-9]|1[0-4]:[0-5][0-9]|15:[0-2][0-9]|15:30)
二、性能对比:枚举式 vs 优化正则
在Go的RE2正则引擎中,优化后的紧凑正则速度更快,原因如下:
- 枚举式正则会生成大量分支(比如09:30到15:30对应361个分支),RE2处理多分支时需要逐个匹配,分支越多耗时越长。
- 紧凑正则通过字符范围匹配将分支压缩到个位数(示例仅4个分支),引擎可以快速完成范围判断。
- 枚举式正则字符串长度远大于紧凑正则,加载和解析的开销也更高。
仅当时间范围极小(比如仅几个时刻)时,两者差异可忽略;范围超过10个时刻后,紧凑正则的优势会明显显现。
三、Unix工具替代方案
如果不需要用正则,Unix环境下用awk直接解析时间做数值比较,效率比正则更高:
# 假设日志每行包含类似"T09:30"的时间字段,提取后判断 awk -v start="09:30" -v stop="15:30" ' function time2min(t) { split(t, hhmm, ":"); return hhmm[1]*60 + hhmm[2]; } { # 提取时间部分(假设时间在字符串开头,格式为"T09:30...") t = substr($0, 2, 5); if (time2min(t) >= time2min(start) && time2min(t) <= time2min(stop)) print; }' logfile.txt
这种方法直接将时间转为分钟数做数值对比,逻辑直观,处理大日志文件时性能更稳定。
内容的提问来源于stack exchange,提问作者KamilCuk
相关产品推荐
相关产品推荐

