这是一个填字游戏。例子:
- 解决方案是一个以“r”开头并以“r”结尾的 6 个字母的单词
- 因此模式是“r....r”
- 未知的 4 个字母必须从字母“a”、“e”、“i”和“p”的池中抽取
- 每个字母必须只使用一次
- 我们有大量候选 6 字母单词
解决方案:“剑杆”或“修复”。
过滤模式“r....r”是微不足道的,但是在“未知”插槽中找到也有 [aeip] 的单词超出了我的范围。
这个问题适合正则表达式,还是必须通过详尽的方法来完成?
尝试这个:
r(?:(?!\1)a()|(?!\2)e()|(?!\3)i()|(?!\4)p()){4}r
...或者更具可读性:
r
(?:
(?!\1) a () |
(?!\2) e () |
(?!\3) i () |
(?!\4) p ()
){4}
r
空组用作复选标记,在每个字母被消耗时打勾。例如,如果要匹配的单词是repair
,则 thee
将是此构造匹配的第一个字母。e
如果正则表达式稍后尝试匹配另一个,则该替代将不匹配它。负前瞻(?!\2)
将失败,因为第 2 组已经参加了比赛,更不用说它没有消耗任何东西。
真正酷的是它同样适用于包含重复字母的字符串。举个redeem
例子:
r
(?:
(?!\1) e () |
(?!\2) e () |
(?!\3) e () |
(?!\4) d ()
){4}
m
在第一个e
被消耗后,第一个替代品被有效地禁用,所以第二个替代品取而代之。等等...
不幸的是,这种技术不适用于所有正则表达式。一方面,他们并不都将空/失败的组捕获视为相同的。ECMAScript 规范明确指出,对非参与组的引用应该总是成功的。
正则表达式风格还必须支持前向引用——即出现在它们在正则表达式中引用的组之前的反向引用。( ref ) 据我所知,它应该可以在 .NET、Java、Perl、PCRE 和 Ruby 中工作。
假设您的意思是未知字母必须在 [aeip] 之间,那么合适的正则表达式可能是:
/r[aeip]{4,4}r/
由于 sed 多正则表达式操作,未完全正则表达式
sed -n -e '/^r[aiep]\{4,\}r$/{/\([aiep]\).*\1/!p;}' YourFile
取组中的模式 4 字母aeip
,r
只保留在子组中没有找到字母的行两次。
用于比较字符串的前端语言是什么,是 java、.net ...
这是使用 java 的示例/伪代码
String mandateLetters = "aeio"
String regPattern = "\\br["+mandateLetters+"]*r$"; // or if for specific length \\br[+mandateLetters+]{4}r$
Pattern pattern = Pattern.compile(regPattern);
Matcher matcher = pattern.matcher("is this repair ");
matcher.find();
为什么不替换每个“。” 在您的原始模式中使用“[aeip]”?
你会得到一个正则表达式 string r[aeip][aeip][aeip][aeip]r
。
这当然可以缩短为r[aeip]{4,4}r
,但在一般情况下实现起来会很痛苦,并且可能不会改进代码。
这并没有解决重复字母使用的问题。如果我正在编码它,我会在正则表达式之外的代码中处理它 - 主要是因为正则表达式会变得比我想要处理的更复杂。
所以“只有一次”部分是关键。列出所有排列显然是不可行的。如果您的语言/环境支持前瞻和反向引用,您可以让自己更轻松一些:
r(?=[aeip]{4,4})(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3).r
仍然很丑陋,但它是这样工作的:
r # match an r
(?= # positive lookahead (doesn't advance position of "cursor" in input string)
[aeip]{4,4}
) # make sure that there are the four desired character ahead
(.) # match any character and capture it in group 1
(?!\1)# make sure that the next character is NOT the same as the previous one
(.) # match any character and capture it in group 2
(?!\1|\2)
# make sure that the next character is neither the first nor the second
(.) # match any character and capture it in group 3
(?!\1|\2|\3)
# same thing again for all three characters
. # match another arbitrary character
r # match an r
这既不优雅也不可扩展。因此,您可能只想使用r([aiep]{4,4})r
(捕获四个关键字母)并确保没有正则表达式的附加条件。
编辑:事实上,如果你只是想确保你有 4 个不相同的字符,上述模式才是真正有用和必要的。对于您的具体情况,再次使用前瞻,有更简单(尽管更长)的解决方案:
r(?=[^a]*a[^a]*r)(?=[^e]*e[^e]*r)(?=[^i]*i[^i]*r)(?=[^p]*p[^p]*r)[aeip]{4,4}r
解释:
r # match an r
(?= # lookahead: ensure that there is exactly one a until the next r
[^a]* # match an arbitrary amount of non-a characters
a # match one a
[^a]* # match an arbitrary amount of non-a characters
r # match the final r
) # end of lookahead
(?=[^e]*e[^e]*r) # ensure that there is exactly one e until the next r
(?=[^i]*i[^i]*r) # ensure that there is exactly one i until the next r
(?=[^p]*p[^p]*r) # ensure that there is exactly one p until the next r
[aeip]{4,4}r # actually match the rest to include it in the result
对于r....m
的池deee
,可以将其调整为:
r(?=[^d]*d[^d]*m)(?=[^e]*(?:e[^e])*{3,3}m)[de]{4,4}m
这确保了d
恰好 1 秒和 3e
秒。
一个更具可扩展性的解决方案(无需为每个字母或位置编写 \1、\2、\3 等)是使用负前瞻来断言每个字符稍后不会出现:
^r(?:([aeip])(?!.*\1)){4}r$
更具可读性:
^r
(?:
([aeip])
(?!.*\1)
){4}
r$
这是一个快速的解决方案,适用于您给我们的情况,但这里有一些额外的限制,以获得更强大的版本:
如果“字母池”可能与字符串结尾共享一些字母,请在前瞻中包含模式结尾:
^r(?:([aeip])(?!.*\1.*\2)){4}(r$)
(可能无法在所有正则表达式风格中按预期工作,在这种情况下,复制粘贴模式的结尾而不是使用\2
)
如果某些字母不仅必须出现一次,而且必须出现不同的固定次数,请为共享此次数的所有字母添加单独的前瞻。例如,带有一个“a”和一个“p”但两个“e”的“r....r”将被这个正则表达式匹配(但“rapper”和“repeer”不会):
^r(?:([ap])(?!.*\1.*\3)|([e])(?!.*\2.*\2.*\3)){4}(r$)
非捕获组现在有 2 个备选方案:([ap])(?!.*\1.*\3)
匹配 "a" 或 "p" 在任何地方都没有跟随,直到另一个结尾,([e])(?!.*\2.*\2.*\3)
它匹配 "e" 在任何地方都没有跟随,直到其他 2 个结尾(所以它在第一个失败如果总共有 3 个,则为一个)。顺便说一句,此解决方案包括上述解决方案,但模式的结尾在这里转移到\3
(另请参阅关于风味的注释)。