Обзор
На высоком уровне, шаблон соответствует любому одному символу .
, но дополнительно выполняет действие grab$2
, которое фиксирует «мат» обращения персонажа, который был сопоставлен с группой 2. Этот захват выполняется построением суффикс входной строки, длина которой соответствует длине префикса до текущей позиции. Мы делаем это, применяя assertSuffix
к шаблону, который увеличивает суффикс на один символ, повторяя это один раз forEachDotBehind
. Группа 1 фиксирует этот суффикс. Первым символом этого суффикса, захваченным в группе 2, является обратный «помощник» для сопоставляемого символа.
Таким образом, замена каждого совпавшего символа его "сопряжением" имеет эффект обращения строки.
Как это работает: более простой пример
Чтобы лучше понять, как работает шаблон регулярных выражений, давайте сначала применим его к более простому вводу. Кроме того, для нашего шаблона замены мы просто «выбросим» все захваченные строки, чтобы лучше понять, что происходит. Вот версия Java:
System.out.println(
"123456789"
.replaceAll(REVERSE, "[$0; $1; $2]\n")
);
Приведенные выше отпечатки ( как видно на ideone.com ):
[1; 9; 9]
[2; 89; 8]
[3; 789; 7]
[4; 6789; 6]
[5; 56789; 5]
[6; 456789; 4]
[7; 3456789; 3]
[8; 23456789; 2]
[9; 123456789; 1]
Таким образом, например, [3; 789; 7]
означает, что точка соответствует 3
(захвачено в группе 0), соответствующий суффикс - 789
(группа 1), первый символ которого - 7
(группа 2). Обратите внимание, что 7
является 3
"помощником".
current position after
the dot matched 3
↓ ________
1 2 [3] 4 5 6 (7) 8 9
\______/ \______/
3 dots corresponding
behind suffix of length 3
Обратите внимание, что "помощник" персонажа может быть справа или слева. Персонаж может даже быть его собственным "помощником".
Как создается суффикс: вложенная ссылка
Шаблон, отвечающий за сопоставление и построение растущего суффикса, выглядит следующим образом:
((.) \1?)
|\_/ |
| 2 | "suffix := (.) + suffix
|_______| or just (.) if there's no suffix"
1
Обратите внимание, что в определении группы 1 есть ссылка на себя (с \1
), хотя это необязательно (с ?
). Необязательная часть предоставляет «базовый случай», способ сопоставления группы без ссылки на себя. Это необходимо, потому что попытка сопоставить ссылку на группу всегда терпит неудачу, когда группа еще ничего не захватила.
Как только группа 1 что-то захватывает, необязательная часть никогда не выполняется в нашей настройке, поскольку суффикс, который мы только что записали в прошлый раз, все еще будет там на этот раз, и мы всегда можем добавить другой символ к началу этого суффикса, используя 1043 *. Этот предваряющий персонаж попадает в группу 2.
Таким образом, этот шаблон пытается увеличить суффикс на одну точку. Повторяя это один раз forEachDotBehind
, вы получите суффикс, длина которого равна длине префикса до нашей текущей позиции.
Как работают assertSuffix
и forEachDotBehind
: абстракции мета-паттернов
Обратите внимание, что до сих пор мы рассматривали assertSuffix
и forEachDotBehind
как черные ящики. Фактически, оставить это обсуждение насовсем - это преднамеренный акт: имена и краткая документация предполагают ЧТО они делают, и этой информации было достаточно, чтобы мы могли написать и прочитать наш шаблон REVERSE
!
При ближайшем рассмотрении мы видим, что реализации этих абстракций в Java и C # немного различаются. Это связано с различиями между двумя двигателями регулярных выражений.
Механизм регулярных выражений .NET допускает полное регулярное выражение во внешнем виде, поэтому эти мета-шаблоны выглядят намного более естественными в этом виде.
AssertSuffix(pattern) := (?=.*$(?<=pattern))
, т. Е. Мы используем упреждающий просмотр, чтобы пройти до конца строки, а затем используем вложенный вид сзади, чтобы сопоставить шаблон с суффиксом.
ForEachDotBehind(assertion) := (?<=(?:.assertion)*)
, т. Е. Мы просто сопоставляем .*
во взгляде, помечая утверждение вместе с точкой внутри группы без захвата.
Так как Java официально не поддерживает просмотр задних частей бесконечной длины (но он все равно работает при определенных обстоятельствах), его аналог немного более неудобен:
assertSuffix(pattern) := (?<=(?=^.*?pattern$).*)
, т. Е. Мы используем взгляд назад, чтобы пройти весь путь до начала строки, а затем используем вложенный взгляд, чтобы сопоставить всю строку , добавляя суффикс шаблон с .*?
, чтобы неохотно соответствовать некоторому неуместному префиксу. forEachDotBehind(assertion) := (?<=^(?:.assertion)*?)
, т. Е. Мы используем закрепленный вид сзади с неохотным повторением, т. Е. ^.*?
(и аналогично помечаем утверждение вместе с точкой внутри группы без захвата).
Следует отметить, что хотя реализация C # этих мета-шаблонов не работает в Java, реализация Java работает в C # ( см. На ideone.com ) , Таким образом, в действительности нет необходимости иметь разные реализации для C # и Java, но реализация C # сознательно использовала преимущества более мощной поддержки .NET regex engine для поддержки более естественного выражения шаблонов.
Таким образом, мы показали преимущества использования абстракций мета-паттернов:
- Мы можем самостоятельно разрабатывать, проверять, тестировать, оптимизировать и т. Д. Эти реализации мета-шаблонов, возможно, используя преимущества специфических для ароматов функций для повышения производительности и / или читабельности.
- После того, как эти строительные блоки разработаны и хорошо протестированы, мы можем просто использовать их как части более крупного шаблона, что позволяет нам высказывать идеи на более высоких уровнях для более удобочитаемых, более удобных в обслуживании и более переносимых решений.
- Мета-шаблоны способствуют повторному использованию, а программная генерация означает, что дублирования становится меньше
Хотя это конкретное проявление концепции довольно примитивно, возможно также пойти дальше и разработать более надежную структуру генерации программных шаблонов с библиотекой хорошо протестированных и оптимизированных мета-шаблонов.
Смотри также
Заключительные мысли
Необходимо повторить, что обращение строки с регулярным выражением НЕ хорошая идея на практике. Это намного сложнее, чем необходимо, и производительность довольно низкая.
Тем не менее, эта статья показывает, что это МОЖЕТ на самом деле быть выполненным, и что при выражении на более высоких уровнях с использованием абстракций мета-паттернов решение на самом деле вполне читабельно. В качестве ключевого компонента решения вложенная ссылка снова демонстрируется в том, что, как мы надеемся, является еще одним привлекательным примером.
Менее ощутимо, возможно, статья также показывает решимость, необходимую для решения проблемы, которая на первый взгляд может показаться сложной (или даже «невозможной»). Возможно, это также показывает ясность мысли, которая приходит с более глубоким пониманием предмета, результатом многочисленных исследований и тяжелой работы.
Без сомнения, регулярное выражение может быть пугающим предметом, и, конечно, оно не предназначено для решения всех ваших проблем. Однако это не оправдывает ненавистное невежество, и это удивительно глубокий источник знаний, если вы готовы учиться.