强大的解决方案
仅使用正则表达式可能无法以稳健的方式解决此问题,因为这不是常规语法:平衡括号基本上将其移至乔姆斯基的语言复杂性层次结构中。所以为了稳健地解决这个问题,你实际上必须编写一个解析器并创建一个表达式树。虽然这听起来可能令人生畏,但实际上并没有那么糟糕。这是完整的解决方案:
// 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)"]
那么,我们需要的是匹配右括号,但不要使用它。这就是“前瞻”匹配(有时称为“零宽度断言”)背后的全部理念。这个想法是你确保它在那里,但你不将它作为匹配的一部分包含在内,因此它不会阻止角色被包含在未来的匹配中。在 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 的示例输入,但在各种情况下它都会失败。有关完整的防水解决方案,请参见下文。
基本上,这个解决方案涉及首先匹配(排序)看起来像括号组的东西:
/\(+.+?\)+/g
一旦你得到所有这些,你检查它们是否是无效标签(((n))
,(((n)))
等),或者是好的标签:
if( s.match(/\(\(\d+\)\)/) ) return null;
return s.match(/\(\d+\)/);
您可以在此处看到此解决方案适用于所有 OP 的示例输入:
http://jsfiddle.net/Cb5aG/