簡體   English   中英

如何確保我的正則表達式捕獲只被一對括號包圍?

[英]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]; }

稍微好一點的RexExp-Only解決方案

在OP用一些輸入樣本和預期輸出更新問題之后,我能夠制作一些正則表達式以滿足所有樣本輸入。 像許多正則表達式解決方案一樣,答案通常是多個正則表達式,而不是單個巨型正則表達式。

注意:雖然此解決方案適用於所有OP的樣本輸入,但在各種情況下它都會失敗。 請參閱下面的完整防水解決方案。

基本上這個解決方案涉及首先匹配(sortof)看起來像括號組的東西:

/\(+.+?\)+/g

一旦你得到所有這些,你檢查它們是無效標簽( ((n))(((n)))等,或好的:

if( s.match(/\(\(\d+\)\)/) ) return null;
return s.match(/\(\d+\)/);

您可以在此處看到此解決方案適用於所有OP的示例輸入:

http://jsfiddle.net/Cb5aG/

回答你的編輯

所以你要替換! 這意味着你的問題實際上與這個問題相同。 這也使事情變得容易多了。 我們做的是:

  • 要么匹配((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的正則表達風格非常有限。


這是一個負面的前瞻,然后是一個負面的前瞻:

\((?!\()(\d+)\)(?!\))

正則表達圖像

在Debuggex上實時編輯

這是你想要的嗎?

"(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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM