[英]How can I ensure that my regular expression capture is surrounded by only a single pair of parentheses?
我想要一個正則表達式來匹配由一對括號括起來的數字,例如,它會匹配看起來像這樣的東西:
(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)"]
那么,我們需要匹配那個右括號,但不要消耗它 。 這就是“先行”匹配背后的整個想法(有時稱為“零寬度斷言”)。 這個想法是你確保它在那里,但你沒有把它作為比賽的一部分包括在內,所以它不會阻止角色被包含在未來的比賽中。 在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]; }
在OP用一些輸入樣本和預期輸出更新問題之后,我能夠制作一些正則表達式以滿足所有樣本輸入。 像許多正則表達式解決方案一樣,答案通常是多個正則表達式,而不是單個巨型正則表達式。
注意:雖然此解決方案適用於所有OP的樣本輸入,但在各種情況下它都會失敗。 請參閱下面的完整防水解決方案。
基本上這個解決方案涉及首先匹配(sortof)看起來像括號組的東西:
/\(+.+?\)+/g
一旦你得到所有這些,你檢查它們是無效標簽( ((n))
, (((n)))
等,或好的:
if( s.match(/\(\(\d+\)\)/) ) return null;
return s.match(/\(\d+\)/);
您可以在此處看到此解決方案適用於所有OP的示例輸入:
回答你的編輯
所以你要替換! 這意味着你的問題實際上與這個問題相同。 這也使事情變得容易多了。 我們做的是:
((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
,如果它更方便的話也可以使用它。
原始答案,關於匹配:
你會需要的是lookarounds,但JavaScript不支持lookbehinds。 我在這里解釋了一些更詳細的解決方法。 但由於你的lookbehind僅適用於單個字符,因此檢查字符串的開頭或不同的字符就足夠了。 這導致
/(?:^|[^(])[(](\d+)[)](?:[^)]|$)/
但是還有另一個問題:匹配不能重疊! 在(1)(2)
,引擎匹配(1)(
(因為[^)]
包括匹配中的字符)。 因此, (2)
不能匹配,因為它會與前一個匹配重疊。
所以我們將它從第一場比賽中刪除,將數字后的所有內容放入前瞻:
/(?:^|[^(])[(](\d+)(?=[)](?:[^)]|$))/
但請注意,此解決方案也排除了圍繞它們只有一個雙括號的數字:例如, ((1) abc)
和(abc (2))
和((1) (2))
都不會產生匹配。 如果這不是您要查找的內容,則需要將兩種情況(前面和前面的括號)放在一起進行更改。 為了使這更容易,有助於在數字前面拉前瞻:
/(?:^|[^(]|(?=[(]\d+[)](?:[^)]|$)))[(](\d+)/
令我感到困惑,我知道。 但畢竟,JavaScript的正則表達風格非常有限。
這是你想要的嗎?
"(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)"]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.