Урок 12. Регулярные выражения

Лабораторные работы си шарп и задания по теме «Язык программирования c sharp: регулярные выражения»

Теория

  • Регулярные выражения позволяют осуществлять поиск по шаблону в тексте.
  • Строка шаблона включает символьные классы.
  • Символьные классы — символы (латинские буквы) с впереди стоящим слешем \, которые в шаблоне несут смысловую нагрузку: на их месте должно или может находиться что-то конкретное.

using System.Text.RegularExpressions; // пространство имен для работы с регулярными выражениями

классы платформы .NET для работы с регулярными выражениями:

Статические методы класса Regex

    1. Match Regex.Match(s,pattern)
    2. bool Regex.IsMatch(s,pattern)
    3. MatchCollection Regex.Matches(s,pattern)
    4. string Regex.Replace(s,pattern,replace_s)
    5. string[] Regex.Split(s,pattern)
    

    Методы для многократного использования одного шаблона

    var r = new Regex(pattern);
    r.Match(s) 
    r.IsMatch(s) 
    r.Matches(s) 
    r.Replace(s,replace_s)

    Переменные класса Match и их свойства

    string s = "one two three four two five alice two";
    Match m = Regex.Match(s, "two"); 
     
    //1.  m.Success
    Console.WriteLine(m.Success); // True
    //2. m.Value
    Console.WriteLine(m.Value); //  two  
    //3. m.Index 
    Console.WriteLine(m.Index); //  4    
    //4. m.Length
    Console.WriteLine(m.Length); //  2  
    //5. m.NextMatch().Index
    Console.WriteLine(m.NextMatch().Index); //  19

    Класс MatchCollection – свойство Count.

    foreach (var m in MatchCollection) 
    // here m is Match type

    Пример 1:

    //...
    // подключение области имен RegularExpressions
    using System.Text.RegularExpressions;
    //...
    // просто какая-то строка
    string s = " one two three four two five alice two";
    var m = Regex.Match(s, "two"); // шаблон
     
    // методы:
    Console.WriteLine(m.Index); // вывод: 5
    Console.WriteLine(m.NextMatch().Index); // вывод: 20
    Console.WriteLine(m.NextMatch().NextMatch().Index); // вывод: 35

    Пример 2:

    // подключение области имен RegularExpressions
    using System.Text.RegularExpressions;
    //...
    // просто какая-то строка
    string s = " one two three four two five alice two";
    // используем цикл для прохода по строке
    foreach (Match m in Regex.Matches(s, "two"))
                {
                    Console.Write(m.Index + " "); // вывод: 5 20 35
                }

    Пример 3:

    // подключение области имен RegularExpressions
    using System.Text.RegularExpressions;
    //...
    // просто какая-то строка
    string s = " one two three four two five alice two";
     
    var ss = s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    var ss = Regex.Split(s, " +");
    Console.WriteLine(ss);

Примеры регулярных выражений

Текст для поиска шаблон поиска
asdasdasdhelloasdasdasd @“hello”
hello @“^hello$”
asdasdelholasdasdasd

asdasdeeeeeedfgdgdg

@“[hello]”  or   @“[hello]{6}”
SdfuiyuiewrR345 @“[a-zA-Z0-9]”  or  @“\w”
452341 @“[0-9]”    or   @“\d”
jsdf8H?& @“.”

Example:

string s = "asdasdasdhelloasdasdasd";
Match m = Regex.Match(s, @"hello");
Console.WriteLine(m.Success); //  True 
m = Regex.Match(s, @"^hello");
Console.WriteLine(m.Success); //  False

Метасимволы и экранирование

Следующие метасимволы имеют специальное назначение в регулярных выражениях, поэтому не могут быть просто так использованы в тексте:

(   )   {   }   [   ]   ?   *   +   -   ^   $   .   |   \

Если необходимо прямо в тексте использовать данные символа (например, символ точку (.)), то необходимо выполнить, так сказать, «экранирование». Это можно сделать расположив обратный слэш (\) перед самим символом.

Конечно, \ также является escape-символом для строковых литералов в C#. Чтобы получить литерал \, необходимо удвоить его в строке (т.e. «\\» — это строка с одним символом). В качестве альтернативы, можно использовать также так называемые дословные строковые литералы, начинающиеся с символа @; в таком случае использовать \ в качестве экранизации не нужно. Т.о. следующие две строки абсолютно одинаковы:

"c:\\Docs\\Source\\a.txt"
@"c:\Docs\Source\a.txt"

Кванторы

    Подстановочные знаки
    Подстановочные знаки объяснение Пример Подходящий текст
    \d цифра 0 до 9 file_\d\d file_25
    \w Символ Unicode, цифра \w\w\w\w A-b_1
    \s «пробельный символ»: любой пробельный символ Unicode a\sb\sc a b
    c
    \D Символ, не являющийся цифрой \D\D\D ABC
    \W Один символ, который не является символом из набора \w \W\W\W\W\W *-+=)
    \S Один символ, который не является пробельным символом из набора \s \S\S\S\S Yoyo
    \b Границы слов
    \B Обратное \b
    . Любой символ, кроме разрыва строки a.c abc
    \. Точка (специальный символ: должен быть экранирован слэшем \) a\.c a.c
    \ Экранирует специальный символ \[\{\(\)\}\] [{()}]
    Кванторы
    Квантор Пояснение Пример Подходящий текст
    + Один или более Version \w-\w+ Version A-b1_1
    {3} Ровно три раза \D{3} ABC
    {2,4} Два или четыре раза \d{2,4} 156
    {3,} три раза или более \w{3,} regex_tutorial
    * ноль или более раз A*B*C* AAACC
    ? один раз или нисколько plurals? plural
    [] Любой символ в фигурных скобках
    | Символ перед | ИЛИ после него cat|dog sdfcatsdf
    Директивы нулевой длины
    ^ Поиск с начала строки
    $ Поиск до конца строки
    \b положение на границе слова

    Замены с помощью регулярных выражений

    string s = "10+2=12";
    s = Regex.Replace(s, @"\d+", "<$0>"); // <10>+<2>=<12>
    s = Regex.Replace(s, @"\d+",
            m => (int.Parse(m.Value) * 2).ToString()); // 20+4=24

     

    Примеры

    Пример 1:
    Для следующего примера подходят слова, начинающиеся с ‘S’

    using System;
    using System.Text.RegularExpressions;
     
    namespace RegExApplication {
       class Program {
          private static void showMatch(string text, string expr) {
             Console.WriteLine("The Expression: " + expr);
             MatchCollection mc = Regex.Matches(text, expr);
     
             foreach (Match m in mc) {
                Console.WriteLine(m);
             }
          }
          static void Main(string[] args) {
             string str = "A Thousand Splendid Suns";
     
             Console.WriteLine("В следующем примере подходят слова, начинающиеся с 'S': ");
             showMatch(str, @"\bS\S*");
             Console.ReadKey();
          }
       }
    }

    Результат:

      подходят слова, начинающиеся с 'S':
      Выражение: \bS\S*
      Splendid
      Suns
      

    Пример 2:
    В следующем примере подойдут слова, начинающиеся с ‘m’ и заканчивающиеся ‘e’

    using System;
    using System.Text.RegularExpressions;
     
    namespace RegExApplication {
       class Program {
          private static void showMatch(string text, string expr) {
             Console.WriteLine("Выражение: " + expr);
             MatchCollection mc = Regex.Matches(text, expr);
     
             foreach (Match m in mc) {
                Console.WriteLine(m);
             }
          }
          static void Main(string[] args) {
             string str = "make maze and manage to measure it";
     
             Console.WriteLine("подойдут слова, начинающиеся с 'm' и заканчивающиеся 'e':");
             showMatch(str, @"\bm\S*e\b");
             Console.ReadKey();
          }
       }
    }

    Результат:

      подойдут слова, начинающиеся с 'm' и заканчивающиеся 'e':
      Выражение: \bm\S*e\b
      make
      maze
      manage
      measure
      

    Пример 3:
    Пример удаляет лишние пробелы

    Live Demo
    using System;
    using System.Text.RegularExpressions;
     
    namespace RegExApplication {
       class Program {
          static void Main(string[] args) {
             string input = "Hello   World   ";
             string pattern = "\\s+";
             string replacement = " ";
     
             Regex rgx = new Regex(pattern);
             string result = rgx.Replace(input, replacement);
     
             Console.WriteLine("Исходная строка: {0}", input);
             Console.WriteLine("Результат: {0}", result);    
             Console.ReadKey();
          }
       }
    }

    Результат:

      Исходная строка: Hello World   
      Результат: Hello World
      

Задания и лабораторные работы

Лабораторная работа 1. Класс Regex и шаблоны
  
Выполнить: Запрашивается номер телефона. Проверьте, является ли введенный номер ростовским номером федерального формата (номер федерального формата: +7 (863) 3**-**-** или +7 (863) 2**-**-**). Где * означает цифру. Напишите функцию, возвращающую логическое (Boolean) значение (true или false).

Указание 1: Строка должна содержать только номер телефона, а регулярное выражение должно содержать при этом кванторы ^ и $.

Указание 2: Так как +, ( и ) символы имеют специальное предназначение в регулярных выражениях, они должны быть экранированы, т.е. перед ними должен быть установлен обратный слэш: \+.

Указание 3: Для того, чтобы удостовериться, что программа работает корректно, необходимо сделать 3 или 4 теста, используя метод Debug.Assert.  

Примерный результат:

Тесты пройдены
Введите номер телефона:
+7 (863) 323-22-12
True
+++++++++++++++++
Тесты пройдены
Введите номер телефона:
7 (863) 323-22-12
False
+++++++++++++++++
Тесты пройдены
Введите номер телефона:
+7 (8634) 323-22-12
False

 
[Название проекта: Lesson_12Lab1, название файла L12Lab1.cs]

✍ Алгоритм:

  1. Создайте проект и переименуйте файл.
  2. Установите курсор в начало кода, после подключения пространств имен и классов. Включите следующее пространство имен для использования методов регулярных выражений:
  3. //...
    using System.Text.RegularExpressions;
    //...
    
  4. Для выполнения автоматических тестов необходимо добавить еще один класс. Поместите следующий код после предыдущего:
  5. //...
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    //...
    
  6. Давайте последовательно, символ за символом, рассмотрим формат номера телефона:
    +7 (863) 3**-**-** or +7 (863) 2**-**-**

    • + : это специальный символ, это означает, что для его использования в нашем шаблоне нам нужно поместить \ для экранизации. Таким образом имеем:
    • \+
      
    • 7 далее идет определенная цифра, то есть нет необходимости использовать квантор или специальный символ. Имеем:
    • \+7
      
    • пробел : Для отображения пробела есть специальный символ — \s; имеем:
    • \+7\s
      
    • ( : это специальный символ, который необходимо экранировать префиксом \:
    • \+7\s\(
      
    • 863 : это определенные цифры, т.е. имеем:
    • \+7\s\(863
      
    • ) : скобку необходимо экранировать символом \:
    • \+7\s\(863\)
      
    • пробел : Для пробела используем подстановочный знак \s:
    • \+7\s\(863\)\s
      
    • 3 или 2 : Для выбора одного из вариантов применяем квантор []:
    • \+7\s\(863\)\s[32]
      
    • любая цифра : За цифры отвечает подстановочный знак \d. Для трех цифр подряд (в формате имеем ***) будем использовать квантор фигурные скобки (()):
    • \+7\s\(863\)\s\d{3}
      
    • -: это конкретный символ, значит, не используем ни квантор, ни подстановочный знак:
    • \+7\s\(863\)\s\d{3}-
      
    • любая цифра : Для любой цифры используем подстановочный знак \d. Для того, чтобы указать, что цифры будет две, будем использовать фигурные скобки с указанием количества:
    • \+7\s\(863\)\s\d{3}-\d{2}
      
    • -: конкретный символ:
    • \+7\s\(863\)\s\d{3}-\d{2}-
      
    • любой символ:
    • \+7\s\(863\)\s\d{3}-\d{2}-\d{2}
      
  7. В задании у нас есть указание, что текст не должен содержать ничего другого, кроме самого телефона. То есть необходимо добавить кванторы начала и конца строки (^ и $):
  8. ^\+7\s\(863\)\s\d{3}-\d{2}-\d{2}$
    
  9. Создайте функцию IsPhonenumber() с одним параметром — вводимой строкой (телефоном); функция возвращает значение типа booleantrue или false:
  10. static bool IsPhonenumber(string number)
      {
        ...
      }
    
  11. Внутри функции будем использовать статический метод IsMatch класса Regex с двумя параметрами: вводимая строка и регулярное выражение.
  12. Метод IsMatch логического типа определяет соответствует ли строка регулярному выражению. Метод возвращает true или false.
    static bool IsPhonenumber(string stringNumber)
      {
        return Regex.IsMatch(stringNumber, @"^\+7\s\(863\)\s\d{3}-\d{2}-\d{2}$");
      }
    
  13. В функции Main вызовите метод IsPhonenumber. Это необходимо сделать, используя автоматический тест: использовать метод Assert класса Debug.
    Assert(bool) метод проверяет предоставленное ему сравнение; если сравнение равно false, он отображает окно сообщения, в котором указывается стек вызовов. Если сравнение true, сообщение об ошибке не отправляется, и окно сообщения не отображается.
  14. Давайте сначала в автоматическом тесте вызовем наш метод с неверным форматом телефона. Чтобы получить результат true будем использовать знак отрицания !:
  15. Debug.Assert(!IsPhonenumber("+7 (800) 231-45-84"));
    
  16. Запустите приложение. Никакого сообщения не появилось. Это означает, что введенный в автоматическом тесте телефон действительно был с неправильным форматом (мы добавили отрицание !).
  17. Затем создадим автоматический тест с верным форматом телефона:
  18. Debug.Assert(IsPhonenumber("+7 (863) 231-45-84"));
    
  19. Затем, опять неверным форматом:
  20. Debug.Assert(!IsPhonenumber("+7 (8631) 21-45-84"));
    

    После всех тестов необходимо добавить оповещение, что все тесты пройдены:

    Console.WriteLine("Тесты пройдены");
    
  21. Запустите приложение. Должно быть только оповещение о том, что тесты пройдены.
  22. Теперь попросим пользователя ввести как-либо номер телефона:
  23. Console.WriteLine("Введите номер телефона:");
    string number = Console.ReadLine();
    Console.WriteLine(IsPhonenumber(number));
    
  24. Запустите приложение и проверьте правильность его исполнения.
  25. Добавьте комментарий с текстом задания и отправьте главный файл на проверку.
Задание 1:

Выполнить: Запросите у пользователя ввести дату. Проверьте, имеет ли введенная дата верный формат: dd-mm-yyyy (если одно из составных чисел dd или mm или yyyy имеет только одну цифру, то необходимо доставлять префикс 0: например, 02)

Указание: Создайте функцию для проверки вводимого текста. В главной программе создайте три или четыре автоматических теста для проверки корректности работы созданной функции. Используйте метод Debug.Assert.
     

Примерный вывод:

Тесты пройдены корректно
Пожалуйста, введите дату:
12/03/1975
Формат даты неверный
+++++++++
Тесты пройдены корректно
Пожалуйста, введите дату:
2-3-1975
Формат даты неверный
+++++++++
Тесты пройдены корректно
Пожалуйста, введите дату:
12-03-1975
Формат даты верный
+++++++++

[Название проекта: Lesson_12Task1, название файла L12Task1.cs]

Лабораторная работа 2. Метод Count класса Regex
  
Выполнить: Создайте функцию, которая подсчитывает количество почтовых индексов внутри строки (известно, что почтовый индекс содержит 6 подряд идущих цифр).

Указание 1: Создайте метод для подсчета индексов.

Указание 2: Для подсчета используйте метод Count класса Regex (информацию можно найти в справке языка C#).

Указание 3: Для проверки корректности работы созданной функции создайте 3 или 4 автоматических теста. Используйте метод Debug.Assert.  

Примерный вывод:

Тесты пройдены
Для строки '123: почтовый индекс 367824 севернее, чем 123712' 
строка содержит 2 почтовых индекса

[Название проекта: Lesson_12Lab2, название файла L12Lab2.cs]

✍ Алгоритм:

  1. Создайте новый проект.
  2. Для использования методов регулярных выражений подключите необходимое пространство имен:
  3. //...
    using System.Text.RegularExpressions;
    //...
    
  4. Для возможности добавления автоматических тестов подключите класс Diagnostics:
  5. //...
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    //...
    
  6. Создайте функцию CountZip с одним аргументом — введенной строкой. Функция должна возвращать целочисленное значение — количество вхождений почтовых индексов:
  7.  
    static int CountZip(string zip)
            {
                ...
            }
    
  8. Теперь создадим шаблон регулярного выражения.
    • Сначала укажем границу слова (\b обозначает начало и конец слова):
    • "\b...\b"
      
    • Почтовый индекс состоит из 6 цифр подряд. Для цифр будем использовать подстановочный знак \d, для обозначение того, что их шесть используем квантор {6}:
    • "\b\d{6}\b"
      
    • Таким образом, имеем шаблон:
    • \b начало слова
      \d{6}   любые цифры, 6 подряд
      \b	конец слова
      
  9. Для того чтобы посчитать сколько именно раз шаблон регулярного выражения будет «входить» в строку будет использовать стандартный метод Matches.
  10. Метод Matches(String) ищет все вхождения регулярного выражения в указанной строке. Метод возвращает коллекцию объектов Match вхождений. Если вхождений не обнаружено, метод возвратит пустую коллекцию.
  11. Расположите следующий код в теле созданной функции:
  12.  
    var m = Regex.Matches(zip, @"\b\d{6}\b");
    return m.Count;
    
  13. Внутри функции Main вызовите функцию CountZip. Но сначала будем использовать автоматические тесты для проверки корректности работы созданной функции: используем метод Assert класса Debug.
    Assert(bool) проверяет условие; в случае если оно ложно, отображается сообщение с ошибкой.
  14. В автоматическом тесте сначала вызовем функцию со строкой с двумя почтовыми индексами. Чтобы в качестве результата получить true нужно в условии указать, что функция вернет значение 2:
  15. Debug.Assert(CountZip("344113 34116 15 152566  14254124    12515 hello") == 2);
    
  16. Запустите приложение. В случае корректности работы функции никакого сообщения появиться не должно было.
  17. Создадим тест с вызовом функции со строкой без почтового индекса. Затем выведем оповещение, что тесты выполнены:
  18. Debug.Assert(CountZip("hello") == 0);
    Console.WriteLine("Тесты пройдены");
  19. Запустите приложение.
  20. Ну и наконец вызовем функцию для конкретной строки. Создадим строковую переменную и присвоим ей значение:
  21. string zipCode = "123: почтовый индекс 367824 севернее, чем 123712";
    Console.WriteLine($"строка содержит {CountZip(zipCode)} почтовых индекса");
  22. Запустите приложение.
  23. Добавьте комментарий с заданием и отправьте файл на проверку.
Задание 2:

Выполнить: Создайте функцию, подсчитывающую количество смайликов в переданной строке.
Смайликом читаются следующие варианты:

  • Первый символ либо ; (точка с запятой) либо : (двоеточие), встречающиеся ровно один раз;
  • затем - (дефис) может встретиться один раз или сколько угодно раз (даже ноль раз);
  • в конце должно быть определенное количество идентичных скобок (по крайней мере 1 раз) из следующего набора скобок: (,), [,];
  • других символов «внутри» смайликов быть не должно.
  •   
    Указание 1: Создайте функцию для подсчета смайликов.

    Указание 2: Используйте метод Count класса Regex.

    Указание 3: Добавьте 3-4 автоматических теста для проверки корректности работы функции. ИспользуйтеDebug.Assert.  

    Примерный вывод:

    Тесты пройдены
    Для строки 'Привет папа :) Я скучаю :-(' 
    имеем 2 смайлика
    

    [Название проекта: Lesson_12Task2, файл L12Task2.cs]

    Лабораторная работа 3. Метод Replace класса Regex
      
    Выполнить: Создайте функцию, удаляющую лишние пробелы в строке (пробелов может быть несколько подряд). Необходимо использовать метод Replace.

    Указание 1: Создайте метод с тремя аргументами: исходная строка, шаблон регулярного выражения, и строка в которой замены. Значение по умолчанию для строки замены должен быть просто пробельный символ " " (т.е. пробел должен быть вставлен вместо любого количества подряд идущих пробелов).

    Указание 2: Использовать метод Replace класса Regex.

    Указание 3: Создайте 3-4 автоматических теста для проверки правильности работы функции.

    Примерный вывод:

    Тесты пройдены
    Исходная строка: 'Hello   World   '
    Результирующая строка: 'Hello World '
    

     
    [Название проекта: Lesson_12Lab3, название файла L12Lab3.cs]

    ✍ Алгоритм:

    1. Создайте новый проект.
    2. Подключите необходимое пространство имен для работы с регулярными выражениями:
    3. //...
      using System.Text.RegularExpressions;
      //...
    4. Для добавления автоматических тестов подключите пространство имен Diagnostics:
    5. //...
      using System.Text.RegularExpressions;
      using System.Diagnostics;
      //...
    6. Создайте функцию ReplaceSpaces с тремя аргументами — исходная строка, шаблон регулярного выражения и строка замены. Функция должна возвращать значение типа string — результирующую строку:
    7. static string ReplaceSpaces(string input, string pattern, string replacement)
              {
                  ...
              }
    8. Создадим шаблон регулярного выражения.
      Для использования пробельного знака в шаблоне есть специальный подстановочный знак \s. Так как в строке может находиться несколько пробелов подряд, то будем использовать квантор +, который обозначает одно или несколько вхождений:
    9. "\s+"
      
    10. В функции Main присвойте шаблон переменной с именем pattern:
    11. string pattern = @"\s+";
    12. Затем создайте переменную для хранения строки замены; несколько пробелов подряд будут заменены на " " — один пробел:
    13. string replacement = " ";
    14. Необходимо использовать метод Replace.
    15. Метод Replace(string input, string replacement): в указанной строке (input string) заменяет все вхождения шаблона на указанную строку замены (string replacement).
    16. Расположите код в теле созданной функции:
    17. Regex rgx = new Regex(pattern);
      string result = rgx.Replace(input, replacement);
      return result;
    18. Перейдите в функцию Main и вызовите созданный метод ReplaceSpaces. Но сначала создадим несколько автоматических тестов: будем использовать метод Assert.
    19. В автоматическом тесте вызовем метод с исходной строкой, включающей несколько пробелов подряд:
    20. Debug.Assert(ReplaceSpaces(" Good day  !",pattern, replacement) == " Good day !");
      Console.WriteLine("Тесты пройдены");
    21. Запустите приложение. В консоли должно появиться только сообщение «Тесты пройдены», что означает, что функция работает корректно.
    22. И наконец, мы должны заменить все подряд идущие пробелы в конкретной строке (из Примерного вывода задания). Объявим строковую переменную и присвоим ей значение:
    23. string input = "Hello   World   ";
      Console.WriteLine($"Original String: {input}");
      Console.WriteLine($"Replacement String: {ReplaceSpaces(input, pattern, replacement)}");
      Console.ReadKey();
    24. Запустите приложение и проверьте его работоспособность.
    25. Добавьте комментарий с заданием и загрузите файл на проверку.