Многим в детстве нравились приключенческие игры с головоломками и загадками. Найти решение было личным вызовом, а его поиски напоминали охоту за сокровищами. И когда, наконец, загадка была разгадана, ощущения были потрясающими!
Регулярные выражения могут помочь ощутить тот же детский восторг. (Регулярные выражения на английском языке — regular expressions, сокращенно — regex, — прим. перев.). Их использование открывает множество возможностей. К тому же, когда читаешь регулярное выражение и понимаешь, что оно означает, — это как расшифровать египетский иероглиф, как внезапно обнаружить, что знаешь чужой язык!
На Codewars есть задачка: нужно написать функцию для конвертации римских чисел в арабские. Мы не будем разбирать решение этой задачи целиком. Вместо этого сосредоточимся на одном этапе: проверке, ввел ли пользователь валидное римское число.
Как формируются римские числа
Римские числа записываются римскими цифрами, которые вы видите в табличке:
Тысячи | Сотни | Десятки | Единицы | |
---|---|---|---|---|
1 | M | C | X | I |
2 | MM | CC | XX | II |
3 | MMM | CCC | XXX | III |
4 | CD | LX | IV | |
5 | D | L | V | |
6 | DC | LX | VI | |
7 | DCC | LXX | VII | |
8 | DCCC | LXXX | VIII | |
9 | CM | XC | IX |
Кажется, цифры для обозначения тысяч заканчиваются на [MMM], а значит, самое большое число, которое можно написать римскими цифрами, это 3999 ([MMMCMXCIX]). Для целей этой статьи и решения нашей задачи будем считать, что римские числа укладываются в диапазон от 1 до 3999. (На самом деле есть и большие числа, подробнее об этом можно почитать в Википедии, — прим. перев.).
Что нужно понимать, так это важность порядка в расположении римских цифр. Если вы поставите их не в том порядке, вы напишете неправильное или вообще нечитаемое число. Как было показано в таблице выше, число начинается с тысяч [M] 1000, затем идут сотни [D/C] 500/100, затем десятки [L/X] 50/10 и, наконец, единицы [V/I] 5/1.
Но это еще далеко не все! Цифры могут повторяться не больше трех раз подряд (например, 300 = CCC), а потом заменяются комбинацией двух цифр (400 = CD, а не CCCC). Дальше вы можете продолжать использовать C, но уже перед M, как в числе 900 (CM).
Не так-то все просто, верно?
Ладно, давайте повторим наши условия
- Римские числа — это числа в диапазоне от [I] 1 до [MMMCMXCIX] 3999
- Цифры в числе располагаются в строго определенном порядке: [M] 1000 / [D] 500 / [C] 100 / [L] 50 / [X] 10 / [V] 5 / [I] 1
- Любая отдельная цифра не может повторяться больше трех раз подряд, на четвертый раз используется уже пара цифр
- Пары цифр бывают следующие: [CM] 900 / [CD] 400 / [XC] 90 / [XL] 40 / [IX] 9 / [IV] 4
Вы замечаете, как начинает вырисовываться регулярное выражение?
Давайте переведем это в код
Мы будем использовать один очень полезный при написании регулярных выражений тег, — VERBOSE или re.X). Он позволяет разделять паттерн на несколько строк, чтобы сделать его более читаемым. Давайте попробуем!
import re def is_roman_number(num): pattern = re.compile(r""" ^M{0,3} (CM|CD|D?C{0,3})? (XC|XL|L?X{0,3})? (IX|IV|V?I{0,3})?$ """, re.VERBOSE) if re.match(pattern, num): return True return False
Вах! Это уже потрясающе выглядит! Рассмотрим строки паттерна подробнее:
^M{0,3}
= От 0 до 3 символов [M] в начале строки [^](CM|CD|D?C{0,3})?
= Одна пара [CM] или одна пара [CD], или [D], за которой идет от 0 до 3 символов [C]. Каждый элемент является опциональным [?], так же, как и весь блок [()?](XC|XL|L?X{0,3})?
= Одна пара [XC] или одна пара [XL] или [L], за которой идет от 0 до 3 символов [X]. Каждый элемент является опциональным [?], так же, как и весь блок [()?](IX|IV|V?I{0,3})?$
= Одна пара [IX] или одна пара [IV] или [V], за которой идет от 0 до 3 символов [I]. Каждый элемент является опциональным [?], так же, как и весь блок [()?], стоящий в конце строки [$]
Протестируем наш код
Используем fstring для вызова функции и сравнения строки с паттерном:
num_valid = 'MMDCCLXXIII' num_invalid = 'CCCMMVIIVV' print(f"{num_valid} - это {'не ' if not is_roman_number(num_valid) else ''}римское число") print(f"{num_invalid} - это {'не ' if not is_roman_number(num_invalid) else ''}римское число") # Вывод: # MMDCCLXXIII - это римское число # CCCMMVIIVV - это не римское число
Не так уж и плохо! А теперь посмотрите на это и попробуйте сказать, что это не самая прекрасная вещь, которую вы видели в жизни!
^M{0,3}(CM|CD|D?C{0,3})?(XC|XL|L?X{0,3})?(IX|IV|V?I{0,3})?$'