我想要一个正则表达式来匹配由一对括号括起来的数字,例如,它会匹配看起来像这样的东西:
(1)
但 匹配<{1}}里面的内容:
(1)
最初我试过这个:
((1))
但它无法在字符串的开头或结尾处匹配单个带括号的数字。因此,([^\(])\(([0-9]+)\)([^\)])
没有返回匹配项,即使它非常明确地包含blah blah (1)
。这是因为上面的正则表达式查找不在打开或关闭括号的字符,当在字符串的开头或结尾时,没有要查找的字符。
然后我尝试了这个:
(1)
这已成功匹配([^\(]?)\(([0-9]+)\)([^\)]?)
,但也匹配(1)
内的(1)
,因为它只是忽略了正则表达式中的周围括号。所以这个太宽泛了我的需求。
我会继续尝试,如果找到一个解决方案,我会在这里发布一个解决方案,但是我会非常感谢任何帮助。有什么想法吗?
请注意:我使用的是JavaScript。 JavaScript中不包含一些正则表达式功能。
更新:
我没有明确指出,当匹配很重要时,在括号内捕获数字很重要。 (我希望这不会对下面给出的解决方案产生负面影响,除了让它们更难阅读之外!)但是,整个((1))
应该被替换为结果,所以匹配两个括号也很重要。
所有发人深省的反应都让我为不同的情况制定了一堆预期的结果。希望这能使表达的目标更加清晰。
(1)
==&gt;匹配'(1)'并捕获'1'
(1)
==&gt;不匹配
((1))
==&gt;不匹配
(((1)))
==&gt;匹配'(1)'和'(2)'并捕获'1'和'2'
(1) (2)
==&gt;匹配'(1)'并捕获'1'
(1) ((2))
==&gt;匹配'(1)'和'(2)'并捕获'1'和'2'
((1) (2))
==&gt;匹配'(1)'和'(2)'并捕获'1'和'2'[理想情况]或不匹配
(1)(2)
==&gt;匹配'(1)'并捕获'1'[理想情况]或不匹配
(1)((2))
==&gt;匹配'(1)'和'(2)'并捕获'1'和'2'[理想情况]或不匹配
对于最后三个人,我说'理想'因为有宽大处理。第一个结果是首选的结果,但如果不可能,我可以忍受根本没有匹配。我意识到这是一个挑战(在JavaScript的RegExp限制中可能甚至是不可能的),但这就是我将问题提交给这个专家论坛的原因。
答案 0 :(得分:5)
这个问题可能无法单独使用正则表达式以健壮的方式解决,因为这不是常规语法:平衡括号基本上将其移动到乔姆斯基的语言复杂性层次结构中。因此,为了有力地解决这个问题,您实际上必须编写解析器并创建表达式树。虽然这可能听起来令人生畏,但实际上并没有那么糟糕。这是完整的解决方案:
// 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} })?
我们现在要忽略第二个问题,把重点放在两者的难度上。
显然,如果我们不关心括号是否已关闭,我们可以通过这种方式得到我们想要的东西:
(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)"]
然后,我们需要匹配那个右括号,但不消耗它。这就是“先行”匹配背后的整个想法(有时称为“零宽度断言”)。这个想法是你确保它在那里,但你没有把它作为比赛的一部分包括在内,所以它不会阻止角色被包含在未来的比赛中。在JavaScript中,使用" (1)(2)((3)) ".match(/[^(]\(\d+\)/g) => [" (1)"]
语法指定前瞻匹配:
(?=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"]
在OP用一些输入样本和预期输出更新了问题之后,我能够制作一些正则表达式来满足所有的样本输入。像许多正则表达式解决方案一样,答案通常是多个正则表达式,而不是单个巨型正则表达式。
注意:虽然此解决方案适用于所有OP的样本输入,但是在各种情况下它都会失败。请参阅下面的完整防水解决方案。
基本上这个解决方案涉及首先匹配(sortof)看起来像括号组的东西:
// 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]; }
一旦获得所有这些内容,您就会检查它们是无效标签(/\(+.+?\)+/g
,((n))
等)还是好标签:
(((n)))
您可以在此处看到此解决方案适用于所有OP的示例输入:
答案 1 :(得分:4)
回答您的修改
所以你要替换!这意味着你的问题实际上等同于this one。这也使事情变得容易多了。我们所做的是:
((number))
并忽略(number)
并替换它第一个选项将自动优先(因为它会向左开始,如果两者都适用),那么该选项将吞噬所有不需要的事件:
"input".replace(/([(][(]\d+[)][)])|[(]\d+[)]/g, function(match, $1) {
if ($1)
return $1;
else
return do_whatever_you_want_with(match);
});
因此,我们有两种情况:匹配((number))
并捕获到群组1
- 或匹配(number)
,让群组1
为undefined
。
替换是通过回调完成的,回调将整个match
作为第一个参数,第一个捕获组作为第二个参数(此处为$1
)。然后我们检查是否使用了$1
- 如果是,我们只需返回它,因此不会替换任何内容。如果没有,我们可以使用match
((number)
)执行任何操作。当然,您也可以将number
仅捕获到另一个变量$2
中,如果它更方便的话,可以使用它。
关于匹配的原始答案:
需要的是外观,但JavaScript不支持lookbehinds。我已经解释了一些更详细的解决方法here。 但由于你的lookbehind仅适用于单个字符,因此检查字符串的开头或不同的字符就足够了。这导致
/(?:^|[^(])[(](\d+)[)](?:[^)]|$)/
但是还有另一个问题:匹配不能重叠!在(1)(2)
中,引擎匹配(1)(
(因为[^)]
包含匹配中的字符)。因此,(2)
无法匹配,因为这会与之前的匹配重叠。
所以我们将它从第一场比赛中删除,将数字后的所有内容放入前瞻:
/(?:^|[^(])[(](\d+)(?=[)](?:[^)]|$))/
但请注意,此解决方案也排除了围绕它们只有一个双括号的数字:例如,((1) abc)
和(abc (2))
以及((1) (2))
都不会产生匹配。如果这不是您要查找的内容,则需要将两种情况(前面和前面的括号)放在一起进行更改。为了使这更容易,有助于将前瞻拉到数字前面:
/(?:^|[^(]|(?=[(]\d+[)](?:[^)]|$)))[(](\d+)/
令人困惑,我知道。但毕竟JavaScript的正则表达风格非常有限。
答案 2 :(得分:2)
答案 3 :(得分:1)
这就是你想要的吗?
"(1)(2)((3))".match(/(\({1}\d+\){1})/g) // === ["(1)", "(2)", "(3)"]
看起来像你想要的,看起来比其他方法更简单,但也许我错过了一些东西......
编辑:错过了一个请求,认为这太容易了......好吧,js正则表达式中有一个限制,它会使这个代码变成熊,所以我会做一些稍微不同的东西来获得理想的结果:
"(1)(2)((3))".match(/(\({1,}\d+\){1,})/g)
.filter(/./.test, /^\(\d\)$/) // == ["(1)", "(2)"]