Комментарии 66
Мало картинок, мало примеров. И почти нет разумных задач.
К этому добавлю: если задачи все же есть, то к ним нет проверочных данных. Без них неопытному человеку самостоятельно очень трудно определить, верно ли решена задача.
Спасибо за проделанную работу.
а) python flavor;
б) русские буквы;
Плюс там есть классная отладка
https://regex101.com/ неплох. есть python, русские буквы
А разве «перевал» соответствует? Наверно «Перевалка». Или я не так понял описание?
Всё подробно и актуально, добавил в закладки
Если адрес вводит пользователь, то пусть вводит почти что угодно, лишь бы там была собака.
После которой, где-то идет хотябы одна точка
Решил я давеча моим школьникам дать задачек на регулярные выраженияВы «регулярки» в контексте теории автоматов (регулярные грамматики, DFA/NFA…) используете?
Интересно, зачем школьникам теория автоматов для использования регулярок?
Регулярки школьникам для обработки текста.
Точно так же как личный автомобиль для поездок на работу.
Изучение теории автоматов для использования регулярок сравнимо с изучением сопромата каждому автолюбителю.
Я на ваш вопрос ответил. Теперь ответьте на мой, пожалуйста. Зачем по вашему мнению школьникам теория автоматов для использования регулярок?
для обработки текстаВы, вероятно, подразумеваете эффективную обработку больших объёмов текста и по всей видимости, предлагаете сделать из каждого школьника как минимум (очень) эффективную секретаршу.
Зачем по вашему мнению школьникам теория автоматов для использования регулярок?Для использования — совершенно ни к чему.
И как по всей видимости, предлагаете сделать из каждого школьника как минимум эффективную секретаршу.
Я считаю правильным в первую очередь дать школьникам удобный инструмент для решения человекопонятных задач. А когда они его освоят на уровне пользователя, тогда и рассказывать как оно работает под капотом: теорию графов, теорию автоматов, написание компиляторов и все сопутствующие дисциплины. Иначе вся эта теория будет абстрактным конем в вакууме.
Добавление? делает их анти-жадными,
они захватывают минимально возможное число символов
Может не анти-жадными, а ленивыми? Ну или хотя бы не жадными. А то уж больно глаз режет анти-жадные
К слову на википедии довольно неплохая статья про основы Регулярных выражений.
Скажем, в bash я бы ре стал писать
rm <регулярка,_которая_в_питоне_делает_то,_что_нужно>
В блоке «Простые шаблоны, соответствующие позиции» рекомендовал бы разобраться с определениями «строка», «строчка», «вся строка», иначе присутствует неоднозначность. Ввести и разъяснить два определения: что такое «строка» и что такое «текст». Тогда всё становится очень просто и однозначно: \А
это начало текста, \Z
это конец текста. ^
/ $
— начало/конец текста ИЛИ строки и данное поведение управляется флагом мультилайн.
Тема с типами квантификаторов плохо раскрыта. По какой-то причине вы в примере, которым хотели пояснить жадность квантификаторов, написали пример с ограничением позиции (в начале слова).
А пример с жадными и ленивыми квантификаторами отлично поясняется на примере вложенных шаблонов, например вложенных кавычек типа:
текст1 «текст2» текст3 «текст4»
при поиске ".*" жадных, ".*?" ленивых и ".*+" сверхжадных квантификаторов разница сразу становится понятной
Есть ещё atomic groups, (?>…), это — полезная штука, хотя немного сложная для восприятия. Может быть, добавлю.
К задаче 15:
А чем вам индийские номера в примере не угодили, что вы их сразу fail? :) не +7 же единым жив человек.
Хочется еще посоветовать хороший тренажер для регулярок: regexcrossword.com
re.compile частично упомянут в «Прочие фичи».
re.compile добавляет фичу, связанную с указанием позиций в строке, на которые нужно смотреть. Без лишнего среза. Ещё в некоторых случаях немного ускорят работу, но не сильно, так как python кеширует регулярки.
re.match и re.compile в данном контексте вступает в противоречие с куском zen of python:
There should be one-- and preferably only one --obvious way to do it.
Поэтому не стал упоминать.
Берём 10 регулярок.
r'\b[a-z]+\b', # слова только из маленьких букв
r'\b[A-Z]\w+\b', # слова с заглавной
r'\b(\w{10})\b', # слова из 10 символов с сохранением
r'te\w*st', # Ищем тест
r'a\w*b\w*c', # a*b*c
r'\(([^)]*)\)', # (...) с сохранением
r'\W{3,}', # Длинные не-слова
r'[aeiouy]+', # Только гласные
r'(?:[aeiouy][bcdfghjklmnpqrstvwxz])+', # Читаем по слогам
r'[\s,.!?;]+', # Для сплит'а
Если берём 1000 текстов по 10000 символов и каждый послед. прогоняем по этим 10 regex:
100000 finditer runs total. 50.33 sec for raw VS 48.33 sec for compiled Raw regexp run: 0.000503 seconds per regexp, x0.960 faster Compiled regexp run: 0.000483 seconds per regexp, x1.041 faster
Если берём 10000 текстов по 1000 символов и каждый послед. прогоняем по этим 10 regex:
1000000 finditer runs total. 50.72 sec for raw VS 50.44 sec for compiled Raw regexp run: 5.07e-05 seconds per regexp, x0.994 faster Compiled regexp run: 5.04e-05 seconds per regexp, x1.006 faster
Если берём 100000 текстов по 100 символов и каждый послед. прогоняем по этим 10 regex:
10000000 finditer runs total. 89.23 sec for raw VS 74.75 sec for compiled Raw regexp run: 8.92e-06 seconds per regexp, x0.838 faster Compiled regexp run: 7.47e-06 seconds per regexp, x1.194 faster
Если берём 500000 текстов по 20 символов и каждый послед. прогоняем по этим 10 regex:
15000000 finditer runs total. 76.47 sec for raw VS 56.42 sec for compiled Raw regexp run: 5.1e-06 seconds per regexp, x0.738 faster Compiled regexp run: 3.76e-06 seconds per regexp, x1.355 faster
from time import perf_counter
import re
import random
from string import ascii_lowercase, ascii_uppercase
chars = ''.join(chr(i) for i in range(33, 127))
chars += ascii_uppercase * 1 + ascii_lowercase * 7
chars += ' ' * 30
NUM_RUNS = 10
NUM_TEXTS = 10000
TEXT_LENS = 1000
texts = []
for __ in range(NUM_TEXTS):
texts.append(''.join(random.choices(chars, k=TEXT_LENS)))
regexps = [
r'\b[a-z]+\b', # слова только из маленьких букв
r'\b[A-Z]\w+\b', # слова с заглавной
r'\b(\w{10})\b', # слова из 10 символов с сохранением
r'te\w*st', # Ищем тест
r'a\w*b\w*c', # a*b*c
r'\(([^)]*)\)', # (...) с сохранением
r'\W{3,}', # Длинные не-слова
r'[aeiouy]+', # Только гласные
r'(?:[aeiouy][bcdfghjklmnpqrstvwxz])+', # Читаем по слогам
r'[\s,.!?;]+', # Для сплит'а
]
def test_raw():
tot = 0
st = perf_counter()
for text in texts:
for regex in regexps:
tot += sum(1 for m in re.finditer(regex, text))
en = perf_counter()
print(f'{tot} matches found in {en-st:0.4} seconds (without compiling)')
return en-st
def test_compiled():
tot = 0
st = perf_counter()
regexps_compiled = [re.compile(r) for r in regexps]
for text in texts:
for regex in regexps_compiled:
tot += sum(1 for m in regex.finditer(text))
en = perf_counter()
print(f'{tot} matches found in {en-st:0.4} seconds (with compiling)')
return en-st
raw_durs = [test_raw() for __ in range(NUM_RUNS)]
compiled_durs = [test_compiled() for __ in range(NUM_RUNS)]
tot_runs = NUM_RUNS*NUM_TEXTS*len(regexps)
raw_per_regex = sum(raw_durs) / tot_runs
comp_per_regex = sum(compiled_durs) / tot_runs
print(f'{tot_runs} finditer runs total. {sum(raw_durs):.2f} sec for raw VS {sum(compiled_durs):.2f} sec for compiled')
print(f'Raw regexp run: {raw_per_regex:.3} seconds per regexp, x{comp_per_regex/raw_per_regex:.3f} faster')
print(f'Compiled regexp run: {comp_per_regex:.3} seconds per regexp, x{raw_per_regex/comp_per_regex:.3f} faster')
Огромное спасибо автору.
Хороший перевод + адаптация с примерами и иллюстрациями, отличная работа.
Но справедливости ради, все же современная документация питона покрывает очень много изложенного. Т.е. несколько странно читать
Например, re.split может добавлять тот кусок текста, по которому был разрез, в список частей. А в re.sub можно вместо шаблона для замены передать функцию. Это — реальные вещи, которые прямо очень нужны, но никто про это не пишет.
когда именно это описано в оффициальной документации в первых двух предложениях для этих функций
И да, в статье вообще нет ничего из «теории» такого, чего нет в документации. Документация у питона весьма приличная. И на английском вообще есть суперские ресурсы: www.regular-expressions.info и www.rexegg.com. На последнем так вообще есть такие штуки, что ого-го.
Но мне нужен был понятный последовательный cookbook с привязкой к питону на русском языке, в котором есть все «нужные» штуки.
Извините, не сочтите за наглость, а вы не думали выложить ее в PDF (раз уж у вас есть опыт перегонки из обычного html в html хабра))?
Тема непростая для понимания и не особо освещенная в рунете, хотя рекурсивные шаблоны появились в PCRE аж в 2000 году.
Интересная статейка на тему — www.rexegg.com/regex-recursion.html (поиск палиндромов прекрасный пример применения)
Скудная документация — perldoc.perl.org/perlretut.html#Recursive-patterns
Есть ли способ визуально разделить группы поиска?
# NEED MATCH IN THIS CASE
import re
print(re.fullmatch(r"\d\. \d{2}\. \d{3}", "1.12.123"))
мои мысли:
должен быть флаг, позволяющий интерпретировать пробел только через \s или [ ], но такого нет
можно конечно использовать конструкцию с квантификатором "( ){0}" но это забивает/усложняет сам паттерн(((
В python есть флаг re.X
/ re.VERBOSE
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start, then
(?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*) # numerator (possibly empty)
(?: # followed by
(?:/(?P<denom>\d+))? # an optional denominator
| # or
(?:\.(?P<decimal>\d*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+))? # and optional exponent
)
\s*\Z # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE)
m = _RATIONAL_FORMAT.match(numerator)
numerator = int(m.group('num') or '0')
denom = m.group('denom')
decimal = m.group('decimal')
exp = m.group('exp')
Тем самым как шаблон для регулярки
'\\\\par'
означает просто текст\par
Кажется, что тут подразумевалось \\par
Регулярные выражения в Python от простого к сложному. Подробности, примеры, картинки, упражнения