[英]Pythonic way to find the last position in a string matching a negative regex
在Python中,我尝试在与给定模式匹配的任意字符串中找到最后一个位置,该模式被指定为负字符集正则表达式模式。 例如,使用字符串uiae1iuae200
,并且不是数字的模式(Python中的正则表达式模式为[^0-9]
),我需要'8'('200'之前的'e'' ) 结果。
实现这一目标的最pythonic方法是什么?
因为在Python文档中快速找到方法文档和最适合的方法(由于方法文档位于相应页面中间的某个位置,如重新页面中的re.search()
),有点棘手,最好方式我很快发现自己正在使用re.search()
- 但是当前的形式必须是一种次优的方式:
import re
string = 'uiae1iuae200' # the string to investigate
len(string) - re.search(r'[^0-9]', string[::-1]).start()
我对此不满意有两个原因: - a)我需要在使用[::-1]
之前反转string
,并且 - b)我还需要反转结果位置(从len(string)
减去它因为以前扭转了弦。
需要有更好的方法,甚至可能是re.search()
的结果。
我知道re.search(...).end()
over .start()
,但是re.search()
似乎将结果拆分成组,为此我没有快速找到一种不麻烦的方式来申请它到最后一个匹配的组。 如果不指定组, .start()
.end()
等似乎始终匹配第一个组,该组没有关于最后一个匹配的位置信息。 但是,选择组似乎首先要求将返回值临时保存在变量中(这会阻止整齐的单行),因为我需要访问有关选择最后一个组然后选择.end()
来自这个群体。
你的pythonic解决方案是什么? 我认为pythonic比拥有最优化的运行时更重要。
更新
解决方案在角落情况下也应该起作用,例如123
(没有与正则表达式匹配的位置),空字符串等。它不应该崩溃,例如因为选择空列表的最后一个索引。 然而,即使我在问题中上面的丑陋答案需要不止一行,我想这可能是不可能的(仅仅因为需要检查re.search()
或re.finditer()
的返回值re.finditer()
在处理之前)。 出于这个原因,我会接受pythonic多线解决方案。
您可以使用re.finditer
提取所有匹配项的起始位置,并从列表中返回最后一个匹配项。 试试这个Python代码:
import re
print([m.start(0) for m in re.finditer(r'\D', 'uiae1iuae200')][-1])
打印:
8
编辑:为了使解决方案更加优雅,以便在所有类型的输入中正常运行,这里是更新的代码。 现在解决方案分为两行,因为如果列表为空则必须执行检查,然后它将打印-1否则索引值:
import re
arr = ['', '123', 'uiae1iuae200', 'uiae1iuae200aaaaaaaa']
for s in arr:
lst = [m.start() for m in re.finditer(r'\D', s)]
print(s, '-->', lst[-1] if len(lst) > 0 else None)
打印以下内容,如果未找到此索引,则打印None
而不是index:
--> None
123 --> None
uiae1iuae200 --> 8
uiae1iuae200aaaaaaaa --> 19
编辑2:正如OP在他的帖子中所述, \\d
只是我们开始的一个例子,因此我提出了一个解决方案来处理任何一般的正则表达式。 但是,如果这个问题必须只用\\d
来实现,那么我可以提供一个更好的解决方案,根本不需要列表理解,并且可以通过使用更好的正则表达式来查找最后出现的非数字字符来轻松编写并打印其位置。 我们可以使用.*(\\D)
正则表达式查找最后一次出现的非数字,并使用以下Python代码轻松打印其索引:
import re
arr = ['', '123', 'uiae1iuae200', 'uiae1iuae200aaaaaaaa']
for s in arr:
m = re.match(r'.*(\D)', s)
print(s, '-->', m.start(1) if m else None)
打印字符串及其对应的非数字字符索引,如果没有找到则为None
:
--> None
123 --> None
uiae1iuae200 --> 8
uiae1iuae200aaaaaaaa --> 19
正如您所看到的,此代码不需要使用任何列表理解,并且更好,因为它只需通过一个正则表达式调用来match
即可找到索引。
但是,如果OP确实意味着它使用任何一般的正则表达式模式编写,那么我将需要使用理解的上述代码。 我甚至可以把它写成一个函数,可以将正则表达式(如\\d
甚至是复杂的)作为参数,并动态生成传递正则表达式的负数并在代码中使用它。 如果确实需要,请告诉我。
对我而言,你只想要一个匹配给定模式的最后一个位置(在这种情况下不是一个数字模式)。
这与pythonic一样:
import re
string = 'uiae1iuae200'
pattern = r'[^0-9]'
match = re.match(fr'.*({pattern})', string)
print(match.end(1) - 1 if match else None)
输出:
8
或者与函数完全相同,并且有更多测试用例:
import re
def last_match(pattern, string):
match = re.match(fr'.*({pattern})', string)
return match.end(1) - 1 if match else None
cases = [(r'[^0-9]', 'uiae1iuae200'), (r'[^0-9]', '123a'), (r'[^0-9]', '123'), (r'[^abc]', 'abcabc1abc'), (r'[^1]', '11eea11')]
for pattern, string in cases:
print(f'{pattern}, {string}: {last_match(pattern, string)}')
输出:
[^0-9], uiae1iuae200: 8 [^0-9], 123a: 3 [^0-9], 123: None [^abc], abcabc1abc: 6 [^1], 11eea11: 4
这看起来不像Pythonic,因为它不是单行,它使用range(len(foo))
,但它非常简单,可能效率不高。
def last_match(pattern, string):
for i in range(1, len(string) + 1):
substring = string[-i:]
if re.match(pattern, substring):
return len(string) - i
我们的想法是迭代string
的后缀从最短到最长,并检查它是否与pattern
匹配。
由于我们从最后检查,我们确信我们遇到的匹配模式的第一个子串是最后一个。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.