Найти в Дзене

Поиск в тексте. Часть 3.

По сравнению с простым поиском отдельных слов, регулярные выражения конечно выглядят очень мощными, может показаться что теперь то уж с ними можно найти все что угодно. Но на деле все еще остается много задач где не справляются даже регулярные выражения. Например вот, наверно самый популярный пример: есть текст вроде "абв(гдеёж(зийк)л(мн)опр)стуфх". Тут внутри скобок написан текст и еще другие вложенные пары скобок. Нам нужно взять самую первую открывающую скобку, взять соответствующую ей закрывающую скобку и найти весь текст внутри этих скобок. Решения этой задачи с помощью регулярок либо ограничены, например не работают с любыми, заранее неизвестными уровнями вложенности скобок, либо представляют из себя заумные нечитаемые извращения. Для более сложных видов поиска используются программы под названием парсеры, а сам процесс поиска называется парсинг. С ними возможности становятся прям бесконечными. Можно найти в тексте все числа которые делятся на 56, среди всех слов найти именно им
Оглавление

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

Например вот, наверно самый популярный пример: есть текст вроде "абв(гдеёж(зийк)л(мн)опр)стуфх". Тут внутри скобок написан текст и еще другие вложенные пары скобок. Нам нужно взять самую первую открывающую скобку, взять соответствующую ей закрывающую скобку и найти весь текст внутри этих скобок.

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

Парсеры

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

В зависимости от принципа работы парсинг может быть двух видов: нисходящий и восходящий.

Нисходящий парсинг

Допустим есть текст "абв(гдеёж(зийк)л(мн)опр)стуфх". Нам нужно построить иерархию, в зависимости от скобок:

-2

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

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

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

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

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

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

Восходящий парсинг

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

Вот снова наша строка "абв(гдеёж(зийк)л(мн)опр)стуфх". Берем первый символ "а" и смотрим, на что это похоже? Это похоже на набор из одной буквы). Хорошо, идем дальше. "аб" тоже похоже на набор букв, как и "абв". А вот "абв(" это уже не набор букв, ведь там появилась скобка. Значит берем "абв", убираем его из строки и откладываем отдельно, при этом пометив что это "набор букв". Идем дальше. "(" это открывающая скобка, ее мы тоже откладываем отдельно, пометив что это именно открывающая скобка. Дальше мы снова находим набор букв "гдеёж", открывающую скобку, набор букв "зийк". И вот тут начинается самое интересное. Мы нашли закрывающую скобку и отложили ее отдельно. Теперь смотрим на то что мы там отложили и думаем, а нельзя ли эти мелкие куски как то объединить? И действительно, открывающую скобку, набор букв "зийк" и закрывающую скобку можно соединить в одну большую штуку - "текст в скобках".

Подобным образом мы идем дальше, разбирая по ходу что попадется, а там попадется еще один "текст в скобках" - "(мн)", и доходим до последней закрывающей скобки. Теперь мы можем объединить открывающую скобку, буквы "гдеёж", текст в скобках "зийк", букву "л", текст в скобках "мн", буквы "опр" и закрывающую скобку в один большой "текст в скобках".

Остается разобрать последние буквы "стуфх" и подвести итог: мы снова разобрали строку текста и получили иерархию.