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.

Поскольку мы проверяем с конца, мы точно знаем, что первая встречаемая подстрока, которая соответствует шаблону, является последней.