【发布时间】:2011-04-11 06:11:08
【问题描述】:
这是一系列教育正则表达式文章的第四部分。它展示了嵌套引用(参见:How does this regex find triangular numbers?)与断言(参见:How can we match a^n b^n with Java regex?)中“计数”的组合如何用于反转字符串。以编程方式生成的模式使用元模式抽象(参见:How does this Java regex detect palindromes?)。在该系列中,这些技术首次用于替换而不是整个字符串匹配。
提供了完整的 Java 和 C# 实现。包括励志名言。
使用正则表达式反转字符串似乎从来都不是一个好主意,如果它完全可能的话,甚至不是立即显而易见的,如果是的话,人们可能会如何尝试这样做。
虽然仍然不是一个好主意,但至少现在我们知道这是可能的,因为这是一种方法:
C# (also on 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 (also on 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);
}
}
C# 和 Java 版本似乎使用相同的整体算法,仅在抽象的实现细节上略有不同。
显然这不是反转字符串的最佳、最直接、最有效的方法。也就是说,为了了解正则表达式;如何概念化模式;引擎如何工作以匹配它们;如何将各个部分组合在一起以构建我们想要的东西;如何以可读和可维护的方式这样做;只是为了学习新事物的纯粹乐趣,我们能解释一下这是如何工作的吗?
附录:备忘单!
这是对所使用的基本正则表达式结构的简要说明:
-
(?sx)是嵌入标志 modifiers。s启用“单行”模式,允许 dot 匹配 ANY 字符(包括换行符)。x启用 free-spacing 模式,其中未转义的空格将被忽略(#可用于 cmets)。 -
^和$是行首和行尾anchors。 -
?作为重复说明符表示 optional(即零或一)。作为重复量词,例如.*?表示*(即零个或多个)重复是reluctant/non-greedy。 -
(…)用于grouping。(?:…)是非捕获组。一个捕获组保存它匹配的字符串;它允许后退/前进/嵌套引用(例如\1)、替换替换(例如$2)等。 -
(?=…)是积极的lookahead;它看起来向右断言给定模式的匹配。(?<=…)是一个积极的lookbehind;它向左看。
语言参考/其他资源
【问题讨论】:
-
关于meta系列的讨论:meta.stackexchange.com/questions/62695/…
-
嗯,非常漂亮(而且系列概念总体上很有趣)。我认为即使对于那些不熟悉正则表达式的人来说,解释也相当清楚,尽管我希望害怕它们的人在看到“巫毒魔法”后不要逃跑,而不是留下来阅读和学习,呵呵。
-
@Tim:从现在开始,我打算开始写中级的东西,没有什么“高级”的东西。我会继续使用“有趣”的例子来让学习变得更有趣。
-
我喜欢这个,即使没有警告 ;)
-
+1 *。该死的你可变长度的lookbehinds!你可以和他们一起玩得很开心。太糟糕了,我们在 Perl/PCRE 中没有这些。关于正则表达式的一系列问题/答案。 :-) 哦,对于那些感兴趣的人(比如我),完整的 C# 表达式是:
(?sx) . (?<=(?:.(?=.*$(?<=((.) \1?))))*)
标签: c# java regex lookaround nested-reference