Регулярні вирази Python від простого до складного. Подробиці, приклади, ілюстрації, вправи

Регулярні вирази Python від простого до складного

Вирішив я недавно моїм школярам дати завдань на регулярні вирази для вивчення. А до задачкам потрібна якась теорія. І став я шукати хороші тексти російською. П’ят стерпних знайшов, але все не те. Щось зім’яте, щось упущено. У цих текстів був не тільки фатальний недолік. Мало картинок, мало прикладів. І майже немає розумних завдань. Ну невже пошук IP-адреси — це найчастіша завдання для регулярних виразів? Ось і я думаю, що ні.
Про різницю (?:…) / (…) фіг знайдеш, а без цього знання в деяких випадках можна тільки страждати.

Плюс у пітоні є чимало регулярних булочок. Наприклад, re.split може додавати той шматок тексту, за яким був розріз, в список частин. А в re.sub можна замість шаблону для заміни передати функцію. Це реальні речі, які прямо дуже потрібні, але ніхто про це не пише.
Так і народився цей досить многобуквенный матеріал з подробицями, тонкощами, картинками і завданнями.

Сподіваюся, вам вдасться з нього витягти що-небудь нове і корисне, навіть якщо ви вже в ладах з регулярками.
PS. Рішення завдань школярі здають в тестуючу систему, тому завдання оформлені в кілька формальному вигляді.

Зміст

Регулярні вирази Python від простого до складного;
Зміст;
Приклади регулярних виразів;
Сила і відповідальність;
Документація та посилання;
Основи синтаксису;
Шаблони, відповідні одному символу;
Квантифікатори (зазначення кількості повторень);
Жадібність в регулярках і межі знайденого шаблону;
Перетин підрядків;
Експерименти в пісочниці;
Регулярки в пітоні;
Приклад використання всіх основних функцій;
Тонкощі екранування у пітоні (‘\\\\foo’);
Використання додаткових прапорів у пітоні;
Написання і тестування регулярних виразів;
Завдання — 1;
Скобочные групи (?:…) і перерахування |;
Перерахування (операція «АБО»);
Скобочные групи (групування плюс кванторів);
Дужки плюс перерахування;
Ще приклади;
Завдання — 2;
Групувальні дужки (…) і match-об’єкти в пітоні;
Match-об’єкти;
Групувальні дужки (…);
Тонкощі з дужками і нумерацією груп.;
Групи і re.findall;
Групи і re.split;
Використання груп при замінах;
Заміна з обробкою шаблону функцією в пітоні;
Посилання на групи при пошуку;
Завдання — 3;
Шаблони, відповідні не конкретного тексту, а позиції;
Прості шаблони, відповідні позиції;
Складні шаблони, відповідні позиції (lookaround і Co);
lookaround на прикладі королів та імператорів Франції;
Завдання — 4;
Post scriptum;

Регулярний вираз — це рядок, що задає шаблон пошуку підрядків у тексті. Одним шаблоном може відповідати багато різних рядків. Термін «Регулярні вирази» є перекладом англійського словосполучення «Regular expressions». Переклад не дуже точно відображає зміст, правильніше було б «шаблонні вирази». Регулярний вираз, або коротко «регулярка», складається із звичайних символів і спеціальних командних послідовностей. Наприклад, d задає будь-яку цифру, а d+ — задає будь-яку послідовність з одного чи більш цифр. Робота з регулярками реалізована у всіх сучасних мовах програмування. Однак існує кілька «діалектів», тому функціонал регулярних виразів може відрізнятися від мови до мови. У деяких мовах програмування регулярками користуватися дуже зручно (наприклад, у пітоні), в деяких — не дуже (наприклад, в C++).

Приклади регулярних виразів

Регулярка
Її сенс
simple text У точності текст «simple text»
d{5} Послідовності з 5 цифр
d означає будь-яку цифру
{5} — рівно 5 разів
dd/dd/d{4} Дати в форматі ДД/ММ/РРРР
(та інші шматки, на них схожі, наприклад, 98/76/5432)
bw{3}b Слова в точності з трьох літер
b означає кордон слова
(з одного боку буква, а з іншого — ні)
w — будь-яка літера,
{3} — рівно три рази
[-+]?d+ Ціле число, наприклад, 7, +17, -42, 0013 (можливі провідні нулі)
[-+]? — або-або +, або пусто
d+ — послідовність з 1 або більше цифр
[-+]?(?:d+(?:.d*)?|.d+)(?:[eE][-+]?d+)? Дійсне число, можливо експоненційної запису
Наприклад, 0.2, +5.45, -.4, 6e23, -3.17 E-14.
См. нижче картинку.

Сила і відповідальність

Регулярні вирази, або коротко, регулярки — це дуже потужний інструмент. Але використовувати їх потрібно з розумом і обережністю, і тільки там, де вони дійсно приносять користь, а не шкоду. По-перше, погано написані регулярні вирази працюють повільно. По-друге, їх часто дуже складно читати, особливо якщо регулярка написана не особисто тобою п’ять хвилин тому. По-третє, дуже часто навіть невелика зміна завдання (того, що потрібно знайти) призводить до значного зміни виразу. Тому про регулярки часто говорять, що це write-only code (код, який тільки пишуть з нуля, але не читають і не правлять). А також жартують: Деякі люди, коли стикаються з проблемою, думають: «Я знаю, я вирішу її за допомогою регулярних виразів.» Тепер у них дві проблеми. Ось приклад write-only регулярки (для перевірки валідності адреси e-mail (не треба так робити!!!)):

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?:(?:25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|\[x01-x09x0bx0cx0e-x7f])+)])

А ось тут більш точна регулярка для перевірки коректності email адреси стандарту RFC822. Якщо раптом будете перевіряти email, то не робіть так!Якщо адреса вводить користувач, то нехай вводить майже що завгодно, лише б там була собака. Надійніше всього відправити туди лист і переконатися, що користувач може його отримати.

Документація та посилання

  • Оригінальна документація: https://docs.python.org/3/library/re.html;
  • Дуже докладний і грунтовний матеріал: https://www.regular-expressions.info/;
  • Різні складні трюки і тонкощі з прикладами: http://www.rexegg.com/;
  • Онлайн налагодження регулярок https://regex101.com (не забудьте поставити галочку Python у розділі FLAVOR зліва);
  • Онлайн візуалізація регулярок https://www.debuggex.com/ (не забудьте вибрати Python);
  • Могутній текстовий редактор Sublime text 3, в якому дуже зручний пошук за регуляркам;

Основи синтаксису

Будь-яка рядок (у якої немає символів .^$*+?{}[]|()сама по собі є регулярним виразом. Так, вираженню Хаха буде відповідати рядок “Хаха” і тільки вона. Регулярні вирази є регістрозалежними, тому рядок “хаха” (з маленької літери) вже не буде відповідати висловом вище. Подібно рядками в мові Python, регулярні вирази мають спецсимволи .^$*+?{}[]|(), які регулярках є керуючими конструкціями. Для написання їх просто як символів потрібно екранувати їх, для чого потрібно поставити перед ними знак . Так само, як і в пітоні, регулярні вирази вираз n відповідає кінцю рядка, а t — табуляції.

Шаблони, відповідні одному символу

У всіх прикладах нижче відповідності регулярному виразу виділяються бірюзовим кольором, підкресленням.

Шаблон
Опис
Приклад
Застосовуємо до тексту
. Один будь-який символ, крім нового рядка n. м. л. до молоко, малако,
Ім0л0коИхлеб
d Будь-яка цифра СУdd СУ35, СУ111, АЛСУ14
D Будь-який символ, крім цифри 926D123 926)123, 1926-1234
s Будь пробільний символ (пробіл, табуляція, кінець рядка і т. п.) борѕода бор ода, бор
ода
, борода
S Будь-який символ символ який не є пропуском S123 X123, я123, !123456, 1 + 123456
w Будь-яка буква (те, що може бути частиною слова), а також цифри і _ www Рік, f_3, qwert
W Будь-яка не-буква, цифра і не підкреслення сомW сом!, сом?
[..] Один з символів в дужках,
а також будь-який символ з діапазону a-b
[0-9][0-9A-Fa-f] 12, 1F, 4B
[^..] Будь-який символ, крім перерахованих <[^>]> <1>, <a>, <>>
d≈[0-9],
D≈[^0-9],
w≈[0-9a-zA-Z
а-яА-ЯеЕ],
s≈[ fnrtv]
Буква “е” не включається в загальний діапазон літер!
Взагалі кажучи, в d включається все, що в юникое позначено як «цифра», а в w — як буква. Ще багато всього!
[abc-], [-1] якщо потрібен мінус, його потрібно вказати останнім чи першим
[*[(+\]t] усередині дужок потрібно екранувати тільки ] і
b Початок або кінець слова (зліва порожньо чи не буква, праворуч буква і навпаки).
На відміну від попередніх відповідає позиції, а не символу
bвал вал, перевал, Перевалка
B Не межа слова: або і зліва, і справа букви,
і зліва, і справа НЕ букви
Ввал перевал, вал, Перевалка
ВвалB перевал, вал, Перевалка

Квантифікатори (зазначення кількості повторень)

Шаблон
Опис
Приклад
Застосовуємо до тексту
{n} Рівно n повторень d{4} 1, 12, 123, 1234, 12345
{m,n} Від m до n повторень включно d{2,4} 1, 12, 123, 1234, 12345
{m} Не менш m повторень d{3,} 1, 12, 123, 1234, 12345
{,n} Не більше n повторень d{,2} 1, 12, 123
? Нуль або одне входження, синонім {0,1} вали? вал, вали, валів
* Нуль або більше, синонім {0,} СУd* СУ, СУ1, СУ12, …
+ Одне або більше, синонім {1,} a)+ a), a)), a))), ba)])
*?
+?
??
{m,n}?
{,n}?
{m}?
За замовчуванням кванторів жадібні
захоплюють максимально можливе число символів.
Додавання ? робить їх ледачими,
вони захоплюють мінімально можливе число символів
(.*)
(.*?)
(a + b) * (c + d) * (e + f)
(a + b) * (c + d) * (e + f)

Жадібність в регулярках і межі знайденого шаблону

Як зазначено вище, за замовчуванням кванторів жадібні. Цей підхід вирішує дуже важливу проблему — проблему межі шаблону. Скажімо, шаблон d+ захоплює максимально можлива кількість цифр. Тому можна бути впевненим, що перед знайденим шаблоном йде не цифра, і після йде не цифра. Однак якщо в шаблоні є не жадібні частини (наприклад, явний текст), то текст може бути знайдена невдало. Наприклад, якщо ми хочемо знайти «слова», що починаються на СУ, після якої йдуть цифри, за допомогою регулярки СУd*, то ми знайдемо і неправильні шаблони:

ПАСУ13 СУ12, ЩОБ СУ6ОК В ТОК ВДАЛОСЯ.
У тих випадках, коли це важливо, умова на кордон шаблону потрібно обов’язково додавати в регулярку. Про те, як це можна робити, що буде далі.

Перетин підрядків

У звичайній ситуації регулярки дозволяють знайти лише непересічні шаблони. Разом з проблемою кордону слова це робить їх використання в деяких випадках більш складним. Наприклад, якщо ми будемо шукати e-mail адреси за допомогою неправильної регулярки w+@w+ (або навіть краще, [w'._+-]+@[w'._+-]+), то в невдалому разі знайдемо ось що:

foo@boo@goo@moo@roo@zoo
Тобто це з одного боку і не e-mail, а з іншого боку це не все підрядка виду текст-собака-текст, так як boo@goo і moo@roo пропущені.

Експерименти в пісочниці

Якщо ви вперше стикаєтеся з регулярними виразами, то краще всього спочатку спробувати пісочницю. Подивіться, як працюють прості шаблони і кванторів. Вирішіть наступні завдання цього тексту (можливо, до частини доведеться повернутися після наступної теорії):

  1. Знайдіть усі натуральні числа (можливо, оточені літерами);
  2. Знайдіть всі «слова», написані капсом (тобто строго заголовними), можливо усередині цих слів (аааБББввв);
  3. Знайдіть слова, в яких є російська буква, а коли-небудь за нею цифра;
  4. Знайдіть всі слова, що починаються з російської чи латинської великої літери (b — межа слова);
  5. Знайдіть слова, які починаються на голосну (b — межа слова);;
  6. Знайдіть усі натуральні числа, що не знаходяться всередині або на кордоні слова;
  7. Знайдіть рядки, в яких є символ * (. — це точно не кінець рядка!);
  8. Знайдіть рядки, в яких є відкриває і коли-небудь потім закриває дужки;
  9. Виділіть одним махом весь шматок змісту (в кінці прикладу, разом з тегами);
  10. Виділіть одним махом тільки текстову частину змісту, без тегів;
  11. Знайдіть порожні рядки;
Читайте також  Ще раз про рівні Tier

Регулярки в пітоні

Функції для роботи з регулярками живуть в модулі re. Основні функції:

Функція
Її сенс
re.search(pattern, string) Знайти в рядку string першу сходинку, відповідну під шаблон pattern;
re.fullmatch(pattern, string) Перевірити, чи підходить рядок string під шаблон pattern;
re.split(pattern, string, maxsplit=0) Аналог str.split(), тільки поділ відбувається з підрядками, що підходять під шаблон pattern;
re.findall(pattern, string) Знайти в рядку string всі непересічні шаблони pattern;
re.finditer(pattern, string) Ітератор всім непересічним шаблонами pattern у рядку string (видаються match-об’єкти);
re.sub(pattern, repl, string, count=0) Замінити в рядку string всі непересічні шаблони pattern на repl;

Приклад використання всіх основних функцій

re import 

match = re.search(r'ddDdd', r Телефон 123-12-12') 
print(match[0] if match else 'Not found') 
# -> 23-12 
match = re.search(r'ddDdd', r Телефон 1231212') 
print(match[0] if match else 'Not found') 
# -> Not found 

match = re.fullmatch(r'ddDdd', r'12-12') 
print('YES' if match else 'NO') 
# -> YES 
match = re.fullmatch(r'ddDdd', r Т. 12-12') 
print('YES' if match else 'NO') 
# -> NO 

print(re.split(r'W+', 'Де, скажіть мені, мої окуляри??!')) 
# -> ['Де', 'скажіть', 'мені', 'мої', 'окуляри', "] 

print(re.findall(r'dd.dd.d{4}', 
 r Ця рядок написана 19.01.2018, а могла б і 01.09.2017')) 
# -> ['19.01.2018', '01.09.2017'] 

for m in re.finditer(r'dd.dd.d{4}', r Ця рядок написана 19.01.2018, а могла б і 01.09.2017'): 
 print('Дата', m[0], 'починається з позиції', m.(start)) 
# -> Дата 19.01.2018 починається з позиції 20 
# -> Дата 01.09.2017 починається з позиції 45 

print(re.sub(r'dd.dd.d{4}', 
 r DD.MM.YYYY', 
 r Ця рядок написана 19.01.2018, а могла б і 01.09.2017')) 
# -> Цей рядок написана DD.MM.РРРР, а могла б і DD.MM.YYYY 

Тонкощі екранування у пітоні ('\\\\foo')

Так як символ в питоновских рядках також необхідно екранувати, то в результаті в шаблонах можуть виникати конструкції виду '\\par'. Перший слеш означає, що наступний за ним символ потрібно залишити «як є». Третій також. В результаті з точки зору пітона '\\' означає просто два слеша \. Тепер з точки зору движка регулярних виразів, перший слеш екранує другий. Тим самим як шаблон для регулярки '\\par' означає просто текст par. Для того, щоб не було таких нагромаджень слешів, відкриває перед лапками потрібно поставити символ r, що скаже пітона «не розглядай як екрануючий символ (крім випадків екранування відкриває лапки)». Відповідно можна буде писати r'\par'.

Використання додаткових прапорів у пітоні

Кожній з функцій, перерахованих вище, можна дати додатковий параметр flags, що дещо змінить режим роботи регулярок. В якості значення потрібно передати суму вибраних констант, ось вони:

Константа
Її сенс
re.ASCII За замовчуванням w, W, b, B, d, D, s, S відповідають
всі юнікодние символи з відповідною якістю.
Наприклад, d відповідають не тільки арабські цифри,
але ось такі: ٠١٢٣٤٥٦٧٨٩.
re.ASCII прискорює роботу,
якщо все відповідності лежать всередині ASCII.
re.IGNORECASE Не розрізняти великі і малі літери.
Працює повільніше, але іноді зручно
re.MULTILINE Спеціальні символи ^ і $ відповідають
початок і кінець кожного рядка
re.DOTALL За замовчуванням символ n кінця рядка не підходить під крапку.
З цим прапором точка — взагалі будь-який символ
re import 
print(re.findall(r'd+', '12 + ٦٧')) 
# -> ['12', '٦٧'] 
print(re.findall(r'w+', 'Привіт, світе!')) 
# -> ['Hello', 'світ'] 
print(re.findall(r'd+', '12 + ٦٧', flags=re.ASCII)) 
# -> ['12'] 
print(re.findall(r'w+', 'Hello, світ!', flags=re.ASCII)) 
# -> ['Hello'] 
print(re.findall(r'[уеыаоэяию]+', 'ОООО ааааа ррррр ЫЫЫЫ яяяя')) 
# -> ['ааааа', 'яяяя'] 
print(re.findall(r'[уеыаоэяию]+', 'ОООО ааааа ррррр ЫЫЫЫ яяяя', flags=re.IGNORECASE)) 
# -> ['ОООО', 'ааааа', 'ЫЫЫЫ', 'яяяя'] 

text = r""" 
Торт 
з вишней1 
вишней2 
""" 
print(re.findall(r Торт.з', text)) 
# -> [] 
print(re.findall(r Торт.з', text, flags=re.DOTALL)) 
# -> ['Тортпс'] 
print(re.findall(r вішw+', text, flags=re.MULTILINE)) 
# -> ['вишней1', 'вишней2'] 
print(re.findall(r'^вішw+', text, flags=re.MULTILINE)) 
# -> ['вишней2'] 

Написання і тестування регулярних виразів

Для написання і тестування регулярних виразів зручно використовувати сервіс https://regex101.com (не забудьте поставити галочку Python у розділі FLAVOR ліворуч) або текстовий редактор Sublime text 3.

Завдання — 1

Завдання 01. Реєстраційні знаки транспортних засобів

У Росії застосовуються реєстраційні знаки декількох видів.
Спільного в них те, що вони складаються з цифр і букв. Причому використовуються тільки 12 букв кирилиці, мають графічні відповідники у латинському алфавіті — А, В, Е, К, М, Н, О, Р, С, Т, У і Х.

У приватних легкових автомобілях номери — це буква, три цифри, дві літери, потім дві або три цифри з кодом регіону. У таксі — дві літери, три цифри, потім дві або три цифри з кодом регіону. Є також й інші види, але в цьому завданні вони не знадобляться.

Вам потрібно визначити, чи є послідовність букв коректним номером вказаних двох типів, і якщо є, то яким.

На вхід даються рядки, які претендують на те, щоб бути номером. Визначте тип номера. Букви в номерах — великі російські. Маленькі і англійські для простоти можна ігнорувати.

Введення
Висновок
С227НА777 
КУ22777 
Т22В7477 
М227К19У9 
 С227НА777 
Private 
Taxi 
Fail 
Fail 
Fail

Завдання 02. Кількість слівСлово — це послідовність з літер (або російських англійських), всередині якої можуть бути дефіси.
На вхід дається текст, порахуйте, скільки в ньому слів.
PS. Завдання вирішується в одну сходинку. Ніякі хитрі техніки, не згадані вище, не потрібні.

Введення
Висновок
Він --- сіро-буро-малинова редиска!! 
>>>:-> 
А не кіт. 
www.kot.ru
9

Завдання 03. Пошук e-mailов

Допустимий формат адреси e-mail регулюється стандартом RFC 5322.
Якщо говорити коротко, то e-mail складається з одного символу @ (at-символ або собака), тексту до собаки (Local-part) і тексту після собаки (Domain part). Взагалі в адресі може бути всякий свавілля (коротко можна прочитати про нього в вікіпедії). Досить дивні штуки можуть бути валідним адресою, наприклад:
"very.(),:;<>[]".VERY."very@\ "very".unusual"@[IPv6:2001:db8::1]
"()<>[]:,;@\"!#$%&'-/=?^_`{}| ~.a"@(comment)example
Але більшість поштових сервісів таке пекло і вакханалію не допускають. І ми теж не будемо 🙂

Будемо розглядати тільки адреси, ім’я яких складається з не більш, ніж 64 латинських букв, цифр і символів '._+-, а домен — не більше, ніж 255 латинських букв, цифр і символів .-. Ні Local-part, ні Domain part не може починатися або закінчуватися .+-, а ще в адресі не може бути більше однієї точки.
До речі, корисно знати, що частина імені після символу + ігнорується, тому можна використовувати синоніми своєї адреси (наприклад, shаshkоv+spam@179.ru і shаshkоv+vk@179.ru), для того, щоб спростити собі сортування пошти. (Правда не всі сайти дозволяють використовувати “+”, на жаль)

На вхід дається текст. Необхідно вивести всі e-mail адреси, які в ньому зустрічаються. У загальному вигляді завдання досить складна, тому у нас буде 3 обмеження:
дві точки всередині адреси не зустрічаються;
дві собаки всередині адреси не зустрічаються;
вважаємо, що e-mail може бути частиною «слова», тобто в boo@ya_ru ми бачимо адресу boo@ya, а в foo№boo@ya.ru бачимо boo@ya.ru.

PS. Зовсім не обов’язково робити все перевірки тільки регулярками. Регулярні вирази — це просто інструмент, який робить частину завдань простими. Не потрібно робити їх назад складними 🙂

Введення
Висновок
Іван Іванович! 
Потрібен відповідь на лист від ivanoff@ivan-chai.ru. 
Не забудьте поставити в копію 
serge'o-lupin@mail.ru - це важливо.
ivanoff@ivan-chai.ru 
serge'o-lupin@mail.ru
NO: foo.@ya.ru, foo@.ya.ru 
PARTLY: boo@ya_ru, -boo@ya.ru-, foo№boo@ya.ru
boo@ya 
boo@ya.ru 
boo@ya.ru 

Скобочные групи (?:...) і перерахування |

Перерахування (операція «АБО»)

Щоб перевірити, чи задовольняє рядок хоча б одному з шаблонів, можна скористатися аналогом оператора or, який записується з допомогою символу |. Так, деяка рядок підходить до регулярного виразу A|B тоді і тільки тоді, коли вона підходить хоча б до одного з регулярних виразів A або B. Наприклад, окремі овочі в тексті можна шукати за допомогою шаблону морковк|св[її]кл|картошк|редиск.

Скобочные групи (групування плюс кванторів)

Найчастіше шаблон складається з кількох повторюваних груп. Так, MAC-адресу мережного пристрою зазвичай записується як шість груп з двох шістнадцятирічних цифр, розділених символами - або :. Наприклад, 01:23:45:67:89:ab. Кожен окремий символ можна задати як [0-9a-fA-F], і можна весь шаблон записати так:
[0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}

Ситуація стає набагато складніше, коли кількість груп заздалегідь не зафіксовано.
Щоб вирішити цю проблему синтаксис регулярних виразів є угруповання (?:...). Можна писати круглі дужки без значків ?:, однак від цього угруповання значно змінюється зміст, регулярка починає працювати значно повільніше. Про це буде написано нижче. Отже, якщо REGEXP — шаблон, то (?:REGEXP) — еквівалентний йому шаблон. Різниця тільки в тому, що тепер до (?:REGEXP) можна застосовувати кванторів, вказуючи, скільки саме разів повинна повторитися група. Наприклад, шаблон для пошуку MAC-адреси, можна записати так:
[0-9a-fA-F]{2}(?:[:-][0-9a-fA-F]{2}){5}

Дужки плюс перерахування

Також дужки (?:...) дозволяють локалізувати частина шаблону, всередині якої відбувається перерахування. Наприклад, шаблон (?:він той) (?:йшов|плив) відповідає кожної з рядків «він йшов», «він плив», «йшов», «той плив», і є синонімом він йшов він плив|той йшов той плив.

Ще приклади

Шаблон
Застосовуємо до тексту
(?:wwdd)+ Є миг29а, ту154б. Деякі роблять навіть миг29ту154ил86.
(?:w+d+)+ Є миг29а, ту154б. Деякі роблять навіть миг29ту154ил86.
(?:+7/8)(?:-d{2,3}){4} +7-926-123-12-12, 8-926-123-12-12
(?:[Хх][аоеи]+)+ Гохахахахехо, ну хааахооохе, так хахахехохииии! Хам трамвайний.
b(?:[Хх][аоеи]+)+b Муха — хахахехо, ну хааахооохе, так хахахехохииии! Трамвайний Хам.

Завдання 2

Завдання 04. Заміна часу

Вовочка підготував одна дуже важлива лист, але скрізь вказав неправильний час.
Тому потрібно замінити всі входження часу на рядок (TBD). Час — це рядок виду HH:MM:SS або HH:MM, в якій HH — число від 00 до 23, а MM і SS — число від 00 до 59.

Читайте також  Як ми придумували набори дитячої технічної творчості
Введення
Висновок
Шановні! Якщо ви до 09:00 не повернете 
валіза, то вже в 09:00:01 я за себе не відповідаю. 
PS. З відношенням 25:50 все нормально!
Шановні! Якщо ви до (TBD) не повернете 
валіза, то вже в (TBD) я за себе не відповідаю. 
PS. З відношенням 25:50 все нормально!

Завдання 05. Дійсні числа в паскалі

Pascal requires that real constants have either a decimal point, or an exponent (starting with the letter e or E, and officially called a scale factor), or both, in addition to the usual collection of decimal digits. If a decimal point is included it must have at least one decimal digit on each side of it. As expected a sign (+ або -) may precede the entire number, or the exponent, or both. Exponents may not include fractional digits. Чарівна історія пусті клітинки may precede or follow the real constant, but they may not be embedded within it. Note that the Pascal syntax rules for real constants make no assumptions about the range of real values, and neither does this problem. Your task in this problem is to identify legal Pascal real constants.

Введення
Висновок
1.2 
 1. 
 1.0 e-55 
 e-12 
 6.5 E 
 1e-12 
 +4.1234567890 E-99999 
 7.6 e+12.5 
 99 
1.2 is legal. 
1. is illegal. 
1.0 e-55 is legal. 
e-12 is illegal. 
6.5 E is illegal. 
1e-12 is legal. 
+4.1234567890 E-99999 is legal. 
7.6 e+12.5 is illegal. 
99 is illegal. 

Завдання 06. Абревіатури

Володимир влаштувався на роботу в одне дуже важливе місце. І в першому ж документі він нічого не зрозумів,
там були суцільні ФГУП НДЦ ГИДГЕО, ФГТУ ЧШУ АПК і т. п. Тоді він вирішив зібрати всі абревіатури, щоб потім знайти їх розшифровки на http://sokr.ru/. Допоможіть йому.

Будемо вважати абревіатурою слова лише із заголовних букв (як мінімум, з двох). Якщо кілька таких слів розділені пробілами, то вони
вважаються однією абревіатурою.

Введення
Висновок
Це курс інформатики відповідає ФГОС і ПООП, 
це підтверджено ФДМ ФНЦ НИИСИ РАН
ФГОС 
ПООП 
ФДМ ФНЦ НИИСИ РАН

Групувальні дужки (...) і match-об’єкти в пітоні

Match-об’єкти

Якщо функції re.search, re.fullmatch не знаходять відповідність шаблону в рядку, то вони повертають None, функція re.finditer не видає нічого. Однак якщо відповідність знайдено, повертається match-об’єкт. Ця штука містить в собі купу корисної інформації про відповідність шаблону. Повний набір атрибутів можна подивитися в документації, а тут наведемо найкорисніше.

Метод
Опис
Приклад
match[0],
match.group()
Рядок, що відповідає шаблону match = re.search(r'w+', r'$$ What??')
match[0] # -> 'What'
match.start() Індекс вихідної рядку, починаючи з якого йде знайдена підрядок match = re.search(r'w+', r'$$ What??')
match.start() # -> 3
match.end() Індекс вихідної рядку, який слід відразу за знайденої підрядок match = re.search(r'w+', r'$$ What??')
match.end() # -> 7

Групувальні дужки (...)

Якщо шаблон регулярного виразу зустрічаються дужки (...) без ?:, то вони стають группирующими. У match-об’єкт, який повертають re.search, re.fullmatch і re.finditer, по кожній такій групі можна отримати ту ж інформацію, що і по всьому шаблоном. А саме частина підрядка, яка відповідає (...), а також індекси початку і закінчення початкової рядку. Досить часто це буває корисно.

re import 
pattern = r's*([А-Яа-яЕе]+)(d+)s*' 
string = r'--- Опять45 ---' 
match = re.search(pattern, string) 
print(f'Знайдена підрядок >{match[0]}< з позиції {match.start(0)} до {match.end(0)}') 
print(f'Група букв >{match[1]}< з позиції {match.start(1)} до {match.end(1)}') 
print(f'Група цифр >{match[2]}< з позиції {match.start(2)} до {match.end(2)}') 
### 
# -> Знайдена підрядок > Опять45 < з позиції 3 до 16 
# -> Група букв >Знову< з позиції 6 до 11 
# -> Група цифр >45< з позиції 11 до 13 


Тонкощі з дужками і нумерацією груп.

Якщо до группирующим дужкам застосований квантіфікатор (тобто вказано кількість повторень), то підгрупа в match-об’єкті буде створена тільки для останнього відповідності. Наприклад, якщо б у прикладі вище кванторів були зовні від дужок 's*([А-Яа-яЕе])+(d+s*', то висновок був би таким:

# -> Знайдена підрядок > Опять45 < з позиції 3 до 16 
# -> Група букв >ь< з позиції 10 до 11 
# -> Група цифр >5< з позиції 12 до 13 

Всередині групують дужок можуть бути й інші групувальні дужки. У цьому випадку їх нумерація здійснюється у відповідності з номером появи відкриваючої дужки з шаблоні.

re import 
pattern = r'((d)(d))((d)(d))' 
string = r'123456789' 
match = re.search(pattern, string) 
print(f'Знайдена підрядок >{match[0]}< з позиції {match.start(0)} до {match.end(0)}') 
for i in range(1, 7): 
 print(f'Група №{i} >{match[i]}< з позиції {match.start(i)} до {match.end(i)}') 
### 
# -> Знайдена підрядок >1234< з позиції 0 до 4 
# -> Група №1 >12< з позиції 0 до 2 
# -> Група №2 >1< з позиції 0 до 1 
# -> Група №3 >2< з позиції 1 до 2 
# -> Група №4 >34< з позиції 2 до 4 
# -> Група №5 >3< з позиції 2 до 3 
# -> Група №6 >4< з позиції 3 до 4 

Групи і re.findall

Якщо в шаблоні є групувальні дужки, то замість списку знайдених підрядків буде повернутий список кортежів, в кожному з яких тільки відповідність кожній групі. Це не завжди відбувається за планом, тому зазвичай потрібно використовувати негруппирующие дужки (?:...).

re import 
print(re.findall(r'([a-z]+)(d*)', r foo3, im12, go, 24buz42')) 
# -> [('foo', '3'), ('im', '12'), ('go', "), ('buz', '42')] 

Групи і re.split

Якщо в шаблоні немає групують дужок, то re.split працює дуже схожим чином на str.split. А ось якщо групувальні дужки в шаблоні є, то між кожними розрізаними рядками будуть все відповідності кожній з підгруп.

re import 
print(re.split(r'(s*)([+*/-])(s*)', r'12 + 13*15 - 6')) 
# -> ['12', ' ', '+', ' ', '13', ", '*', ", '15', ' ', '-', ' ', '6'] 

В деяких ситуація ця можливість буває надзвичайно зручна! Наприклад, досить з попереднього прикладу прибрати зайві групи, і користь відразу стані очевидна!

re import 
print(re.split(r's*([+*/-])s*', r'12 + 13*15 - 6')) 
# -> ['12', '+', '13', '*', '15', '-', '6'] 

Використання груп при замінах

Використання груп додає заміні (re.sub, працює не тільки у пітоні, а майже скрізь) дуже зручну можливість: в шаблоні для заміни можна посилатися на відповідну групу за допомогою 1, 2, 3, .... Наприклад, якщо потрібно дати з незручного формату ДД/ММ/РРРР перевести в зручний ДД.ММ.РРРР, то можна використовувати таку регулярку:

re import 
text = "We arrive on 03/25/2018. So you are welcome after 04/01/2018." 
print(re.sub(r'(dd)/(dd)/(d{4})', r'2.1.3', text)) 
# -> We arrive on 25.03.2018. So you are welcome after 01.04.2018. 

Якщо груп більше 9, то можна посилатися на них за допомогою конструкції виду g<12>.

Заміна з обробкою шаблону функцією в пітоні

Ще одна питоновская фіча для регулярних виразів: функції re.sub замість тексту для заміни можна передати функцію, яка буде отримувати на вхід match-об’єкт і повинна повертати рядок, на яку і буде проведена заміна. Це дозволяє не писати пекло в шаблоні для заміни, а використовувати зручну функцію. Наприклад, «зацензурим» всі слова, що починаються на букву «Х»:

re import 
def repl(m): 
 return '>censored(' + str(len(m[0])) + ')<' 
text = "Деякі хороші слова підозрілі: хор, хоровод, хороводоводовед." 
print(re.sub(r'b[xxxx]w*', repl, text)) 
# -> Деякі >censored(7)< підозрілі слова: >censored(3)<, >censored(7)<, >censored(15)<. 

Посилання на групи при пошуку

За допомогою 1, 2, 3, ... і g<12> можна посилатися на знайдену групу і при пошуку. Необхідність у цьому зустрічається досить рідко, але це буває корисно при обробці простих xml і html.

Тільки пообіцяйте, що не будете парсити складний xml і тим більше html за допомогою регулярок! Регулярні вирази для цього не підходять. Використовуйте інші інструменти. Кожен раз, коли недосвідчений програміст парсити html регулярками, у світі помирає кошеня. Якщо здається «Так тут дуже простий html, напишу регулярку», то відразу згадуйте жарт про дві проблеми. Не потрібно намагатися парсити html регулярками, навіть Петро Мітрічев не зможе це зробити в загальному випадку 🙂 Використання регулярних виразів при парсингу html подібно залатування гумового човна шилом. Закон Мерфі для парсингу html і xml за допомогою регулярок говорить: парсинг html і xml регулярками іноді працює, але в точності до того моменту, коли правильність результату буде дуже важлива.

Використовуйте lxml і beautiful soup.

re import 
text = "SPAM <foo>Here we can <boo>find</boo> something interesting</foo> SPAM" 
print(re.search(r'<(w+?)>.*?</1>', text)[0]) 
# -> <foo>Here we can <boo>find</boo> something interesting</foo> 
text = "SPAM <foo>Here we can <foo>find</foo> OH, NO MATCH HERE!</foo> SPAM" 
print(re.search(r'<(w+?)>.*?</1>', text)[0]) 
# -> <foo>Here we can <foo>find</foo> 

Завдання 3

Завдання 07. Шифровка

Володимиру знадобилося терміново заплутати фінансову документацію. Але так, щоб це було оборотно.
Він не придумав нічого кращого, ніж замінити кожне ціле число (послідовність цифр) на його куб. Допоможіть йому.

Введення
Висновок
Було придбано 12 одиниць техніки 
за 410.37 рублів.
Було закуплено 1728 одиниць техніки 
за 68921000.50653 рублів.

Завдання 08. То акростих, то акронім, то апроним Акростих — осмислений текст, складений з початкових літер кожного рядка вірша.
Акронім — вид абревіатури, утвореної початковими звуками (напр. НАТО, вуз, НАСА, ТАСС), яку можна вимовити злито (на відміну від абревіатури, яку вимовляють «по буквах», наприклад: КДБ — «ка-ге-бе»).
На вхід дається текст. Виведіть разом перші літери кожного слова. Букви необхідно виводити великими.
Цю задачу можна вирішити в один рядок.

Введення
Висновок
Московський державний інститут міжнародних відносин
МДІМВ
мікоян авіацію забезпечив алкоголем, 
народ задоволений роботою авіаконструктора
МАСАНДРА

Завдання 09. Хайку

Хайку — жанр традиційної японської ліричної поезії століття, відомий з XIV століття.
Оригінальне японське хайку складається з 17 складів, складових один стовпець ієрогліфів. Особливими розділовими словами — кирэдзи — текст хайку ділиться на частини з 5, 7 і знову 5 складів. При перекладі хайку на західні мови традиційно замість розділового слова використовую розрив рядка і, таким чином, хайку записуються як тривірші.

Читайте також  Пишемо гру на LWJGL

Перед вами тривірші, які претендують на те, щоб бути хайку. Як роздільник рядків використовуються символи / . Якщо роздільники ділять текст на рядки, в яких 5/7/5 складів, то виведіть «Хайку!». Якщо кількість рядків дорівнює 3, то виведіть рядок «Не хайку. Має бути 3 рядка.» Інакше виведіть рядок вигляду «Не хайку. У першому рядку складів не s, а j.», де рядок i — найраніша, в якій кількість складів неправильне.

Для простоти будемо вважати, що складів рівно стільки ж, скільки гласних, не замислюючись про тонкощі.

Введення
Висновок
Вечір за вікном. / Ще один день прожитий. / Життя швидкоплинне… Хайку!
Просто текст Не хайку. Має бути 3 рядка.
Як вишня розцвіла! / Вона з коня зігнала / І князя-гордія. Не хайку. У рядку 1 складів не 5, а 6.
На голій гілці / Ворон сидить самотньо… / Осінній вечір! Не хайку. У 2 рядку складів не 7, а 8.
Тихо, тихо повзи, / Равлик, по схилу Фудзі, / Вгору, до самих висот! Не хайку. У рядку 1 складів не 5, а 6.
Життя швидкоплинне… / чи Думає про це / Маленький хлопчик. Хайку!

Шаблони, відповідні не конкретного тексту, а позиції

Окремі частини регулярного виразу можуть не відповідати тексту, а позиції в цьому тексті. Тобто такого шаблону відповідає не підрядок, а певна позиція в тексті, як би «між» літерами.

Прості шаблони, відповідні позиції

Для визначеності рядок, в якій ми шукаємо шаблон будемо називати всім текстом.Кожну сходинку всього тексту (тобто кожен максимальний шматок без символи кінця рядка) будемо називати рядками тексту.

Шаблон
Опис
Приклад
Застосовуємо до тексту
^ Початок всього тексту або початок рядка тексту,
якщо flag=re.MULTILINE
^Привіт
$ Кінець всього тексту або кінець рядка тексту,
якщо flag=re.MULTILINE
Будь здоровий!$
A Строго початок усього тексту
Z Строго кінець усього тексту
b Початок або кінець слова (зліва порожньо чи не буква, праворуч буква і навпаки) bвал вал, перевал, Перевалка
B Не межа слова: або і зліва, і справа букви,
і зліва, і справа НЕ букви
Ввал перевал, вал, Перевалка
ВвалB перевал, вал, Перевалка

Складні шаблони, відповідні позиції (lookaround і Co)

Такі шаблони застосовуються в основному в тих випадках, коли потрібно уточнити, що має йти безпосередньо перед або після шаблону, але при цьому
не включати знайдене в match-об’єкт.

Шаблон
Опис
Приклад
Застосовуємо до тексту
(?=...) lookahead assertion, відповідає кожній
позиції, відразу після якої починається
відповідність шаблону …
Isaac (?=Asimov) Isaac Asimov, Isaac other
(?!...) negative lookahead assertion, відповідає
кожної позиції, відразу після якої
НЕ може починатися шаблон …
Isaac (?!Asimov) Isaac Asimov, Isaac other
(?<=...) positive lookbehind assertion, відповідає
кожної позиції, якої може закінчуватися шаблон …
Довжина шаблону повинна бути фіксованою,
тобто abc і a|b — це ОК, а a* і a{2,3} — ні.
(?<=abc)def abcdef, bcdef
(?<!...) negative lookbehind assertion, відповідає
кожної позиції, якої НЕ може
закінчуватися шаблон …
(?<!abc)def abcdef, bcdef

На всяк випадок ще раз. Кожен їх цих шаблонів перевіряє лише те, що йде безпосередньо перед позицією або безпосередньо після позиції. Якщо пару таких шаблонів написати поруч, то перевірки будуть незалежні (тобто будуть відповідати AND в якомусь сенсі).

lookaround на прикладі королів та імператорів Франції

Людовік(?=VI) — Людовик, за яким йде VI

КарлIV, КарлІХ, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикІХ, ЛюдовікVI, ЛюдовикVII, ЛюдовікVIII, ЛюдовикХ, …, ЛюдовикXVIII,
ФилиппІ, Філіпп Ii, ФилиппІІІ, Філіпп Iv, ФилиппV, ФилиппVI

Людовік(?!VI) — Людовик, за яким йде не VI
КарлIV, КарлІХ, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикIX, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикX, …, ЛюдовикXVIII,
ФилиппІ, Філіпп Ii, ФилиппІІІ, Філіпп Iv, ФилиппV, ФилиппVI

(?<=Людовик)VI — «шостий», але тільки якщо Людовик
КарлIV, КарлІХ, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикІХ, ЛюдовікVI, ЛюдовікVII, ЛюдовікVIII, ЛюдовикХ, …, ЛюдовикXVIII,
ФилиппІ, Філіпп Ii, ФилиппІІІ, Філіпп Iv, ФилиппV, ФилиппVI

(?<!Людовик)VI — «шостий», але тільки якщо не Людовик
КарлIV, КарлІХ, КарлV, КарлVI, КарлVII, КарлVIII,
ЛюдовикІХ, ЛюдовикVI, ЛюдовикVII, ЛюдовикVIII, ЛюдовикХ, …, ЛюдовикХVIII,
ФилиппІ, філіпп ii, ФилиппІІІ, філіпп iv, ФилиппV, ФіліпVI

Шаблон
Коментар
Застосовуємо до тексту
(?<!d)d(?!d) Цифра, оточена не-цифрами Text ABC 123 A1B2C3!
(?<=#START#).*?(?=#END#) Текст від #START# #END# text from #START# till #END#
d+(?=_(?!_)) Цифра, після якої йде рівно одне підкреслення 12_34__56
^(?:(?!boo).)*?$ Рядок, в якій немає boo
(тобто немає такого символу,
перед яким є boo)
a foo and
boo and zoo
and others
^(?:(?!boo)(?!foo).)*?$ Рядок, в якій немає ні boo, ні foo a foo and
boo and zoo
and others


Інші фічі

Звичайно, тут описано не все, що вміють регулярні вирази, і навіть не все, що вміють регулярні вирази в пітоні. За подальшим можна звертатися до цього розділу. З корисного за кадром залишилася компіляція регулярок для прискорення багаторазового використання одного шаблону, використання іменних груп і різні хитрі трюки.
А вже які збочення можна робити з регулярними виразами в мові Perl — поручик Ржевський просто відпочиває 🙂

Завдання 4

Завдання 10. CamelCase -> under_score

Володимир написав свій відкритий проект, називаючи змінні в стилі «ВерблюжийРегистр».
І тільки після того, як написав про нього статтю, він дізнався, що в пітоні для імен змінних прийнято використовувати підкреслення для розділення слів (under_score). Треба терміново все виправити, поки його не «закидали тапками».

Завдання могла б виявитися досить складною, але, на щастя, Володимир зовсім не використовував рядкових констант і класів.
Тому будь-яка послідовність літер та цифр, всередині якої є великі, — це ім’я змінної, яку потрібно поправити.

Введення
Висновок
MyVar17 = OtherVar + YetAnother2Var 
TheAnswerToLifeTheUniverseAndEverything = 42
my_var17 = other_var + yet_another2_var 
the_answer_to_life_the_universe_and_everything = 42

Завдання 11. Видалення повторів

Досить поширена помилка помилка — це повтор слова.
Ось в попередньому реченні така допущена. Необхідно виправити кожен такий повтор (слово, один або кілька пробільних символів, і знову те ж слово).

Введення
Висновок
Досить поширена помилка помилка — це зайвий повтор повтор слова слова. Смішно, чи не правда? Не потрібно псувати хор хоровод. Досить поширена помилка — це зайвий повтор слова. Смішно, чи не правда? Не потрібно псувати хор хоровод.

Завдання 12. Близькі слова

Для простоти будемо вважати словом будь-яку послідовність букв, цифр і знаків _ (тобто символів w).
Даний текст. Необхідно знайти в ньому будь-який фрагмент, де спочатку йде слово «олень», потім не більше 5 слів, і після цього йде слово «заєць».

Введення
Висновок
Так він олень, а не заєць!
олень, а не заєць

Завдання 13. Форматування великих чисел

Великі цілі числа зручно читати, коли цифри в них розділені на трійки комами.
Просте переформатування цілі числа в тексті.

Введення
Висновок
12 мало 
краще 123 
1234 майже 
12354 добре 
стало 123456 
супер 1234567
12 мало 
краще 123 
1,234 майже 
12,354 добре 
стало 123,456 
супер 1,234,567

Завдання 14. Поділити текст на речення

Для простоти будемо вважати, що:

  • кожне речення починається з великої російської або латинської літери;
  • кожне речення закінчується одним із знаків пунктуації .;!?;
  • між пропозиціями може бути будь-якої непустої набір пробільних символів;
  • всередині пропозицій немає великих і точок (немає капостей в дусі «Ми любимо творчість А. С. Пушкіна)».

Розділіть текст на речення так, щоб кожне речення займало один рядок.
Порожніх рядків у висновку бути не повинно. Будь-які набори з понад одного пробільних символу замініть на один пробіл.

Введення
Висновок
У цьому 
пропозиції розриви рядка... Але це 
не так важливо! Зовсім? Так, зовсім! І це 

не повинно заважати.
У цьому реченні розриви рядка... 
Але це не так важливо! 
Зовсім? 
Так, зовсім! 
І це не повинно заважати. 

Завдання 15. Форматування номера телефону

Якщо ви коли-небудь намагалися збирати номери мобільних телефонів, то напевно знаєте, що майже будь-10 осіб використовують як мінімум п’ять різних способів записати номер телефону. Хтось починає з +7, хтось просто з 7 або 8, а деякі взагалі не пишуть префікс. Тризначний код хтось відокремлюється пробілами, хтось за допомогою дефіса, хтось дужками (і після дужки ще пробіл деякі додають). Після наступних трьох цифр хтось ставить пробіл, хтось дефіс, хто нічого не робить. І після наступних двох цифр — теж. А деякі починають за здравіє, а закінчують… загалом дуже незручно!

На вхід дається номер телефону, як його міг би ввести людина. Необхідно його переформатувати у формат +7 123 456-78-90. Якщо з номером щось не так, то потрібно вивести рядок Fail!.

Введення
Висновок
+7 123 456-78-90
+7 123 456-78-90
8(123)456-78-90
+7 123 456-78-90
7(123) 456-78-90
+7 123 456-78-90
1234567890
+7 123 456-78-90
123456789
Fail!
+9 123 456-78-90
Fail!
+7 123 456+78=90
Fail!
+7(123 45678-90
+7 123 456-78-90
8(123 456-78-90
Fail!

Завдання 16. Пошук e-mail’ов — 2

В попередній задачі ми трохи схалтурили.
Проте до цього моменту завдання має стати посильної!

На вхід дається текст. Необхідно вивести всі e-mail адреси, які в ньому зустрічаються. При цьому e-mail не може бути частиною слова, тобто ліворуч і праворуч від e-mail’ом а повинен бути або кінець рядка, або не-буква і при цьому не один з символів '._+-, допустимих в адресі.

Введення
Висновок
Іван Іванович! 
Потрібен відповідь на лист від ivanoff@ivan-chai.ru. 
Не забудьте поставити в копію 
serge'o-lupin@mail.ru - це важливо.
ivanoff@ivan-chai.ru 
serge'o-lupin@mail.ru
NO: foo.@ya.ru, foo@.ya.ru, foo@foo@foo
NO: +foo@ya.ru, foo@ya-ua
NO: foo@ya_ru, -foo@ya.ru-, foo@ya.ru+
NO: foo..foo@ya.ru 
YES: (boo1@ya.ru), boo2@ya.ru!, boo3@ya.ru
boo1@ya.ru 
boo2@ya.ru 
boo3@ya.ru 

Post scriptum

PS. Текст довгий, в ньому напевно є помилки і помилки. Пишіть про них скоріше в лічку, я виправлю.
PSS. Ух і намаялся я нормальний html в хабра-html переганяти. Здається, парсер хабра писаний на регулярках, інакше як пояснити всі ті дивні речі, які доводилося виловлювати бинпоиском? 🙂

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *