Это четвертая часть в серии учебных статей регулярных выражений. Он показывает, как комбинация вложенной ссылки (см. Как это регулярное выражение находит треугольные числа?) для "подсчета" внутри утверждений (см. Как мы можем сопоставить a ^ nb ^ n с регулярным выражением Java?) можно использовать для изменения строки. Программно сгенерированный шаблон использует абстракции мета-шаблона (см. Как это регулярное выражение Java обнаруживает палиндромы?). Впервые в серии эти методы используются вместо замены целых строк.
Предоставляются полные рабочие реализации Java и С#. Вдохновенные цитаты включены.
Реверсирование строки с использованием регулярных выражений никогда не казалось хорошей идеей и не было даже сразу очевидным, если бы это было возможно, и если да, то как это можно было бы сделать.
Пока это еще не очень хорошая идея, по крайней мере теперь мы знаем, что это возможно, потому что здесь один из способов сделать это:
С# (также на ideone.com)
using System;
using System.Text.RegularExpressions;
public class TwoDollarReversal {
public static void Main() {
string REVERSE =
@"(?sx) . grab$2"
.Replace("grab$2",
ForEachDotBehind(
AssertSuffix(@"((.) \1?)")
)
);
Console.WriteLine(
Regex.Replace(
@"nietsniE treblA --
hguone llew ti dnatsrednu t'nod uoy ,ylpmis ti nialpxe t'nac uoy fI",
REVERSE, "$2"
)
);
// If you can't explain it simply, you don't understand it well enough
// -- Albert Einstein
}
// performs an assertion for each dot behind current position
static string ForEachDotBehind(string assertion) {
return "(?<=(?:.assertion)*)".Replace("assertion", assertion);
}
// asserts that the suffix of the string matches a given pattern
static string AssertSuffix(string pattern) {
return "(?=.*$(?<=pattern))".Replace("pattern", pattern);
}
}
Java (также на ideone.com)
class TwoDollarReversal {
public static void main(String[] args) {
String REVERSE =
"(?sx) . grab$2"
.replace("grab$2",
forEachDotBehind(
assertSuffix("((.) \\1?)")
)
);
System.out.println(
"taerG eht rednaxelA --\nyrt lliw ohw mih ot elbissopmi gnihton si erehT"
.replaceAll(REVERSE, "$2")
);
// There is nothing impossible to him who will try
// -- Alexander the Great"
}
static String forEachDotBehind(String assertion) {
return "(?<=^(?:.assertion)*?)".replace("assertion", assertion);
}
static String assertSuffix(String pattern) {
return "(?<=(?=^.*?pattern$).*)".replace("pattern", pattern);
}
}
Обе версии С# и Java, похоже, используют один и тот же общий алгоритм с незначительными вариациями только в деталях реферируемой реализации.
Очевидно, что это не лучший, самый простой и эффективный способ изменить строку. Тем не менее, в интересах изучения регулярного выражения; как концептуализировать шаблоны; как работает двигатель, чтобы соответствовать им; как собрать различные части, чтобы построить то, что мы хотим; как сделать это таким образом, чтобы читать и обслуживать; и просто для явной радости узнать что-то новое, можем ли мы объяснить, как это работает?
Приложение: Cheat sheet!
Это краткое описание основных конструкций регулярных выражений:
-
(?sx)
- встроенный флаг modifiers.s
позволяет использовать режим "однострочный", позволяя dot соответствовать ЛЮБОМУ символу (включая символы новой строки).x
позволяет режим свободного пробега, где игнорируются пробелы без пробелов (и#
может использоваться для комментариев). -
^
и$
- это начало и конец строки anchors. -
?
как спецификатор повторения обозначает optional (т.е. нуль или один из). В качестве квантификатора повторения, например,.*?
это означает, что повторение*
(т.е. ноль или более) неохотно/non-greedy. -
(…)
используются для grouping.(?:…)
- не захватывающая группа. Группа захвата сохраняет строку, в которой она соответствует; он позволяет использовать обратные/пересылаемые/вложенные ссылки (например,\1
), замещение замещения (например,$2
) и т.д. -
(?=…)
является положительным lookahead; он имеет право утверждать, что существует совпадение данного шаблона.(?<=…)
- положительный lookbehind; он смотрит влево.