我想要一个正则表达式来匹配由一对括号包围的数字,例如,它会匹配如下所示的内容:
(1)
但应该不是 匹配
(1)
这里面:((1))
最初我试过这个:
([^\(])\(([0-9]+)\)([^\)])
但它无法匹配字符串开头或结尾的单括号数字。所以
blah blah (1)
没有返回匹配项,即使它非常清楚地包含 (1)
.这是因为上面的正则表达式查找不在左括号或右括号中的字符,而在字符串的开头或结尾没有要查找的字符。然后我尝试了这个:
([^\(]?)\(([0-9]+)\)([^\)]?)
这成功匹配
(1)
而且还配了(1)
内((1))
,因为它只是忽略了正则表达式中周围的括号。所以这个对于我的需求来说太宽泛了。我会继续试验,如果我找到一个解决方案,我会在这里发布一个解决方案,但任何帮助将不胜感激。有任何想法吗?
请注意:我正在使用 JavaScript。 JavaScript 中不包含某些正则表达式功能。
更新:
我没有明确指出,当匹配很重要时,在括号内捕获数字很重要。 (我希望这不会对下面给出的解决方案产生不利影响,除了让它们更难阅读!)然而,整个
(1)
结果应该被替换,所以匹配两个括号也很重要。所有发人深省的回答都让我针对不同的情况拟定了一堆想要的结果。希望这能让表达式的目的更清楚。
(1)
==> 匹配 '(1)' 并捕获 '1' ((1))
==> 不匹配 (((1)))
==> 不匹配 (1) (2)
==> 匹配 '(1)' 和 '(2)' 并捕获 '1' 和 '2' (1) ((2))
==> 匹配 '(1)' 并捕获 '1' ((1) (2))
==> 匹配 '(1)' 和 '(2)' 并捕获 '1' 和 '2' (1)(2)
==> 匹配 '(1)' 和 '(2)' 并捕获 '1' 和 '2' [理想情况下] 或不匹配 (1)((2))
==> 匹配 '(1)' 并捕获 '1' [理想情况下] 或不匹配 ((1)(2))
==> 匹配 '(1)' 和 '(2)' 并捕获 '1' 和 '2' [理想情况下] 或不匹配 对于最后三个,我说“理想”是因为有宽大处理。第一个结果是首选结果,但如果不可能,我可以忍受根本没有匹配项。我意识到这是一个挑战(在 JavaScript 的 RegExp 限制范围内甚至可能是不可能的),但这就是我将问题提交到这个专家论坛的原因。
最佳答案
强大的解决方案
这个问题可能无法单独使用正则表达式以稳健的方式解决,因为这不是正则文法:平衡括号基本上将其向上移动到乔姆斯基的语言复杂度层次结构。所以要稳健地解决这个问题,你实际上必须编写一个解析器并创建一个表达式树。虽然这听起来令人生畏,但实际上并没有那么糟糕。这是完整的解决方案:
// parse our little parentheses-based language; this will result in an expression
// object that contains the text of the expression, and any children (subexpressions)
// that represent balanced parentheses groups. because the expression objects contain
// start indexes for each balanced parentheses group, you can do fast substition in the
// original input string if desired
function parse(s) {
var expr = {text:s, children:[]}; // root expression; also stores current context
for( var i=0; i<s.length; i++ ) {
switch( s[i] ) {
case '(':
// start of a subexpression; create subexpression and change context
var subexpr = {parent: expr, start_idx: i, children:[]};
expr.children.push(subexpr);
expr = subexpr;
break;
case ')':
// end of a subexpression; fill out subexpression details and change context
if( !expr.parent ) throw new Error( 'Unmatched group!' );
expr.text = s.substr( expr.start_idx, i - expr.start_idx + 1 );
expr = expr.parent;
break;
}
}
return expr;
}
// a "valid tag" is (n) where the parent is not ((n));
function getValidTags(expr,tags) {
// at the beginning of recursion, tags may not be defined
if( tags===undefined ) tags = [];
// if the parent is ((n)), this is not a valid tags so we can just kill the recursion
if( expr.parent && expr.parent.text.match(/^\(\(\d+\)\)$/) ) return tags;
// since we've already handled the ((n)) case, all we have to do is see if this is an (n) tag
if( expr.text.match(/^\(\d+\)$/) ) tags.push( expr );
// recurse into children
expr.children.forEach(function(c){tags.concat(getValidTags(c,tags));});
return tags;
}
您可以在此处查看此解决方案的实际效果:http://jsfiddle.net/SK5ee/3/
在不了解您的应用程序或您尝试执行的操作的所有详细信息的情况下,此解决方案对您来说可能会也可能不会过大。但是,它的优点是您几乎可以使您的解决方案任意复杂。例如,您可能希望能够在输入中“转义”括号,从而将它们从正常的括号平衡方程中移除。或者您可能想要忽略引号内的括号等。使用此解决方案,您只需扩展解析器以涵盖这些情况,并且该解决方案可以变得更加健壮。如果您坚持使用一些巧妙的基于正则表达式的解决方案,如果您需要扩展语法以涵盖这些类型的增强功能,您可能会发现自己碰壁。
原始讨论和幼稚的解决方案
如果我的理解是正确的,你想得到单括号内的数字,但你想排除双括号内的数字。我将进一步假设您只想要这些数字的有序列表。基于此,这就是您要查找的内容:
a) "(1)(2)((3))" => [1,2]
b) " (5) ((7)) (8) " => [5,8]
不清楚的是当括号不平衡时会发生什么,或者当括号内不仅仅是数字时会发生什么。 JavaScript 正则表达式不支持平衡匹配,因此以下情况会导致问题:
"((3) (2)" => [2] (probably we want [3,2]???)
"((3) (2) (4) (5))" => [2,4] (probably we want [3,2,4,5]???)
从最后两个例子中可以清楚地看出,整个事情取决于确定数字前是否有一个或两个括号;不是当括号组关闭时。如果需要处理这些示例,则必须构建一个括号组树并从那里开始。这是一个更难的问题,我不打算在这里解决。
所以,这给我们留下了两个问题:我们如何处理彼此对接的匹配项(
(1)(2)
)以及我们如何处理从字符串开头开始的匹配项( (1)blah blah
)?我们现在将忽略第二个问题,专注于两者中较难的一个。
显然,如果我们不关心括号是否关闭,我们可以通过这种方式得到我们想要的:
" (1)(2)((3)) ".match(/[^(]\(\d+/g) => [" (1", ")(2"]
到目前为止一切顺利,但这可能会产生我们不想要的结果:
" (1: a thing (2)(3)((4)) ".match(/[^(]\(\d+/g) => [" (1)", " (2", ")(3"]
所以我们显然要检查右括号,它适用于这个:
" (1) (2) ((3)) ".match(/[^(]\(\d+\)/g) => [" (1)", " (2)"]
但是当比赛相互对抗时失败:
" (1)(2)((3)) ".match(/[^(]\(\d+\)/g) => [" (1)"]
那么,我们需要的是匹配右括号,但不要使用它。这就是“先行”匹配(有时称为“零宽度断言”)背后的全部想法。这个想法是你确保它在那里,但你不把它作为匹配的一部分,所以它不会阻止字符被包含在 future 的匹配中。在 JavaScript 中,先行匹配是用
(?=subexpression)
指定的。句法:" (1)(2)((3)) ".match(/[^(]\(\d+(?=\))/g) => [" (1", ")(2"]
好的,这样就解决了这个问题!关于如何处理发生在字符串开头/结尾的匹配的更简单的问题。真的,我们所要做的就是使用交替来表示“匹配不是左括号或字符串开头的内容”等:
"(1)(2)((3))".match(/(^|[^(])\(\d+(?=\))/g) => ["(1", ")(2"]
另一种“偷偷摸摸”的方法是填充输入字符串以完全避免问题:
s = "(1)(2)((3))"; // our original input
(" " + s + " ").match(/[^(]\(\d+(?=\))/g) => ["(1", ")(2"]
这样我们就不必为交替而大惊小怪。
好的,这是一个疯狂的长答案,但我将用如何清理我们的输出来结束它。显然,我们不想要那些带有我们不想要的所有额外匹配垃圾的字符串:我们只想要数字。有很多方法可以做到这一点,但这里是我的最爱:
// if your JavaScript implementation supports Array.prototype.map():
" (1)(2)((3)) ".match( /[^(]\(\d+(?=\))/g )
.map(function(m){return m.match(/\d+/)[0];})
// and if not:
var matches = " (1)(2)((3)) ".match( /[^(]\(\d+(?=\))/g );
for( var i=0; i<matches.length; i++ )
{ matches[i] = matches[i].match(/\d+/)[0]; }
稍微好一点的 RexExp-Only 解决方案
在 OP 使用一些输入样本和预期输出更新问题后,我能够制作一些正则表达式来满足所有样本输入。像许多正则表达式解决方案一样,答案通常是多个正则表达式,而不是一个巨大的正则表达式。
注意:虽然此解决方案适用于所有 OP 的示例输入,但在各种情况下它都会失败。有关完整的防水解决方案,请参见下文。
基本上这个解决方案涉及首先匹配(sortof)看起来像括号组的东西:
/\(+.+?\)+/g
获得所有这些后,您可以检查它们是无效标签(
((n))
、 (((n)))
等)还是好的标签:if( s.match(/\(\(\d+\)\)/) ) return null;
return s.match(/\(\d+\)/);
您可以在此处看到此解决方案适用于所有 OP 的示例输入:
http://jsfiddle.net/Cb5aG/
关于javascript - 如何确保我的正则表达式捕获仅被一对括号包围?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17456031/