简单的东西
尽管有不准确的声明,即使用正则表达式这是不可能的,但它确实是。
虽然@cjm 公正地指出,否定正匹配比将负匹配表示为单个模式要容易得多,但这样做的模型已经广为人知,它变成了一个简单的插入问题东西到那个模型。鉴于:
/X/
匹配某物,然后是表达条件的方式
! /X/
在一个单一的、正匹配的模式中是这样写的
/\A (?: (?!X) . ) * \z /sx
因此,假设正模式是
/ (\pL) .* \1 /sxi
相应的消极需求一定是
/\A (?: (?! (\pL) .* \1 ) . ) * \z /sxi
通过简单的替换X.
现实世界的担忧
也就是说,有一些情有可原的担忧,有时可能需要更多的工作。例如,虽然\pL 描述了任何具有 GeneralCategory=Letter 属性的代码点,但它不考虑如何处理诸如 red-violet–colored、'Tisn't 或 fiancée — 后者在其他等效的 NFD 与 NFC 形式中是不同的。
因此,您必须首先对其进行完全分解,这样"r\x{E9}sume\x{301}" 之类的字符串才能正确检测到重复的“字母é's”——即所有规范等效的字素簇单元。
为了解决这些问题,您至少必须首先通过 NFD 分解运行您的字符串,然后再通过\X 使用字素簇,而不是通过. 使用任意代码点。
因此,对于英语,您可能希望在积极匹配中遵循这些行,并根据上面的替换给出相应的否定匹配:
NFD($string) =~ m{
(?
(?= [\p{Alphabetic}\p{Dash}\p{Quotation_Mark}] ) \X
)
\X *
\k
}xi
但即便如此,仍然存在一些悬而未决的问题,例如 \N{EN DASH} 和 \N{HYPHEN} 是否应该被视为等效元素或不同元素。
那是因为写得好,将两个元素如 red-violet 和 colored 连成一个复合词 red-violet–colored,如果一对中的至少一个已经包含连字符,则需要使用 EN DASH 作为分隔符,而不是仅仅使用连字符。
通常,EN DASH 保留给性质相似的化合物,例如 时空权衡。但是,使用 typewriter-English 的人甚至不会这样做,而是使用超大量重载的遗留代码点 HYPHEN-MINUS,用于两者:red-violet-colored。
这仅取决于您的文本是否来自某些 19 世纪的手动打字机——或者它是否代表了在现代排版规则下正确呈现的英文文本。 :)
认真的不区分大小写
您会注意到,我在这里将仅区分大小写的字母视为相同的字母。那是因为我使用了 /i 正则表达式开关,ᴀᴋᴀ (?i) 模式修饰符。
这相当就像说它们与排序规则强度 1 相同——但不完全是,因为 Perl 只使用大小写折叠(尽管 完全 大小写折叠而不是 简单),因为它不区分大小写匹配,而不是比可能首选的第三级更高的排序规则。
主要排序规则强度的完全等价是一个明显更强的陈述,但在一般情况下可能需要完全解决问题。然而,在许多特定情况下, 需要的工作量比问题所需要的工作量要多得多。简而言之,对于实际出现的许多特定情况来说,它是多余的,无论假设的一般情况可能需要多少。
这变得更加困难,因为尽管您可以这样做:
我的 $collator = new Unicode::Collate::Locale::
级别 => 1,
locale => "de__phonebook",
标准化 => undef,
;
if ($collator->cmp("müß", "MUESS") == 0) { ... }
并期望得到正确的答案 - 你做到了,万岁! — 这种强大的字符串比较不容易扩展到正则表达式匹配。
然而。 :)
总结
选择是否对解决方案进行设计不足或过度设计将根据个人情况而有所不同,没有人可以为您决定。
我喜欢 CJM 的否定正匹配的解决方案,我自己,虽然它认为重复的字母有点漫不经心。注意:
while ("de__phonebook" =~ /(?=((\w).*?\2))/g) {
print "字母 在子字符串 中重复。\n";
}
产生:
字母
在子字符串 中重复。
字母 <_> 在子字符串 <__> 中重复。
字母 在子字符串 中重复。
字母 在子字符串 中重复。
这说明了为什么当你需要匹配一个字母时,你应该总是使用\pLᴀᴋᴀ\p{Letter},而不是\w,后者实际上匹配[\p{alpha}\p{GC=Mark}\p{NT=De}\p{GC=Pc}]。
当然,当您需要匹配字母时,您需要使用\p{alpha}ᴀᴋᴀ\p{Alphabetic},这与单纯的字母完全不同——这与流行的误解相反。 :)