Разминаем мозг регулярными выражениями — Regex Tuesday Challenge

    Я хочу предложить вам поломать голову вечерок-другой над интересными задачками, на регулярные выражения, которые Callum Macrae выкладывает на своем сайте на GitHub каждый Вторник.

    Каждый вопрос представлен в виде набора тестов. Задача — написать такой регулярное выражение, чтобы все тесты стали зелеными.
    Некоторые из задач сами по себе довольно простые, а самая интересная часть — в том, чтобы написать наиболее короткое возможное регулярное выражение.

    Тесты используют JavaScript Regex движок вашего браузера, который обладает всеми основными возможностями PCRE.  Подробнее можно посмотреть тут (англ.) , в колонке ECMA в таблице.

    Я собрал в этой статье русские версии задач и материалов, которые могут помочь в их решении. Было бы интересно увидеть самые интересные решения в комментах.

    UPD: В регулярных выражениях ECMAScript нету ретроспективных проверок.


    1. Выделяем повторяющиеся слова (Ссылка ведет на задачу)


    Задача:
    Выделить тегом <strong> повторяющиеся слова.

    Примеры:
    Тhis is a test=> this is a test
    Тhis is is a test => this is <strong>is</strong> a test

    2. Оттенки серого


    Задача:
    Выбрать отенки серого в разных цветовых системах.
    Почитать про цвета можно по этой ссылке.

    Примеры:
    #FFFДа
    rgb(2.5, 2.5,2.5)Да
    rgb(2, 4, 7) Нет

    3. Даты найти строки сответствующем этому шаблону: YYYY/MM/DD HH:MM(:SS)


    Задача:
    Выбрать существующие даты между 1000 и 2012 годом. Секунды могут быть опущены.
    Автор облегчает задачу: в каждом месяце 30 дней.

    Примеры:
    2012/09/18 12:10Да
    2013/09/09 09:09Нет (после 2012)

    4. Курсив в MarkDown


    Задача:
    Преобразовать текст, обрамленный в звездочки, в курсив. Не трогать текст в двойных звездочках (жирный).
    Почитать подробнее про MarkDown можно в википедии.

    Примеры:
    *this is italic*" => <em>this is italic</em>
    **bold text (not italic)** => **bold text (not italic)**

    5. Числа


    Задача:
    Выбрать числа с запятой или пробелом, в качестве разделителя разрядов. (к счастью обошлось без моммайе)

    Примеры:
    8,205,500.4672Да
    1,5826,000 Нет

    6. IPv4 адреса


    Задача:
    Выбрать IPv4 адреса во всех возможных, представлениях: десятичном, шестнадцатеричном и восьмеричном. С точками и без. Подробнее про IP адреса можно узнать в википедии

    Примеры:
    99.198.122.146Да
    0xFF.255.0377.0x12Да
    256.256.256.256 Нет

    7. Доменные имена


    Задача:
    Доменные имена для протоколов http и https, с необязательным слешем в конце. Специальые символы не используются.

    Примеры:
    http://example.com/Да
    example.com Нет
    кремль.рфНет :(

    8. Повторяющиеся пункты в списке MarkDown


    Задача:
    Найти и выделить жирным (**) повтряющиеся пункты в MarkDown-списке.

    Примеры:
    * First list item
    * Second list item
    =>
    * First list item
    * Second list item

    * Repeated list item
    * Repeated list item
    =>
    * Repeated list item
    * **Repeated list item**

    9. Ссылки в MarkDown


    Задача:
    Преобразовать MarkDown ссылки в HTML. Выглядят вот так: [text](http://example.com)
    Главное не перепутать с картинками: ![alt text](image location)

    Примеры:
    [Basic link](http://example.com) => <a href="http://example.com">Basic link
    [Invalid](javascript:alert()) => [Invalid](javascript:alert())

    10. Делим предложение на токены.


    Задача:
    Разбить предложение на токены. Это может быть полезно, например, для поисковой системы.

    Есть несколько правил:

    • Несколько слов в кавычках должны попасть в один токен
      This "huge test" is pointless => this,huge test,is,pointless
    • Слова написанные через дефис так же попадают в один токен.
      Слова написанные через несколько дефисов (тире), или имеющие дефис в начале или в конце попадают в раздельные токены.
      Suzie Smith-Hopper test--hyphens => Suzie,Smith-Hopper,test,hyphens.
    • Сокращения (стяжения) попадают в один токен
      I can't do it => I,can't,do,it.
    • Вся пунктуация кроме апострофов и дефисов должна быть убрана.
      Too long; didn't read => Too,long,didn't,read.


    11. Буквы в алфавитном порядке.


    Задача:
    Выбрать последовательность неповторяющихся символов в алфавитнои порядке.Пробелы нужно игнорировать. К сожалению известные мне решения не очень удачны.

    Примеры:
    abcdefghijkДа
    abbcНет

    12. Исправляем пробелы


    Задача:
    Убрать повторяющиеся пробелы и знаки табуляции, оставить по одному пробелу между словами и по два между предложениями.

    Примеры:
    Extra       spaces => Extra spaces
    Sentence.      Sentence. => Sentence.  Sentence.

    13. Повторяющиеся слова друг под другом


    Задача:
    Выбрать повторяющиеся слова, которые находятся непосредственно друг под другом.
    Предполагается использование моноширинного шрифта. Строки длиннее 32х символов переносятся.

    Примеры:
    This sentence is pretty long and
    this sentence is also a testДа
    This sentence also shouldn't
    match as this has no words
    below.Нет

    14. Брутфорсим химические элементы


    Задача:
    >Выбираем первые 50 химических элементов таблицы менделеева. Решение довольно очевидное, поэтому задача — найти максимально короткое решение.

    Примеры:
    HДа
    MНет

    15. Музыкальные аккорды


    Задача:
    Выбрать музыкальные аккорлы, например как Cmin, или Bmaj. Нужны и краткая и полная записи. Для данной задачи предпложим, что аккорды E♯, B♯, F♭ and C♭ не существует.

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

    Так же обратите внимание, что диез (♯)
    это не то же самое, что решетка (#).

    Примеры:
    CДа
    Z Нет

    16. Брутфорсим химические элементы


    Задача:
    Выбрать химические элементы с атомным числом больше 50.

    Примеры:
    IДа
    AНет

    17. Регулярное выражение для регулярного выражения.


    Задача:
    Выбрать правильно построенное регулярное выражение.Для начала ограничимся литералами (возможно экранированными), классами и несколькими квантификаторами.

    Примеры:
    /regexp?/Да
    regexНет

    18. IRC — Сообщения


    Задача:
    Выбрать правильно сформированное IRC сообщение.
    Вот ссылка на русскую версию спецификаци.

    Примеры:
    [_]!abc@test PRIVMSG #chat :TestДа
    [email protected] PRIVMSG #chat :Hello!Нет
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 40

      +26
      Черт. Лютая убийца времени. Автор, гореть тебе в аду.
        +14
        Взявшись за решение этих задачек, я понял что совсем ничего не смыслю в регулярных выражениях :)
          +3
          Для большинства задач — согласен. Прошу решивших помаленьку публиковать сюда их ответы, желательно с обоснованием или хотя-бы ходом размышлений при решении.
            +1
            В первой задаче необходимо ссылаться на найденный ранее паттерн. По-моему, сложность была в обработке окружающих символов (просмотр вперёд/назад).
            можно улучшить решение
              +1
              Первая. Проблема в обработке начал/концов.

              /(^|\W)(['\w]+)\s(\2)\b/gi

              $1$2 <strong>$3</strong>

                +4
                /\b(['\w]+) (\1)\b/gi
                $1 <strong>$2</strong>
                  +1
                  Пробел
                    +1
                    Ну у вас тоже любой пробельный символ на пробел заменяется.
                      0
                      Как на счет
                      /(\b\S+\b)(\s+)(\1\b)/ig
                      $1$2<strong>$3</strong>
                    +2
                    первая задача: решение получилось один-в-один с вашим;

                    а вот во второй задаче всё гораздо печальней, особенно с разделением rgb/rgba, hsl/hsla — можно ли обойтись без копи-паста? — у меня не получилось :'-(

                    Вот моё решение второй задачи (нужно только реплейснуть в текстовом редакторе (^\s*|\n) на «ничто»):

                    /^
                    (?:
                        #([\da-f]{1,2})\1\1
                        |
                        rgb\(
                            \s*0*
                            (
                                (?:
                                    1?\d?\d
                                    |
                                    2[0-4]\d
                                    |
                                    25[0-5]
                                )
                                (?:\.\d+)?
                                %?
                            ),
                            \s*0*
                            \2,
                            \s*0*
                            \2
                            \s*
                        \)
                        |
                        rgba\(
                            \s*0*
                            (
                                (?:
                                    1?\d?\d
                                    |
                                    2[0-4]\d
                                    |
                                    25[0-5]
                                )
                                (?:\.\d+)?
                                %?
                            ),
                            \s*0*
                            \3,
                            \s*0*
                            \3,
                            \s*0*
                            \d+(?:\.\d+)?%?
                            \s*
                        \)
                        |
                        hsl\(
                            \s*0*
                            \d+(?:\.\d+)?,
                            \s*0*
                            (?:
                                0%,
                                \s*0*
                                \d+(?:\.\d+)?%
                                |
                                \d+(?:\.\d+)?%,
                                \s*0*
                                (?:0|100)%
                            )
                            \s*
                        \)
                        |
                        hsla\(
                            \s*0*
                            \d+(?:\.\d+)?,
                            \s*0*
                            (?:
                                0%,
                                \s*0*
                                \d+(?:\.\d+)?%
                                |
                                \d+(?:\.\d+)?%,
                                \s*0*
                                (?:0|100)%
                            ),
                            \s*0*
                            \d+(?:\.\d+)?%?
                            \s*
                        \)
                    )
                    $/i
                    
              +1
              /^(1\d{3}|20(0\d|1[0-2]))\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2]\d|30) ([0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/i

              теперь сижу и думаю… зачем я это сделал?
                +1
                Быть может, это самое индусское регулярное выражение, что вы видели в своей жизни, но тем не менее, мои мучения не были напрасны, и одно из заданий я таки выполнил. (задача №3)
                  +1
                  ^(?!a)

                  костыль под конкретную задачу? :)
                    +1
                    что-то вроде того :)
                    в любом случае, моё решение и рядом не стоит с решением RemRyahirev, так что мне здесь ещё есть чему учиться.
                  +7
                  Задача №1.

                  Вторую не осилил :(

                  Задача №3. Если кто знает решение лучше — напишите, плиз.

                  Задача №4. Там, по-идее, надо вместо подмаски (^|[^*]) писать утверждение (?<!\*), но оно не срабатывает. Не понял почему.

                  Задача №5.
                    +1
                    По поводу 4-ой, сам на это наткнулся. Негативная ретроспективная проверка далеко не везде поддерживается.
                      +2
                      Задача №7. На домен первого уровня, обычно, более суровые ограничения накладывают нежели [a-z\d]{1,6} (длинные домены типа travel, info, museum просто через | перечисляют)…

                      Задача №8.

                      Задача №9.

                      Задача №12.

                      Задача №15.

                      Задача №17.
                        +1
                        Задача №18. Костыльненько как-то получилось… видимо, надо описание протокола копать, чтоб понять как там что положено.
                          0
                          №5 /^\d{1,3}([, ]\d{3})*([.,]\d+)?$/.test('1,000,9') = true
                          Хотя число 1,000,9 некорректно — там надо разделять форматы на два отдельных выражения:
                          либо разряды разделяет запятая — тогда дробная часть отделяется точкой,
                          либо разряды — пробел, дробная часть — запятой.
                            0
                            Задача №3 чуть покороче.
                            А еще в вашей регулярке ошибка, вы не матчите 31 день. В тестах такой даты не было но все же
                              0
                              В целом, согласен. Но автор челленджа упростил задачу, а то получится, что и 30 февраля не должно попадать
                                0
                                Я сначала так и строил регулярку, с учетом высокосных лет. Но потом все же решил не настолько углубляться, а то уж совсем встремная регулярка получалась. Ну а месяцев где 31 день все же намного больше чем 30.
                                Вообще все эти тесты по хорошему бы генерить каждый раз разные, как в спортивном программировании, так было бы честнее
                            +3
                            Решение задачи №1.
                            +10
                            «Если у вас была одна проблема, и вы решили использовать регулярные выражения, то теперь у вас есть две проблемы» :)

                            Автору спасибо за полезную информацию, разомнем мозг.
                              +1
                              Костылеобразное решение задачи №7
                                +2
                                Странноватое решение задачи №11 (Если у кого-то есть идеи как проще — напишите пожалуйста)
                                  +2
                                  Гигантское решение задачи №6
                                    +2
                                    Разбирать число «4294967295» рег.вырами бред конечно полный — это не их задача. Составитель заданий, смотрю, знатный извращенец.

                                    В данном случае следовало бы определить число ли это, например так: "/^\d+$/". Если RE-тест пройден, то дальше идёт парсинг числа, и определение больше ли константы 4294967295.
                                    +2
                                    По аналогии с сайтом sql-ex.ru/ кто-нибудь может сделает выдачу сертификатов?
                                      +4
                                      Блин, дошел до 9ого, решил посмотреть сколько их всего там :(( Я думал 10. Мотивация испарилась

                                      Чего зря пропадать.. Хвастаюсь. (решения)
                                      Challenge 1 — выделить повторяющиеся слова — 25 + 22 символа
                                      Challenge 2 — матчить серый цвет в куче форматов — 412 символов
                                      Challenge 3 — матчить дату-время — 146 символов
                                      Challenge 4 — *курсив* — 36 + 13 символов
                                      Challenge 5 — матчить форматированные числа — 54 символа
                                      Challenge 6 — матчить IPv4-адреса в куче форматов (АД) — 519 символов
                                      Challenge 7 — матчить домены с http/https — 94 символа
                                      Challenge 8 — отмечать повторяющиеся элементы списка — 37 + 16 символов
                                      Challenge 9 — применять синтаксис разметки для ссылок — 124 + 23 символа (пригодился регексп из задания про домены)

                                      P.S. Еще хвастаюсь: на 9 первых ушло 1,5 часа \m/
                                        +1
                                        О, снова стал публиковать. Спасибо автору статьи.
                                          +1
                                          допустим, что это решение (2 задача)

                                          Беру его тесты, беру сторонний матчер (или руками прямо в js) — совпадения. А на сайте — нет. Не понимаю почему. Никто не подскажет?
                                            0
                                            Все тесты должны быть зеленые.

                                            Если написано «no match», значит, что это не должно совпадать. У вас совпадает, поэтому и ошибка.
                                              +1
                                              Ах вот в чём дело, теперь понятно. Спасибо! :)
                                              +1
                                              там где «no match» как раз таки не должно быть совпадений.

                                              по вашей логике, регулярка '#ef4'.match(/(.+?)/i) должна подходить под ответ задачи )
                                              0
                                              Не хватает чего-то в таком духе:
                                              a...ab...bc...c, где a, b и с встречаются одинаковое количество раз.
                                              Или вариант покруче:
                                              aabcacbcb… с тем же условием
                                                +1
                                                Посмотрел сколько смог. В целом то все не столько сложно, сколько нудно. На первые два убил час утром, конкретно под тесты реги не затачивал, но и более общий случай, чем те что даны в тестах тоже не рассматривал. Получил:

                                                1.
                                                Рега:
                                                /\b(\S+) (\1)\b/gi
                                                Замена:
                                                $1 <strong>$2</strong>

                                                2.
                                                Рега:
                                                /#([\da-f]{1,2})\1\1$|\brgb\((\d+\.\d+|\d\d?|1\d\d|2[0-4]\d|25[0-5])(%?),\s*0*\2\3,\s*0*\2\3\)|rgba\(([\d.]+)(%?),\s*0*\4\5,\s*0*\4\5,|hsla?\([\.\d]+%?,\s*(0%?,\s*[.\d]+%?|[.\d]+%?,\s*(0|100)%?)(,\s*[.\d]+)?\)/i

                                                Для разминки мозгов неплохо, главное, чтобы кто-то не додумался на собеседованиях такое задавать, особенно на бумажке ;)
                                                  0
                                                  3.
                                                  /^(1\d{3}|200\d|201[012])\/(0[1-9]|1[012])\/(0[1-9]|[12]\d|30)\s+(0\d|1\d|2[0-3]):[0-5]\d(:[0-5]\d)?$/

                                                  Честно говоря, это использование регулярок, ради использования регулярок, причем там, где в реальных условиях используются совсем другие средства. Но, что не говори, мозги разминает.

                                                  Кстати, кто просил ход рассуждений? Рассуждения, например, в этом случае (задании №3) весьма банальны:

                                                  Нужен год от 1000 до 2012, это означает, что подходит любой 4х-значный год начинающийся на «1» (1000-1999), т.е: 1\d\d\d или 1\d{3}, также подходит 2000-2009, т.е. 200\d, и 2010-2012, т.е. 201[012]. Таким образом рега, под которую попадут все нужные года выглядит: (1\d{3}|200\d|201[012]) и принципиально по другому это условие с помощью регулярок не запишешь.Похожие размышления для месяца, дня, часа, минут.

                                                  Например минуты и секунды бывают от 00 до 59, т.е. первая цифра 0-5, а вторая любая, записывается это [0-5]\d. Для секунд то же самое, но они опциональны по условию.
                                                  0
                                                  Меня скоро уволят из-за этих challeng-ей.
                                                  Вот первое задание:
                                                  /\b(\S+)\s+(\1)\b/gi ---> $1 <strong>$2</strong>

                                                  Второе не могу понять hsl и hsla. Почему:
                                                  hsl(20,0%, 50%) — match
                                                  hsl(0, 10%, 100%) — match
                                                  hsl(0.5, 10.5%, 0%) — match
                                                  hsl(5, 5%, 0%) — match
                                                  hsla(20, 0%, 50%, 0.88) — match
                                                  hsla(0, 0%, 0%, 0.25) — match

                                                  a

                                                  hsl(20, 20%, 20%) — no match
                                                  hsl(0, 1%, 01%) — no match
                                                  hsl (20,0%, 50%) — no match
                                                  hsla(0, 10%, 50%, 0.5) — no match

                                                  Подскажите, пожалуйста, по какому алгоритму вычисляется grayscale цвет в этих двух??

                                                  Третье задание —
                                                  /^(1\d+|20(0\d|1[0-2]))/(0[1-9]|1[0-2])/(0[1-9]|[1-2]\d|30) ([0-1]\d|2[0-3]):[0-5]\d:?([0-5]\d)?$/

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое