Artur
40 уровень
Tallinn

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2

Статья из группы Random
RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 1 Оригинал здесь В прошлой части мы освоили простейшие регулярные выражения, и уже кое-чему научились. В этой части мы изучим чуть более сложные конструкции, но, поверьте, это будет не так трудно, как могло-бы показаться. RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2 - 1Итак, продолжим!

Шаг 8: звездочка * и знак "плюс" +

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2 - 2До сих пор нам более или менее удавалось сопоставлять только строки заданной длины. Но в последних задачах мы приблизились к пределу того, что мы можем сделать с помощью обозначений, которые мы видели до сих пор. Предположим, например, что мы не ограничены 3-символьными идентификаторами Java, а у нас могут быть идентификаторы любой длины. Решение, которое могло работать в предыдущем примере, не будет работать в следующем примере:
pattern: [a-zA-Z_$]\w\w
string:  __e $123 3.2 fo Barr a23mm ab x
matches: ^^^ ^^^         ^^^  ^^^  
(Пример) Обратите внимание, что когда идентификатор действителен, но длиннее 3 символов, сопоставляются только первые три символа. И когда идентификатор действителен, но содержит менее 3 символов, то regex его вообще не находит! Проблема в том, что выражения в квадратных скобках [] соответствуют ровно одному символу, как и классы символов, такие как \w. Это означает, что любые совпадения в приведенном выше регулярном выражении должны быть длиной ровно в три символа. Так что это не работает, как мы могли-бы надеяться. Здесь могут помочь специальные символы * и +. Это модификаторы, которые могут быть добавлены справа от любого выражения, чтобы искать соответствия этому выражению более одного раза. Звезда Клини (или "звездочка") * укажет, что нужно искать соответствия предыдущему токену любое количество раз, включая ноль раз. Знак "плюс" + укажет, что нужно искать один или несколько раз. Таким образом, выражение, которое предшествует +, является обязательным (по крайней мере, один раз), в то время как выражение, которое предшествует *, является необязательным, но когда оно появляется, оно может появляться любое количество раз. Теперь, с этим знанием мы можем исправить приведенное выше регулярное выражение:
pattern: [a-zA-Z_$]\w*
string:  __e $123 3.2 fo Barr a23mm ab x
matches: ^^^ ^^^^     ^^ ^^^^ ^^^^^ ^^ ^ 
(Пример) Теперь мы сопоставляем действительные идентификаторы любой длины! Бинго! Но что-бы произошло, если бы мы в примере выше использовали + вместо *?
pattern: [a-zA-Z_$]\w+
string:  __e $123 3.2 fo Barr a23mm ab x
matches: ^^^ ^^^^     ^^ ^^^^ ^^^^^ ^^ 
(Пример) Мы пропустили последнее совпадение, х. Это связано с тем, что для + требуется сопоставление хотя бы одного символа, но поскольку выражение в скобках [], предшествующее \w+, уже 'съело' символ x, то доступных символов больше не осталось, поэтому сопоставление не удалось. Когда мы можем использовать +? Когда нам нужно найти хотя бы одно совпадение, но не важно, сколько раз должно совпасть данное выражение. Например, если мы хотим найти любые числа, содержащие десятичную точку:
pattern: \d*\.\d+
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^  
(Пример) Обратите внимание, что сделав числа слева от десятичной точки необязательными, мы смогли найти как 0.011 так и .2. Для этого нам нужно было сопоставить ровно одну десятичную точку при помощи \. и минимум одну цифру справа от десятичной точки при помощи \d+. Вышеупомянутое регулярное выражение не будет соответствовать числу, подобному 3., потому что для соответствия нам требуется по крайней мере одна цифра справа от десятичной точки.

По традиции, решим пару простых задачек:

Найдите все английские слова в отрывке ниже.
pattern: 
string:  3 plus 3 is six but 4 plus three is 7
matches:   ^^^^   ^^ ^^^ ^^^   ^^^^ ^^^^^ ^^ 
(Решение) Найдите все обозначения размеров файлов в списке ниже. Размеры файлов будут состоять из числа (с десятичной точкой или без нее), за которым следуют KB, MB, GB или TB:
pattern:
string:  11TB 13 14.4MB 22HB 9.9GB TB 0KB
matches: ^^^^    ^^^^^^      ^^^^^    ^^^  
(Решение)

Шаг 9: "optional" вопросительный знак ?

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2 - 3Вы уже написали regex для решения последней задачи? Это сработало? Теперь попробуйте применить его здесь:
pattern:
string:  1..3KB 5...GB ..6TB
matches:  
Очевидно, что ни одно из этих обозначений не является допустимым размером файла, поэтому хорошее регулярное выражение не должно находить ни одно из них. Решение, которое я написал для решения последней задачи, соответствует им всем, по крайней мере частично:
pattern: \d+\.*\d*[KMGT]B
string:  1..3KB 5...GB ..6TB
matches: ^^^^^^ ^^^^^^   ^^^ 
(Пример) Так в чем-же проблема? На самом деле, нам нужно найти только одну десятичную точку, если она есть. Но * допускает любое количество совпадений, включая ноль. Есть ли способ сопоставлять только ноль раз или один раз? Но не более одного раза? Конечно есть. "optional" ? это модификатор, который соответствует нулю или одному из предыдущих символов, но не более:
pattern: \d+\.?\d*[KMGT]B
string:  1..3KB 5...GB ..6TB
matches:    ^^^          ^^^ 
(Пример) Мы здесь приблизились к решению, но это еще не совсем то, что нам надо. Чуть позже мы увидим, как это исправить, через несколько шагов.

А пока решим такую задачу:

В некоторых языках программирования (например, Java) за некоторыми целыми числами и числами с плавающей запятой (точкой) могут следовать l / L и f / F, чтобы указать, что они должны рассматриваться как long / float (соответственно), а не как обычные int / double. Найдите все действительные "long" числа в строке ниже:
pattern:
string:  13L long 2l 19 L lL 0
matches: ^^^      ^^ ^^      ^ 
(Решение)

Шаг 10: знак "or" (или) |

RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 2 - 4На шаге 8 у нас возникли некоторые трудности с нахождением различных типов чисел с плавающей точкой:
pattern: \d*\.\d+
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^  
Приведенный выше шаблон сопоставляет числа с десятичной точкой и минимум одну цифру справа от десятичной точки. Но что, если мы также хотим сопоставить такие строки, как 0.? (Без цифр справа от десятичной точки.) Мы могли бы написать такое регулярное выражение:
pattern: \d*\.\d*
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^ ^^ ^ 
(Пример) Это соответствует 0., но это также соответствует одиночной точке ., как вы можете видеть выше. На самом деле то, что мы пытаемся сопоставить, это два разных класса строк:
  1. числа с минимум одной цифрой справа от десятичной точки
  2. числа с хотя бы одной цифрой слева от десятичной точки
Напишем 2 следующих, независимых друг от друга регулярных выражения:
pattern: \d*\.\d+
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^  
pattern: \d+\.\d*
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^       ^^^ ^^^^ ^^^^^     ^^^^^^^ ^^ 
Мы видим, что ни в одном из этих случаев подстроки 42, 5, 6 или . не находятся движком. Для получения необходимого результата, нам не помешало-бы объединить эти регулярки. Как мы можем этого достичь? Знак "or" | позволяет нам указать в регулярном выражении сразу несколько возможных последовательностей совпадений. Подобно тому, как [] позволяет нам указывать альтернативные одиночные символы, с помощью знака "or" | мы можем указывать альтернативные многосимвольные выражения. Например, если мы хотим найти "собаку" или "кошку", мы могли-бы написать как-то так:
pattern: \w\w\w
string:  Obviously, a dog is a better pet than a cat.
matches: ^^^^^^^^^    ^^^      ^^^^^^ ^^^ ^^^    ^^^ 
(Пример) ... но это соответствует всем тройным последовательностям символов класса "word". Но "dog" и "cat" даже не имеют общих букв, поэтому и квадратные скобки не помогут нам здесь. Вот самое простое регулярное выражение, которое мы могли бы использовать, оно соответствует обоим и только этим двум словам:
pattern: dog|cat
string:  Obviously, a dog is a better pet than a cat.
matches:              ^^^                        ^^^ 
(Пример) Механизм регулярных выражений сначала пытается сопоставить всю последовательность слева от знака |, но если ему это не удается, то затем он пытается сопоставить последовательность справа от знака |. Несколько знаков | также могут быть объединены в цепочки для соответствия более чем двум альтернативным последовательностям:
pattern: dog|cat|pet
string:  Obviously, a dog is a better pet than a cat.
matches:              ^^^             ^^^        ^^^ 
(Пример)

Теперь решим очередную пару задачек, чтобы лучше усвоить этот шаг:

Используйте знак |, чтобы исправить десятичное регулярное выражение, указанное выше, и получить такой результат:
pattern:
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^ ^^ 
(Решение) Используйте знак |, классы символов, "optional" ? и т.д., чтобы создать одно регулярное выражение, соответствующее как целым числам, так и числам с плавающей запятой (точкой), как обсуждалось в задаче в конце предыдущего шага (эта задачка немного посложнее, да ;))
pattern: 
string:  42L 12 x 3.4f 6l 3.3 0F L F .2F 0.
matches: ^^^ ^^   ^^^^ ^^ ^^^ ^^     ^^^ ^^  
(Решение) 20 коротких шагов для освоения регулярных выражений. Часть 3 RegEx: 20 коротких шагов для освоения регулярных выражений. Часть 4
Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Артур Прохоренко Уровень 28, Гомель, Белоруссия
28 ноября 2020
Спасибо! Воскресенья не жалко, что бы эту тему разобрать!
Константин Уровень 17, Москва, Россия
30 апреля 2020

Напишем 2 следующих, независимых друг от друга регулярных выражения:

pattern: \d*\.\d*
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^ ^^    ^^^ ^^^^ ^^^^^     ^^^^^^^  


pattern: \d*\.\d*
string:  0.011 .2 42 2.0 3.33 4.000 5 6 7.89012 0. .
matches: ^^^^^       ^^^ ^^^^ ^^^^^     ^^^^^^^ ^^ 
что-то пошло не так