Pythonic способ найти последнюю позицию в строке, соответствующей отрицательному регулярному выражению - PullRequest
12 голосов
/ 28 апреля 2019

В Python я пытаюсь найти последнюю позицию в произвольной строке, которая соответствует заданному шаблону, который указан как шаблон регулярного выражения отрицательного набора символов.Например, со строкой uiae1iuae200 и шаблоном , а не , представляющим собой число (для регулярного выражения в Python это будет [^0-9]), мне понадобится '8' (последнее 'e')до '200') как результат.

Какой самый питонский способ добиться этого?

Поскольку немного сложно быстро найти документацию по методу и найти подходящий метод для чего-то в документации по 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()

Я не удовлетворен этим по двум причинам: - а) мне нужно изменить string перед использованием его с [::-1], и - б) я такженеобходимо изменить полученную позицию (вычесть ее из len(string) из-за того, что она перевернула строку раньше.

Должны быть лучшие способы для этого, вероятно, даже с результатом re.search().

Мне известно о re.search(...).end() сверх .start(), но re.search(), кажется, разбивает результаты на группы, для которых я не нашел быстрого способа применить его к последней сопоставленной группе без указания tГруппа, .start(), .end() и т. д., кажется, всегда совпадает с первой группой, в которой нет информации о положении в последнем матче.Тем не менее, выбор группы, по-видимому, сначала требует, чтобы возвращаемое значение было временно сохранено в переменной (что предотвращает аккуратные однострочные), так как мне нужно было бы получить доступ как к информации о выборе последней группы, так и к выбору .end()из этой группы.

Какое у вас питоническое решение?Я бы предпочел быть питоническим больше, чем иметь наиболее оптимизированное время выполнения.

Обновление

Решение должно быть функциональным и в угловых случаях, таких как 123 (никакой позиции, котораясоответствует регулярному выражению), пустой строке и т. д. Не должно произойти сбой, например, из-за выбора последнего индекса пустого списка.Тем не менее, поскольку даже моему уродливому ответу на вопрос, приведенному выше, потребуется для этого более одной строки, я предполагаю, что однострочный может быть невозможным для этого (просто потому, что нужно проверить возвращаемое значение re.search() или re.finditer() до того, каксправиться с этим).По этой причине я приму питонские многострочные решения этого ответа.

Ответы [ 3 ]

4 голосов
/ 28 апреля 2019

Вы можете использовать re.finditer, чтобы извлечь начальные позиции всех матчей и вернуть последнюю из списка. Попробуйте этот код Python:

import re
print([m.start(0) for m in re.finditer(r'\D', 'uiae1iuae200')][-1])

Печать:

8

Edit: Для того, чтобы сделать решение немного более элегантным для правильного поведения для всех видов входных данных, вот обновленный код. Теперь решение идет в две строки, так как проверка должна быть выполнена, если список пуст, тогда он напечатает -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) regex, чтобы найти последнее вхождение нецифрового и легко напечатать его индекс, используя следующий код 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 или даже сложное) в качестве аргумента и динамически генерировать отрицание переданного регулярного выражения и использовать его в коде. Дайте мне знать, если это действительно необходимо.

3 голосов
/ 28 апреля 2019

Мне кажется, что вы просто хотите, чтобы последняя позиция соответствовала заданному шаблону (в данном случае это не шаблон номера).
Это настолько же питонно, насколько это возможно:

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
0 голосов
/ 28 апреля 2019

Это не выглядит 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.

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

...