查找与负正则表达式匹配的字符串中最后位置的 Pythonic 方法
Pythonic way to find the last position in a string matching a negative regex
在 Python 中,我尝试在任意字符串中找到与给定模式匹配的最后位置,该模式被指定为负字符集正则表达式模式。例如,字符串 uiae1iuae200
和 not 的模式是一个数字(Python 中的正则表达式模式将是 [^0-9]
),结果我需要'8'('200'之前的最后一个'e')。
What is the most pythonic way to achieve this?
因为在 Python 文档中快速找到方法文档和最适合的方法有点棘手(由于方法文档位于相应页面中间的某个位置,例如 re.search()
在 re page) 中,我很快发现自己的最佳方式是使用 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()
优于 .start()
,但 re.search()
似乎将结果分成几组,为此我没有很快找到一种不麻烦的方法来应用它到最后匹配的组。在不指定组的情况下,.start()
、.end()
等似乎总是匹配第一个组,该组没有上次匹配的位置信息。但是,selecting 该组似乎首先需要将 return 值暂时保存在一个变量中(这会阻止整齐的单行),因为我需要访问关于 selecting 最后一组,然后从这组到 select .end()
。
你的 pythonic 解决方案是什么?与拥有最优化的运行时相比,我更看重 pythonic。
更新
该解决方案在极端情况下也应该起作用,例如 123
(没有与正则表达式匹配的位置)、空字符串等。它不应该崩溃,例如因为 selecting 空列表的最后一个索引。然而,即使是我在上面的问题中丑陋的回答也需要不止一行,我猜一个一行可能是不可能的(仅仅是因为需要检查 [=13= 的 return 值] 或 re.finditer()
处理它之前)。出于这个原因,我会接受这个答案的 pythonic 多行解决方案。
这看起来不像 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
.
由于我们从末尾开始检查,我们可以肯定地知道我们遇到的第一个与模式匹配的子字符串是最后一个。
您可以使用 re.finditer
从列表中提取所有匹配项的开始位置和 return 最后一个匹配项。试试这个 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
而不是索引:
--> None
123 --> None
uiae1iuae200 --> 8
uiae1iuae200aaaaaaaa --> 19
编辑 2:
正如 OP 在他的 post 中所述,\d
只是我们开始的一个例子,因此我想出了一个解决方案来处理任何通用的正则表达式。但是,如果这个问题真的只能用 \d
来解决,那么我可以给出一个更好的解决方案,它根本不需要列表理解,并且可以通过使用更好的正则表达式找到最后一次出现的非-digit 字符并打印其位置。我们可以使用 .*(\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)
Output:
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)}')
Output:
[^0-9], uiae1iuae200: 8
[^0-9], 123a: 3
[^0-9], 123: None
[^abc], abcabc1abc: 6
[^1], 11eea11: 4
在 Python 中,我尝试在任意字符串中找到与给定模式匹配的最后位置,该模式被指定为负字符集正则表达式模式。例如,字符串 uiae1iuae200
和 not 的模式是一个数字(Python 中的正则表达式模式将是 [^0-9]
),结果我需要'8'('200'之前的最后一个'e')。
What is the most pythonic way to achieve this?
因为在 Python 文档中快速找到方法文档和最适合的方法有点棘手(由于方法文档位于相应页面中间的某个位置,例如 re.search()
在 re page) 中,我很快发现自己的最佳方式是使用 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()
优于 .start()
,但 re.search()
似乎将结果分成几组,为此我没有很快找到一种不麻烦的方法来应用它到最后匹配的组。在不指定组的情况下,.start()
、.end()
等似乎总是匹配第一个组,该组没有上次匹配的位置信息。但是,selecting 该组似乎首先需要将 return 值暂时保存在一个变量中(这会阻止整齐的单行),因为我需要访问关于 selecting 最后一组,然后从这组到 select .end()
。
你的 pythonic 解决方案是什么?与拥有最优化的运行时相比,我更看重 pythonic。
更新
该解决方案在极端情况下也应该起作用,例如 123
(没有与正则表达式匹配的位置)、空字符串等。它不应该崩溃,例如因为 selecting 空列表的最后一个索引。然而,即使是我在上面的问题中丑陋的回答也需要不止一行,我猜一个一行可能是不可能的(仅仅是因为需要检查 [=13= 的 return 值] 或 re.finditer()
处理它之前)。出于这个原因,我会接受这个答案的 pythonic 多行解决方案。
这看起来不像 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
.
由于我们从末尾开始检查,我们可以肯定地知道我们遇到的第一个与模式匹配的子字符串是最后一个。
您可以使用 re.finditer
从列表中提取所有匹配项的开始位置和 return 最后一个匹配项。试试这个 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
而不是索引:
--> None
123 --> None
uiae1iuae200 --> 8
uiae1iuae200aaaaaaaa --> 19
编辑 2:
正如 OP 在他的 post 中所述,\d
只是我们开始的一个例子,因此我想出了一个解决方案来处理任何通用的正则表达式。但是,如果这个问题真的只能用 \d
来解决,那么我可以给出一个更好的解决方案,它根本不需要列表理解,并且可以通过使用更好的正则表达式找到最后一次出现的非-digit 字符并打印其位置。我们可以使用 .*(\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)
Output:
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)}')
Output:
[^0-9], uiae1iuae200: 8 [^0-9], 123a: 3 [^0-9], 123: None [^abc], abcabc1abc: 6 [^1], 11eea11: 4