Pythonic способ найти последнюю позицию в строке, соответствующей отрицательному регулярному выражению
В Python я пытаюсь найти последнюю позицию в произвольной строке, которая соответствует заданному шаблону, который указан как шаблон регулярного выражения отрицательного набора символов. Например, со строкой uiae1iuae200
и шаблоном, который не является числом (шаблон регулярного выражения в Python для этого будет [^0-9]
), мне понадобится '8' (последний 'e' перед '200') ) в результате.
Какой самый питонный способ добиться этого?
Так как немного сложно быстро найти документацию по методу и наиболее подходящий метод для чего-либо в документации по Python (поскольку документы по методам находятся где-то посередине соответствующей страницы, например, re.search()
на странице re), лучшим я быстро обнаружил, что использую re.search()
- но текущая форма просто должна быть неоптимальным способом сделать это:
import re
string = 'uiae1iuae200' # the string to investigate
len(string) - re.search(r'[^0-9]', string[::-1]).start()
Я не удовлетворен этим по двум причинам: - а) мне нужно перевернуть string
перед использованием ее с [::-1]
, и - б) мне также нужно перевернуть результирующую позицию (вычитая ее из len(string)
из-за того, что перевернул строку раньше.
re.search()
быть лучшие способы для этого, вероятно, даже с результатом re.search()
.
Мне известно о re.search(...).end()
поверх .start()
, но re.search()
кажется, разбивает результаты на группы, для которых я не быстро нашел не слишком громоздкий способ применения это до последней подобранной группы. Без указания группы .start()
, .end()
и т.д., Кажется, всегда соответствуют первой группе, которая не имеет информации о положении о последнем совпадении. Однако выбор группы, по-видимому, сначала требует, чтобы возвращаемое значение было временно сохранено в переменной (что предотвращает аккуратные однострочные), так как мне нужно было бы получить доступ как к информации о выборе последней группы, так и к выбору .end()
из этой группы.
Какое ваше питоническое решение для этого? Я бы оценил быть питонным больше, чем иметь наиболее оптимизированное время выполнения.
Обновить
Решение должно быть функциональным и в угловых случаях, таких как 123
(нет позиции, соответствующей регулярному выражению), пустая строка и т.д. Оно не должно падать, например, из-за выбора последнего индекса пустого списка. Тем не менее, поскольку даже моему уродливому ответу на этот вопрос в приведенном выше вопросе потребуется более одной строки для этого, я предполагаю, что однострочный может быть невозможным для этого (просто потому, что нужно проверить возвращаемое значение re.search()
или re.finditer()
перед обработкой). По этой причине я приму питонские многострочные решения этого ответа.
Ответы
Ответ 1
Мне кажется, что вы просто хотите, чтобы последняя позиция соответствовала заданному шаблону (в данном случае это не шаблон номера).
Это настолько же питонно, насколько это возможно:
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
Ответ 2
Вы можете использовать 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
вместо индекса:
--> 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)
Печатает строку и соответствующий ей индекс нецифрового символа char и None
если не найдено:
--> None
123 --> None
uiae1iuae200 --> 8
uiae1iuae200aaaaaaaa --> 19
И, как вы можете видеть, этот код не должен использовать какое-либо понимание списка, и он лучше, так как он может просто найти индекс одним match
регулярным выражением.
Но если OP действительно подразумевал, что он должен быть написан с использованием любого общего шаблона регулярных выражений, то мой приведенный выше код, использующий понимание, будет необходим. Я даже могу написать его как функцию, которая может принимать регулярное выражение (например, \d
или даже сложное) в качестве аргумента и динамически генерировать отрицание переданного регулярного выражения и использовать его в коде. Дайте мне знать, если это действительно необходимо.
Ответ 3
Это не выглядит 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
.
Поскольку мы проверяем с конца, мы точно знаем, что первая встречаемая подстрока, которая соответствует шаблону, является последней.