这可能是一个已经解决的问题,但我无法弄清楚。我有两个较大的整数,我们称之为start_number
和end_number
(它们代表一个连续的电话号码块)。其他数字(表示为字符串)将输入到我的系统中,我需要使用正则表达式将其与“范围正则表达式”进行匹配,以查看数字字符串是否落在start_number
和end_number
之间或之间
例如:
start_number = 99519000
end_number = 99519099
因此
expression = "^995190[0-9][0-9]$"
这样我最终可以匹配以下示例数字(一次一个地到达我的系统,并且可以随时到达):
"99519000"
< - MATCH "99519055"
< - MATCH "99519099"
< - MATCH "99519100"
< - NOT MATCH "99512210"
< - NOT MATCH "41234123"
< - NOT MATCH 如果给出任何合理的expression
和start_number
,我如何使用python 创建正则表达式字符串模式“end_number
”?我有几个开始/结束编号'块'我必须创建正则表达式模式,我只需要一种方法来编程这些模式。
假设:
是公平的Start_number
将始终小于end_number
start_number
和end_number
将始终保持相同的“长度”(即,当表示为字符串时,始终将具有相同数量的基本10'字符',如果它'让生活更轻松。编辑:为清晰起见
答案 0 :(得分:16)
[假设您需要这个,因为它是一些需要正则表达式的奇怪的第三方系统]
新方法
我越是想到弗雷德里克的评论,我就越同意。即使输入字符串很长,regexp引擎也应该能够将其编译为紧凑的DFA。在许多情况下,以下是一个明智的解决方案:import re
def regexp(lo, hi):
fmt = '%%0%dd' % len(str(hi))
return re.compile('(%s)' % '|'.join(fmt % i for i in range(lo, hi+1)))
(它适用于下面测试中的所有数值范围,包括99519000 - 99519099.粗略的包络计算表明9位数字大约是1GB内存的限制。这个'如果大多数数字匹配匹配;如果只匹配少数数字,则可以更大。)。
旧方法
[再次更新以提供更短的结果 - 除了合并偶尔\d\d
与手工制作的一样好之外
假设所有数字都是相同的长度(即必要时左边是零填充),这有效:
import re
def alt(*args):
'''format regexp alternatives'''
if len(args) == 1: return args[0]
else: return '(%s)' % '|'.join(args)
def replace(s, c):
'''replace all characters in a string with a different character'''
return ''.join(map(lambda x: c, s))
def repeat(s, n):
'''format a regexp repeat'''
if n == 0: return ''
elif n == 1: return s
else: return '%s{%d}' % (s, n)
def digits(lo, hi):
'''format a regexp digit range'''
if lo == 0 and hi == 9: return r'\d'
elif lo == hi: return str(lo)
else: return '[%d-%d]' % (lo, hi)
def trace(f):
'''for debugging'''
def wrapped(lo, hi):
result = f(lo, hi)
print(lo, hi, result)
return result
return wrapped
#@trace # uncomment to get calls traced to stdout (explains recursion when bug hunting)
def regexp(lo, hi):
'''generate a regexp that matches integers from lo to hi only.
assumes that inputs are zero-padded to the length of hi (like phone numbers).
you probably want to surround with ^ and $ before using.'''
assert lo <= hi
assert lo >= 0
slo, shi = str(lo), str(hi)
# zero-pad to same length
while len(slo) < len(shi): slo = '0' + slo
# first digits and length
l, h, n = int(slo[0]), int(shi[0]), len(slo)
if l == h:
# extract common prefix
common = ''
while slo and slo[0] == shi[0]:
common += slo[0]
slo, shi = slo[1:], shi[1:]
if slo: return common + regexp(int(slo), int(shi))
else: return common
else:
# the core of the routine.
# split into 'complete blocks' like 200-599 and 'edge cases' like 123-199
# and handle each separately.
# are these complete blocks?
xlo = slo[1:] == replace(slo[1:], '0')
xhi = shi[1:] == replace(shi[1:], '9')
# edges of possible complete blocks
mlo = int(slo[0] + replace(slo[1:], '9'))
mhi = int(shi[0] + replace(shi[1:], '0'))
if xlo:
if xhi:
# complete block on both sides
# this is where single digits are finally handled, too.
return digits(l, h) + repeat('\d', n-1)
else:
# complete block to mhi, plus extra on hi side
prefix = '' if l or h-1 else '0'
return alt(prefix + regexp(lo, mhi-1), regexp(mhi, hi))
else:
prefix = '' if l else '0'
if xhi:
# complete block on hi side plus extra on lo
return alt(prefix + regexp(lo, mlo), regexp(mlo+1, hi))
else:
# neither side complete, so add extra on both sides
# (and maybe a complete block in the middle, if room)
if mlo + 1 == mhi:
return alt(prefix + regexp(lo, mlo), regexp(mhi, hi))
else:
return alt(prefix + regexp(lo, mlo), regexp(mlo+1, mhi-1), regexp(mhi, hi))
# test a bunch of different ranges
for (lo, hi) in [(0, 0), (0, 1), (0, 2), (0, 9), (0, 10), (0, 11), (0, 101),
(1, 1), (1, 2), (1, 9), (1, 10), (1, 11), (1, 101),
(0, 123), (111, 123), (123, 222), (123, 333), (123, 444),
(0, 321), (111, 321), (222, 321), (321, 333), (321, 444),
(123, 321), (111, 121), (121, 222), (1234, 4321), (0, 999),
(99519000, 99519099)]:
fmt = '%%0%dd' % len(str(hi))
rx = regexp(lo, hi)
print('%4s - %-4s %s' % (fmt % lo, fmt % hi, rx))
m = re.compile('^%s$' % rx)
for i in range(0, 1+int(replace(str(hi), '9'))):
if m.match(fmt % i):
assert lo <= i <= hi, i
else:
assert i < lo or i > hi, i
函数regexp(lo, hi)
构建一个与lo
和hi
之间的值匹配的正则表达式(零填充到最大长度)。您可能需要在^
之前和$
之后(如在测试代码中)强制匹配为整个字符串。
算法实际上非常简单 - 它递归地将事物划分为公共前缀和&#34;完整块&#34;。一个完整的块就像200-599,可以可靠地匹配(在这种情况下是[2-5]\d{2}
)。
所以123-599分为123-199和200-599。后半部分是一个完整的块,前半部分的公共前缀为1和23-99,递归处理为23-29(公共前缀)和30-99(完整块)(我们最终终止,因为参数每次通话都比初始输入短。)
唯一令人讨厌的细节是prefix
,这是必需的,因为regexp()
的参数是整数,因此当调用生成例如00-09的正则表达式时,它实际上生成了正则表达式0-9,没有前导0。
输出是一堆测试用例,显示范围和正则表达式:
0 - 0 0
0 - 1 [0-1]
0 - 2 [0-2]
0 - 9 \d
00 - 10 (0\d|10)
00 - 11 (0\d|1[0-1])
000 - 101 (0\d\d|10[0-1])
1 - 1 1
1 - 2 [1-2]
1 - 9 [1-9]
01 - 10 (0[1-9]|10)
01 - 11 (0[1-9]|1[0-1])
001 - 101 (0(0[1-9]|[1-9]\d)|10[0-1])
000 - 123 (0\d\d|1([0-1]\d|2[0-3]))
111 - 123 1(1[1-9]|2[0-3])
123 - 222 (1(2[3-9]|[3-9]\d)|2([0-1]\d|2[0-2]))
123 - 333 (1(2[3-9]|[3-9]\d)|2\d\d|3([0-2]\d|3[0-3]))
123 - 444 (1(2[3-9]|[3-9]\d)|[2-3]\d{2}|4([0-3]\d|4[0-4]))
000 - 321 ([0-2]\d{2}|3([0-1]\d|2[0-1]))
111 - 321 (1(1[1-9]|[2-9]\d)|2\d\d|3([0-1]\d|2[0-1]))
222 - 321 (2(2[2-9]|[3-9]\d)|3([0-1]\d|2[0-1]))
321 - 333 3(2[1-9]|3[0-3])
321 - 444 (3(2[1-9]|[3-9]\d)|4([0-3]\d|4[0-4]))
123 - 321 (1(2[3-9]|[3-9]\d)|2\d\d|3([0-1]\d|2[0-1]))
111 - 121 1(1[1-9]|2[0-1])
121 - 222 (1(2[1-9]|[3-9]\d)|2([0-1]\d|2[0-2]))
1234 - 4321 (1(2(3[4-9]|[4-9]\d)|[3-9]\d{2})|[2-3]\d{3}|4([0-2]\d{2}|3([0-1]\d|2[0-1])))
000 - 999 \d\d{2}
99519000 - 99519099 995190\d\d
当最后一次测试循环超过99999999时,需要一段时间才能运行。
表达式应该足够紧凑以避免任何缓冲区限制(我猜测最坏情况下的内存大小与最大数字中的位数的平方成正比)。
ps我正在使用python 3,但我不认为这在这里有很大的不同。
答案 1 :(得分:0)
您可以使用pip安装此软件包
pip安装正则表达式引擎
from regex_engine import generator
generate = generator()
regex = generate.numerical_range(99519000, 99519099)
print(regex)
^(995190 [1-8] [0-9] | 9951900 [0-9] | 9951909 [0-9])$
您还可以为浮点数和负范围生成正则表达式
from regex_engine import generator
generate = generator()
regex1 = generate.numerical_range(5,89)
regex2 = generate.numerical_range(81.78,250.23)
regex3 = generate.numerical_range(-65,12)