3 Работа с регулярными выражениями

3.1 Данные

Задача: импортируйте датасет data_ficbook_mass_effect.csv. Создайте копию, в которой не будет колонки text, либо сразу импортируйте без нее.

library(data.table)
mass_effect <- fread('data_ficbook_mass_effect.csv')
mass_effect_short <- mass_effect[, .SD, .SDcols = -'text']

3.2 Регулярные выражения

Регулярные выражения (англ. Regular Expressions, regexps)- это специальный язык для описания шаблонов строк, который используется для поиска определенных строк, проверки их на соответствие какому-либо шаблону и другой подобной работы.

Реализация регэкспов в разных языках программирования может различаться, в R используется расширенная версия регулярных выражений (ERE, стандарт POSIX 1003.2) с некоторыми собственными дополнениями, а так же Perl-совместимые регулярные выражения (PCRE 8.36). Согласно стандарту POSIX 1003.2, длина регулярных выражений не может превышать 256 байтов. Впрочем, как показывает практика, с таким ограничением мало кто сталкивается в своей работе.

С регулярными выражениями есть, к сожалению, одна достаточно неприятная особенность - если выражение написано неправильно (в частности, при использовании метасимволов), то в результате, в зависимости от контекста использования, будет либо пропуск нужных элементов, либо просто отсутствие какого-либо эффекта. Поэтому есть смысл упрощать работу с регулярными выражениями, особенно если нет достаточного опыта их использования. Например, разбивать делать изменение части строки в несколько этапов, или же просто тестировать регулярные выражения небольшими блоками.

3.3 Символы

В большинстве своем символы в регулярных выражениях обозначают сами себя, за некоторыми исключениями. Каждый символ в паттерне задает соответствие только одному символу в строке, поэтому в тех случаях, когда необходимо указать какой-то символ из определенного набора или указать, что какой-то символ повторяется некоторое количество раз подряд, следует использовать символьные классы или квантификаторы соответственно.

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

Во-вторых, некоторые группы символов могут просто иметь дополнительные фиксированные значения. Так, для кодирования перехода на новую строку, табуляции и некоторых других непечатаемых символов используются определенные обозначения: \n, \r, \t, \v, \f. Соответственно, если в строке встретится какое-то из этих сочетаний, то строка будет прочитана и обработана не как строка, содержащая символы обратного слеша и буквы, а как строка с спец.символом (в случае с табуляцией - \t). Особенность парсера R в том, что символ \ необходимо дополнительно экранировать, тоже обратным слешом. Это значит, что если в паттерне необходимо использовать \v, то в паттерне регулярных выражений в R он указывается как \\v.

Задача: у вас есть вектор vec <- c('abd', 'bbc', 'ecd', 'ebbba'). С помощью функции gsub() замените все буквы a на символ _.

Решение:

vec <- c('abd', 'bbc', 'ecd', 'eba')
gsub('a', '_', vec)
## [1] "_bd" "bbc" "ecd" "eb_"

Задача: у вас есть вектор vec <- c('abd', 'bbc', 'ecd', 'ebbba'). С помощью функции gsub() замените сочетание bb на символ _. Решение:

vec <- c('abd', 'bbc', 'ecd', 'ebbba')
gsub('bb', '_', vec)
## [1] "abd"  "_c"   "ecd"  "e_ba"

С помощью функций grep() / grepl() выберите и посчитайте, в каком количестве строк в колонке link встречается строка source=premium. При желании можете использовать близкие по смыслу функции пакета stringr (str_detect() / str_which()).

Посчитайте, в каком количестве строк не встречается упоминание Тангейзера.

Из ссылки на пользователя вида /authors/4039177 извлеките идентификатор пользователя. Запишите его в колонку author_id.

Выберите текст пролога фанфика ‘Песнь Виктории’ и запишите в новый объект victory_song. Удалите символы переноса строки (чтобы их увидеть, выведите объект на печать).

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

3.4 Классы символов

Задачи на поиск конкретного символа с помощью регулярных выражений встречаются, но обычно требуется большая гибкость поиска. Например, не конкретный символ или сочетание символов, а какой-либо символ из набора - знаки пунктуации или же цифры.

Cамостоятельно указать какой-то набор символов можно с помощью квадратных скобок (квадратные скобки как метасимволы регулярных выражений и квадратные скобки в R - это разные конструкции). При этом можно либо просто перечислить все символы набора, либо, если они принадлежат какому-то известному множеству (например, цифры), то указать первый и последний элементы множества через знак дефиса (-). Также можно сочетать сокращенные формы записи наборов символов. Символы кириллического и латинского алфавитов - разные множества, точно также как строчные и прописные буквы.

Внутри классов поведение метасимволов может различаться в зависимости от места их указания в наборе символов класса. Например, ^ первым символов в наборе символов задает логическое отрицание не из этих символов, и чтобы избежать такого поведения, знак ^ надо поставить на любое место кроме первого в цепочке. Знак ], если есть необходимость его вкючения в набор символов, наоборот, надо ставить первым в наборе, в противном случае он будет проинтерпретирован как завершение класса. Знак -, если указан не первым и не последним, интерпретируется как знак интервала в известном диапазоне символов (например, [0-9] означает от 0 до 9, а [09-] - символы 0, 9 и -)

Из строки my_string удалите все цифры.

my_string <- 'D9586bNd879мрЯпп'

Из строки my_string <- ‘D9586bNd879мрЯпп’ удалите все цифры и кириллические буквы.

3.5 Символьные классы в POSIX

Стандарт POSIX 1003.2 поддерживает несколько определенных обозначения для часто используемых символьных классов:

  • [:alnum:]: все буквы и цифры, сочетание символьных классов [:alpha:] и [:digit:].

  • [:alpha:]: буквы алфавита в обоих регистрах, для прописных и строчных букв есть отдельные символьные классы: [:lower:] и [:upper:].

  • [:punct:]: знаки пунктуации, !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~..

  • [:digit:]: арабские цифры 0123456789.

  • [:xdigit:]: цифры в шестнадцатеричном формате, 0123456789ABCDEFabcdef.

  • [:graph:]: графические знаки, объединенный класс, состоящий из классов [:alnum:] и [:punct:].

  • [:print:]: печатаемые знаки, класс [:graph:], дополненный пробелом.

  • [:blank:]: непечатаемые символы (пробел, знак табуляции, в зависимости от локали - неразрывный пробел и возможные другие непечатаемые символы).

  • [:cntrl:]: управляющие символы (в таблицах символов ANSCII коды 000-031 и 121 в десятичной, 000-037 и 177 в восьмеричной системе, 000-07F в шестнадцатеричной системах счислений)

  • [:space:]: некоторые управляющие символы, которые используются для создания разрывов между символами - пробел, переход на новую строку, возврат каретки, перевод страницы и т.д. В зависимости от локали может содержать дополнительные знаки.

При указании символьного класса с помощью его имени надо помнить, что обозначение класса также заключается в []. То есть, вместо [:digit:] (обозначение класса) надо использовать [[:digit:]] (указание символьного класса в рег.выражении):

gsub('[[:digit:]]', '', my_string)
## [1] "DbNdмрЯпп"

3.6 Дополнительные символьные классы

Помимо обозначений символьных классов, которые поддерживаются стандартом POSIX, также в R реализован ряд собственных символьных классов, которые, как правило, обозначают наиболее часто используемые комбинации:

  • \\s - пробел

  • \\S: все знаки, кроме пробела. Аналогично [^[:space:]] или [^\\s]

  • \\d: цифры, аналогично [0-9]

  • \\D: не цифры, обратно \\d, аналогично [^0-9]

  • \\w: символы, которые используются в письме, аналогично [A-z0-9_]

  • \\W: обратный \\w набор, символы, которые не используются в письме

Из victory_song удалите все знаки препинания (замените на пробел). Попробуйте это сделать как просто с помощью классов символов, так и с помощью POSIX-классов.

Задание повышенной сложности: выберите из victory_song все слова, в которых встречаются заглавные буквы.

3.7 Метасимволы

Ряд символов имеет дополнительное значение - эти символы используются не только сами по себе, но и как определенные конструкции языка регулярных выражений. В частности, это символ [, который используется для создания произвольного символьного класса. Полный список выглядит следующим образом:

  • .: подстановочный знак (wildcard), используется в тех случаях, когда необходимо указать, что на этом месте может быть любой знак

  • \: используется для экранирования метасимволов

  • | : логический оператор или

  • ( и ): используются для указания групп символов

  • [: вместе с ] используется для указания символьных классов

  • ^: якорь, указывающий на начало строки, а так же логический оператор отрицания, используемый в символьных классах

  • $: якорь, указывающий на конец строки

  • *, + и ? - квантификаторы, указывающие, что предыдущий символ или группа символов могут или должны повториться некоторое количество раз

  • {: вместе с } исползуется как квантификатор, указывающий конкретное количество повторений предыдущего символа или группы символов

  • < и >: используется в Perl-совместимых регулярных выражениях

Для того, чтобы эти метасимволы воспринимались не как элементы языка регулярных выражений, а как есть, их необходимо экранировать двумя символами \\. Необходимость в двух символах \\ для экранирования возникает из-за того, что строковая запись регулярного выражения в R и собственно выражение на языке регулярных выражений несколько различаются.

Удалите из строки metachara.cters символы . и .

3.8 Квантификаторы

Для того, чтобы в паттерне указать, что какой-то символ или группа символов может повторяться, используют специальные управляющие знаки - квантификаторы:

  • ?: предыдущий символ или группа символов в паттерне может встречаться 0 или 1 раз. Также вместе с другими квантификаторами используется для нежадного поиска.

  • *: предыдущий символ или группа символов в паттерне может встречаться 0 или больше раз.

  • +: предыдущий символ или группа символов в паттерне может встречаться 1 или больше раз.

  • {n}: предыдущий символ или группа символов может встречаться в паттерно строго n раз.

  • {n,}: предыдущий символ или группа символов может встречаться в паттерно n и более раз. Конструкции {0,} и {1,} тождественны * и + соответственно.

  • {n,m}: предыдущий символ или группа символов может встречаться в паттерно n раз, но не более, чем m раз. Конструкции {0,1} и ? тождественны.

Простейший пример использования квантификаторов - когда надо обработать какой-то символ, который встречается несколько раз. Например, скрыть последние четыре цифры в номере телефона:

phone_number <- 'my phone number: +7-929-138-5896'
gsub('[0-9]{4}', 'XXXX', phone_number)
## [1] "my phone number: +7-929-138-XXXX"

В объекте victory_song замените повторяющиеся пробелы на один пробел.

3.9 Логические операции

В регулярных выражениях логические операции представлены в достаточно ограниченном виде, можно даже сказать, крайне бедно. Наиболее очевидная из существующих операции - это ИЛИ, когда надо задать несколько вариантов комбинаций символов. В частности, это крайне полезный инструмент для фильтрации вектора строковых значений по определенному критерию. Например, регулярными выражениями с логическим оператором можно просто выделить из списка логов те файлы, которые были созданы в 2015-2016 годах:

models <- c('log_2014.csv', 'log_2015.csv', 'log_2016.csv', 'log_2017.csv', 'log_2018.csv')
models[grep('2015|2016', models)]
## [1] "log_2015.csv" "log_2016.csv"

Второй логический оператор - ^, используется в тех случаях, когда надо исключить символы определенного класса. Следует помнить, что ^ используется в этом значении сугубо внутри [], в противном случае будет интерпретироваться в другом значении.

# заменяем на '_' любой из символов d, e, f
gsub('[def]', '_', 'fadbcdefghe')
## [1] "_a_bc___gh_"
# заменяем на '_' все, кроме символов d, e, f
gsub('[^def]', '_', 'fadbcdefghe')
## [1] "f_d__def__e"

3.10 Якори

Якори используются для обозначения, что сочетание символов в паттерне обязательно должно начинать или завершать строку. Также есть якори, которые маркируют начало или конец не строки, а слова, однако само определение слова зависит от используемой локали. В R используются следующие якори:

  • ^ и $: начало и конец строки соответственно

  • \\< и \\>: начало и конец слова

  • \\b: пустая строка или края слова

  • \\B: не край слова

Задание: удалите пробелы в начале и в конце victory_song. Используйте логические операторы

Выберите все фанфики, у которых имя автора начинается с заглавной латинской буквы. Используйте для этого таблицу без текста фанфиков (тексты очень большие, утяжеляют работу с данными).

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

3.11 Группы

Квантификаторы обычно работают относительно предыдущего указанного символа. Однако нередко возникают ситуации, когда требуется провести какую-то операцию над определенной группой символов. В таких случаях используют знаки группировки - ( и ). Набор символов и/или выражение, заключенные в скобки, будут восприниматься как неразрывная последовательность символов, и обрабатываться соответствующим образом. Например, если мы хотим удалить из строки строго двукратное повторение группы символов:

my_string <- c('abRababL')

# пробуем удалить пару символов без указания группы
gsub('ab', '_DELETED_', my_string)
## [1] "_DELETED_R_DELETED__DELETED_L"
# группируем символы с помощью ()
gsub('(ab){2}', '_DELETED_', my_string)
## [1] "abR_DELETED_L"

При необходимости всю обрабатываемую строку можно, с помощью символьных классов, групп и прочих инструментов регулярных выражений, представить в паттерне. При подобном представлении группы нумеруются и может быть обработана конкретная группа символов, вызванная по номеру. Номера групп обозначаются как \\1-\\9.

Например:

gsub('([a-z]*)-([0-9]*)', '\\1', 'abra-9998')
## [1] "abra"
gsub('([a-z]*)-([0-9]*)', '\\2', 'abra-9998')
## [1] "9998"

Удалите из названия фанфика текст в скобках, если он идет после собственно названия, используя механизм групп. Например, из Aidu (фанфик переписан) надо сделать Aidu. Проверьте, как выражение работает на строке Открытое письмо от Жорика (он же дредноут «Давящий») автору Архин.