Как найти кратчайшее совпадающее совпадение с использованием регулярных выражений?
Я все еще относительно новичок в регулярном выражении. Я пытаюсь найти кратчайшую строку текста, которая соответствует определенному шаблону, но у меня возникают проблемы, если самый короткий шаблон является подстрокой большего совпадения. Например:
import re
string = "A|B|A|B|C|D|E|F|G"
my_pattern = 'a.*?b.*?c'
my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
matches = my_regex.findall(string)
for match in matches:
print match
Отпечатки:
A|B|A|B|C
но я бы хотел, чтобы он возвращался:
A|B|C
Есть ли способ сделать это без необходимости перебирать каждое соответствие, чтобы увидеть, содержит ли он подстроку, которая соответствует?
Ответы
Ответ 1
В отличие от большинства других ответов здесь это можно сделать в одном регулярном выражении, используя положительное утверждение lookback с группа захвата:
>>> my_pattern = '(?=(a.*?b.*?c))'
>>> my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
>>> matches = my_regex.findall(string)
>>> print min(matches, key=len)
A|B|C
findall()
вернет все возможные совпадения, поэтому вам понадобится min()
, чтобы получить самый короткий.
Как это работает:
- Мы не сопоставляем текст в этом регулярном выражении, просто позиции в строке (которую движок регулярного выражения выполняет во время попытки совпадения).
- В каждой позиции двигатель регулярных выражений ожидает, будет ли ваше регулярное выражение соответствовать этой позиции.
- Если это так, он будет захвачен группой захвата.
- Если нет, это не произойдет.
- В любом случае, двигатель регулярных выражений затем продвигается вперед по одному символу и повторяет процесс до конца строки.
- Поскольку утверждение lookahead не потребляет никаких символов, будут найдены все совпадающие совпадения.
Ответ 2
Нет. Perl возвращает самое длинное, самое левое совпадение, подчиняясь вашим не жадным квантификаторам. Боюсь, вам придется зацикливаться.
Изменить: Да, я понимаю, что я сказал Perl выше, но я считаю, что это верно для Python.
Ответ 3
Другое решение для регулярных выражений; он находит только последнее вхождение. * a. * b. * c:
my_pattern = 'a(?!.*a.*b.*c).*b[^c]*c'
a(?!.*a.*?b.*?c)
гарантирует, что нет 'a.*?b.*?c'
после первого 'A'
строки, такие как A | A | B | C или | B | A | B | C или | B | C | A | B | C в результатах исключаются
b[^c]*c
гарантирует, что после 'B' существует только один 'C'
строки, подобные A | B | C | B | C или | B | C | C в результатах, удаляются
Итак, у вас есть наименьшее совпадение 'a.*?b.*?c'
Ответ 4
Механизм regex начинает поиск с начала строки до тех пор, пока не найдет совпадение, а затем выйдет. Таким образом, если он находит совпадение до того, как он даже рассмотрит меньший, вам не удастся заставить его рассмотреть более поздние совпадения в одном и том же прогоне - вам придется повторно запустить регулярное выражение на подстроках.
Установка глобального флага и выбор кратчайшей согласованной строки не помогут, поскольку это видно из вашего примера - более короткое совпадение может быть подстрокой другого соответствия (или частично включенным в него). Я считаю, что вам придется начинать последующие поиски из (1 + индекс предыдущего совпадения) и продолжать так.
Ответ 5
Я не думаю, что эта задача может быть выполнена одним регулярным выражением. У меня нет доказательств того, что это так, но есть много вещей, которые нельзя сделать с регулярными выражениями, и я ожидал, что эта проблема будет одной из них. Некоторые хорошие примеры ограничений регулярных выражений приведены в этом сообщении в блоге.
Ответ 6
Это может быть полезным приложением sexegers. Согласование регулярных выражений смещается в сторону самого длинного, самого левого выбора. Использование не-жадных кванторов, таких как .*?
юбки, самая длинная часть, и реверсирование как ввода, так и шаблона может приближаться к семантике левого соответствия.
Рассмотрим следующую программу, которая выводит A|B|C
по желанию:
#! /usr/bin/env python
import re
string = "A|B|A|B|C|D|E|F|G"
my_pattern = 'c.*?b.*?a'
my_regex = re.compile(my_pattern, re.DOTALL|re.IGNORECASE)
matches = my_regex.findall(string[::-1])
for match in matches:
print match[::-1]
Другой способ - сделать более строгий шаблон. Предположим, вы не хотите разрешать повторения уже увиденных символов:
my_pattern = 'a[^a]*?b[^ab]*?c'
Ваш пример является обобщенным и надуманным, но если бы мы лучше поняли, с какими материалами вы работаете, мы могли бы предложить более полезные и полезные предложения.
Ответ 7
Возможно, вы сможете написать регулярное выражение таким образом, чтобы оно не содержало меньших совпадений.
Для вашего регулярного выражения:
a.*?b.*?c
Я думаю, вы можете написать это:
a[^ab]*b[^c]*c
Это сложно сделать правильно, и я не вижу более общего или более явно правильного способа сделать это. Раньше я предлагал отрицательное утверждение, но я не вижу способа сделать эту работу.
Ответ 8
Цикл Python для поиска кратчайшего соответствия, путем грубой силы, проверяющей каждую подстроку слева направо, набираем кратчайший:
shortest = None
for i in range(len(string)):
m = my_regex.match(string[i:])
if m:
mstr = m.group()
if shortest is None or len(mstr) < len(shortest):
shortest = mstr
print shortest
Еще один цикл, на этот раз позволяющий re.findall выполнять тяжелую работу по поиску всех возможных совпадений, тогда тестирование грубой силы каждый матч справа налево ищет более короткую подстроку:
# find all matches using findall
matches = my_regex.findall(string)
# for each match, try to match right-hand substrings
shortest = None
for m in matches:
for i in range(-1,-len(m),-1):
mstr = m[i:]
if my_regex.match(mstr):
break
else:
mstr = m
if shortest is None or len(mstr) < len(shortest):
shortest = mstr
print shortest
Ответ 9
Нет, в механизме регулярных выражений Python нет.
Мой выбор для пользовательской функции:
import re, itertools
# directly from itertools recipes
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = itertools.tee(iterable)
for elem in b:
break
return itertools.izip(a, b)
def find_matches(rex, text):
"Find all matches, even overlapping ones"
matches= list(rex.finditer(text))
# first produce typical matches
for match in matches:
yield match.group(0)
# next, run it for any patterns included in matches
for match1, match2 in pairwise(matches):
subtext= text[match1.start()+1:match2.end()+1]
for result in find_matches(rex, subtext):
yield result
# also test the last match, if there was at least one
if matches:
subtext= text[matches[-1].start()+1:matches[-1].end()+1]
# perhaps the previous "matches[-1].end()+1" can be omitted
for result in find_matches(rex, subtext):
yield result
def shortest_match(rex, text):
"Find the shortest match"
return min(find_matches(rex, text), key=len)
if __name__ == "__main__":
pattern= re.compile('a.*?b.*?c', re.I)
searched_text= "A|B|A|B|C|D|E|F|G"
print (shortest_match(pattern, searched_text))