バランスのとれた括弧に一致する正規表現


290

2つの外側の角かっこの間のすべてのテキストを選択するには、正規表現が必要です。

例: some text(text here(possible text)text(possible text(more text)))end text

結果: (text here(possible text)text(possible text(more text)))


3
何を求めているのかが明確でないため、この質問は非常に貧弱です。すべての答えはそれを異なって解釈しました。@DaveF質問を明確にしていただけますか?
Matt Fenwick、

1

回答:


144

正規表現は、ネストされた構造、つまり再帰を処理しているため、ジョブにとって不適切なツールです。

しかし、これを行う簡単なアルゴリズムがあります。これは、前の質問に対するこの回答で説明しました


15
.NETの実装は[バランシンググループの定義を持っていmsdn.microsoft.com/en-us/library/...この種のものを許可します。
カールG

22
いくつかの理由により、正規表現がこのための不適切なツールであることに同意しません。1)ほとんどの正規表現の実装は、これに対する完全な解決策ではないにしても、実行可能です。2)正規表現に適した他の基準も機能している状況で、バランスの取れた区切り文字のペアを見つけようとすることがよくあります。3)正規表現のみを受け入れる一部のAPIに正規表現を渡していて、選択の余地がない場合がよくあります。
Kenneth Baltrinic、2014年


20
正規表現は、仕事に最適なツールです。この答えは正しくありません。rogal111の回答を参照してください。
アンドリュー、

4
答えに完全に同意します。regexpには再帰の実装がいくつかありますが、それらは有限状態マシンと同等であり、ネストされた構造を処理するためにサポートされていませんが、文脈自由文法はこれを行います。ホムスキーの正式な文法の階層を見てください。
Nick Roz

138

クイックリファレンスとしてこの回答を追加したいと思います。自由に更新してください。


バランスグループを使用した.NET正規表現

\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)

どこc深カウンタとして使用されます。

Regexstorm.comのデモ


再帰パターンを使用したPCRE

\((?:[^)(]+|(?R))*+\)

regex101でのデモ。または代替なし:

\((?:[^)(]*(?R)?)*+\)

regex101でのデモ。またはパフォーマンスのために展開

\([^)(]*+(?:(?R)[^)(]*)*+\)

regex101でのデモ(?R)を表すパターンが貼り付けられ(?0)ます。

PerlやPHP、メモ帳++、 Rperlの= TRUEPythonの正規表現パッケージ(?V1)Perlの振る舞いについて。


部分式呼び出しを使用するRuby

Ruby 2.0 \g<0>を使用すると、完全なパターンを呼び出すことができます。

\((?>[^)(]+|\g<0>)*\)

Rubularでのデモ。Ruby 1.9は、グループの再帰のキャプチャのみをサポートしています

(\((?>[^)(]+|\g<1>)*\))

Rubularでのデモ  (Ruby 1.9.3以降のアトミックグループ


JavaScript  API :: XRegExp.matchRecursive

XRegExp.matchRecursive(str, '\\(', '\\)', 'g');

JS、Java、およびその他の正規表現フレーバーで、再帰なしで最大2レベルのネスト:

\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)

regex101でのデモ。より深い入れ子をパターンに追加する必要があります
括弧のバランスが取れていない場合に速く失敗するには、+数量詞を削除します。


Java@jayteaによる前方参照を使用した興味深いアイデア


参考-この正規表現はどういう意味ですか?


1
所有格指定子を使用してグループを繰り返す場合、そのグループ内のすべてのバックトラック位置が繰り返されるたびに削除されるため、そのグループをアトミックにすることは役に立ちません。したがって、書くこと(?>[^)(]+|(?R))*+は書くことと同じです(?:[^)(]+|(?R))*+。次のパターンについても同じです。展開されたバージョンについて、所有[^)(]*+格量をここに置くことができます:バックトラックを防ぐため(閉じ括弧がない場合)。
Casimir et Hippolyte

Ruby 1.9のパターンについて、代わりに(多くのネストされた括弧がある場合に限られた興味を持って繰り返しグループアトミック作るの(...(..)..(..)..(..)..(..)..)対象文字列中の))、あなたは、単純な非キャプチャグループを使用し、すべての原子団で囲むことができます(?>(?:[^)(]+|\g<1>)*)(これは所有格指定子とまったく同じように動作します)。Ruby 2.xでは、所有格指定子を使用できます。
Casimir et Hippolyte

@CasimiretHippolyteありがとうございます!PCREパターンを調整しました。Ruby1.9の場合、パターン全体がこのようですか?自由に更新してください。私はあなたの意味を理解していますが、多くの改善があるかどうかはわかりません。
ふきだしバブル

117

あなたは正規表現の再帰を使用することができます:

\(([^()]|(?R))*\)

3
ここでは例が本当に役立つでしょう。これを "(1、(2、3))(4、5)"のように機能させることはできません。
Andy Hayden

4
@AndyHaydenは、「(1、(2、3))(4、5)」にスペースで区切られた2つのグループがあるためです。/(([[^()] |(?R))*)/ gのグローバルフラグで正規表現を使用します。こちらがオンラインテストです:regex101.com/r/lF0fI1/1
rogal111

1
この先週、stackoverflow.com
Andy Hayden

7
.NET 4.5では、このパターンに対して次のエラーが発生しますUnrecognized grouping construct
ナム2015年

3
驚くばかり!これは正規表現の優れた機能です。実際に質問に答える唯一の人になってくれてありがとう。また、そのregex101サイトはすばらしいものです。
Andrew

28
[^\(]*(\(.*\))[^\)]*

[^\(]*文字列の最初の開き(\(.*\))角括弧で[^\)]*はないすべてに一致し、角かっこで囲まれた必要な部分文字列を取得し、文字列の終わりにある閉じ角括弧ではないすべてに一致します。この式は角括弧との一致を試みないことに注意してください。単純なパーサー(dehmannの回答を参照)の方が適しています。


クラス内のブラケットはエスケープする必要はありません。内部なので、メタキャラクタではありません。
ホセLealの

10
このexprは、「(text)text(text)」を返す「text(text)text(text)text」などに対して失敗します。正規表現は角括弧を数えることができません。
クリスチャンクラウザー

17
(?<=\().*(?=\))

2つの一致する括弧の間のテキストを選択する場合、正規表現ではうまくいきません。これは不可能です(*)

この正規表現は、文字列の最初の開き括弧と最後の閉じ括弧の間のテキストを返すだけです。


(*)正規表現エンジンにグループのバランス再帰などの機能がない場合。このような機能をサポートするエンジンの数は徐々に増加していますが、それらはまだ一般的に利用可能ではありません。


「<=」および「=」記号はどういう意味ですか?この式はどの正規表現エンジンをターゲットにしていますか?
クリスチャンクラウザー

1
これはルックアラウンド、またはより正確には「ゼロ幅先読み/後読みアサーション」です。最新の正規表現エンジンはそれらをサポートしています。
Tomalak 2009

OPの例によると、彼は最外部の括弧を試合に含めたいと考えています。この正規表現はそれらを捨てます。
アランムーア

1
@アランM:あなたは正しいです。しかし質問文によると、彼は最も外側の括弧ののすべてを望んでいます。選択してください。彼は何時間も努力していたので、「(。*)」のように取るに足らないので、「最も外側の括弧を含むすべてのもの」を意図として考えさえしなかったと言いました。
Tomalak、2009

3
@ghayes答えは2009年からです。それはずっと前のことです。何らかの形の再帰を可能にする正規表現エンジンは、現在よりも一般的ではありませんでした(そして、まだかなり一般的ではありません)。私はそれを私の答えで述べます。
Tomalak、2015年

14

この回答は、正規表現がこのタスクに適したツールではない理由の理論的な制限を説明しています。


正規表現はこれを行うことができません。

正規表現は、と呼ばれるコンピューティングモデルに基づいていFinite State Automata (FSA)ます。名前が示すように、FSAは現在の状態のみを記憶でき、以前の状態に関する情報はありません。

FSA

上の図では、S1とS2が2つの状態で、S1が開始ステップと最終ステップです。したがって、文字列0110で試してみると、遷移は次のようになります。

      0     1     1     0
-> S1 -> S2 -> S2 -> S2 ->S1

私たちは秒であるときに、上記の手順では、S2構文解析した後、すなわち010110、FSAは、以前に関する情報がありません001、それが唯一の現在の状態と次の入力記号を覚えることができるように。

上記の問題では、左括弧の数を知る必要があります。つまり、どこかに保管する必要があります。しかし、それFSAsができないので、正規表現を書くことはできません。

ただし、このタスクを実行するアルゴリズムを作成できます。アルゴリズムは通常、に分類されPushdown Automata (PDA)ます。PDAの1つ上のレベルですFSA。PDAには、追加情報を格納するための追加スタックがあります。PDAは上記の問題を解決するために使用できます。これはpush、スタック内の' '開き括弧と ' pop'が閉じ括弧に遭遇するとそれらを解決できるためです。最後にスタックが空の場合は、左括弧と右括弧が一致します。そうでない場合。



1
ここにはいくつかの答えがありますが、それは確かです。
イジーHerník

1
@Marcoこの回答は、正規表現について理論的な観点から説明しています。現在、多くの正規表現エンジンは、この理論モデルに依存するだけでなく、追加のメモリを使用して作業を行っています。
musibs

JiříHerník@:これらは厳密な意味では、正規表現ではありません。で、正規表現として定義されていませんクリーネ。いくつかの正規表現エンジンは確かにいくつかの追加機能を実装しており、通常の言語だけでなく、より多くを解析できるようにしています
Willem Van Onsem

12

.NET正規表現を使用して実際にそれを行うことは可能ですが、それは簡単ではないので、注意深く読んでください。

ここで素晴らしい記事を読むことができます。また、.NETの正規表現を読む必要がある場合もあります。ここから読み始めることができます

<>エスケープを必要としないため、山かっこが使用されました。

正規表現は次のようになります。

<
[^<>]*
(
    (
        (?<Open><)
        [^<>]*
    )+
    (
        (?<Close-Open>>)
        [^<>]*
    )+
)*
(?(Open)(?!))
>

4

これは決定的な正規表現です:

\(
(?<arguments> 
(  
  ([^\(\)']*) |  
  (\([^\(\)']*\)) |
  '(.*?)'

)*
)
\)

例:

input: ( arg1, arg2, arg3, (arg4), '(pip' )

output: arg1, arg2, arg3, (arg4), '(pip'

'(pip'は文字列として正しく管理されていることに注意してください。(規制機関で試した:http : //sourceforge.net/projects/regulator/


4

このタスクを支援するために、balancedという小さなJavaScriptライブラリを作成しました。あなたはこうすることでこれを達成できます

balanced.matches({
    source: source,
    open: '(',
    close: ')'
});

交換することもできます:

balanced.replacements({
    source: source,
    open: '(',
    close: ')',
    replace: function (source, head, tail) {
        return head + source + tail;
    }
});

次に、より複雑でインタラクティブなJSFiddleの例を示します。


4

ふきだしバブルの答えに加えて、再帰的構成がサポートされている他の正規表現フレーバーがあります。

ルア

%b()(中括弧/角括弧には%b{}/ %b[]を使用):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) endデモを参照)

Perl6

重複しない複数の括弧の一致:

my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)

複数の釣り合った括弧の重複:

say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)

デモを参照してください。

Python re非正規表現ソリューション

括弧内の式を取得する方法については、pokeの回答を参照してください。

Javaのカスタマイズ可能な非正規表現ソリューション

Javaで1文字のリテラル区切り文字を許可するカスタマイズ可能なソリューションを次に示します。

public static List<String> getBalancedSubstrings(String s, Character markStart, 
                                 Character markEnd, Boolean includeMarkers) 

{
        List<String> subTreeList = new ArrayList<String>();
        int level = 0;
        int lastOpenDelimiter = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == markStart) {
                level++;
                if (level == 1) {
                    lastOpenDelimiter = (includeMarkers ? i : i + 1);
                }
            }
            else if (c == markEnd) {
                if (level == 1) {
                    subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
                }
                if (level > 0) level--;
            }
        }
        return subTreeList;
    }
}

使用例:

String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]

複数の一致で機能することの証明については、オンラインJavaデモを参照してください。
WiktorStribiżew2017年


3

最初と最後の括弧が必要です。このようなものを使用してください:

str.indexOf( '(');-最初に出現する

str.lastIndexOf( ')'); - 最後の一つ

したがって、間に文字列が必要です。

String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');

1
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.

This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns.  This is where the re package greatly
assists in parsing. 
"""

import re


# The pattern below recognises a sequence consisting of:
#    1. Any characters not in the set of open/close strings.
#    2. One of the open/close strings.
#    3. The remainder of the string.
# 
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included.  However quotes are not ignored inside
# quotes.  More logic is needed for that....


pat = re.compile("""
    ( .*? )
    ( \( | \) | \[ | \] | \{ | \} | \< | \> |
                           \' | \" | BEGIN | END | $ )
    ( .* )
    """, re.X)

# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.

matching = { "(" : ")",
             "[" : "]",
             "{" : "}",
             "<" : ">",
             '"' : '"',
             "'" : "'",
             "BEGIN" : "END" }

# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.

def matchnested(s, term=""):
    lst = []
    while True:
        m = pat.match(s)

        if m.group(1) != "":
            lst.append(m.group(1))

        if m.group(2) == term:
            return lst, m.group(3)

        if m.group(2) in matching:
            item, s = matchnested(m.group(3), matching[m.group(2)])
            lst.append(m.group(2))
            lst.append(item)
            lst.append(matching[m.group(2)])
        else:
            raise ValueError("After <<%s %s>> expected %s not %s" %
                             (lst, s, term, m.group(2)))

# Unit test.

if __name__ == "__main__":
    for s in ("simple string",
              """ "double quote" """,
              """ 'single quote' """,
              "one'two'three'four'five'six'seven",
              "one(two(three(four)five)six)seven",
              "one(two(three)four)five(six(seven)eight)nine",
              "one(two)three[four]five{six}seven<eight>nine",
              "one(two[three{four<five>six}seven]eight)nine",
              "oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
              "ERROR testing ((( mismatched ))] parens"):
        print "\ninput", s
        try:
            lst, s = matchnested(s)
            print "output", lst
        except ValueError as e:
            print str(e)
    print "done"

0

答えは、一致する大括弧のセットを一致させる必要があるのか​​、それとも単に入力テキストの最初の開始から最後の終了まで一致させる必要があるのか​​によって異なります。

一致するネストされた大括弧を一致させる必要がある場合は、正規表現以外のものが必要です。- @dehmannを参照

最初のオープンから最後のクローズまでの場合は、@ Zachを参照してください

何をしたいかを決定します。

abc ( 123 ( foobar ) def ) xyz ) ghij

この場合、コードが一致する必要があるものを決定する必要があります。


3
これは答えではありません。
アランムーア

はい、質問の変更の要求は、解説として与える必要があります
Gangnus

0

js regexは再帰的な一致をサポートしていないため、括弧の一致を機能させることはできません。

これは「method(arg)」文字列を配列にするループバージョンの単純なjavascriptです。

push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
  let ops = []
  let method, arg
  let isMethod = true
  let open = []

  for (const char of str) {
    // skip whitespace
    if (char === ' ') continue

    // append method or arg string
    if (char !== '(' && char !== ')') {
      if (isMethod) {
        (method ? (method += char) : (method = char))
      } else {
        (arg ? (arg += char) : (arg = char))
      }
    }

    if (char === '(') {
      // nested parenthesis should be a part of arg
      if (!isMethod) arg += char
      isMethod = false
      open.push(char)
    } else if (char === ')') {
      open.pop()
      // check end of arg
      if (open.length < 1) {
        isMethod = true
        ops.push({ method, arg })
        method = arg = undefined
      } else {
        arg += char
      }
    }
  }

  return ops
}

// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)

console.log(test)

結果は

[ { method: 'push', arg: 'number' },
  { method: 'map', arg: 'test(a(a()))' },
  { method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
  { method: 'filter',
    arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
  { method: 'pickBy', arg: '_id,type' },
  { method: 'map', arg: 'test()' },
  { method: 'as', arg: 'groups' } ]

0

正規表現は再帰的マッチングなどをサポートしていないなど、多くの回答がこれを何らかの形で述べていますが、これの主な理由は計算理論のルーツにあります。

形式の言語{a^nb^n | n>=0} is not regular。正規表現は、通常の言語セットの一部を形成するものにのみ一致します。

詳しくはこちら @


0

ネストされたコードを処理することが難しいため、私は正規表現を使用しませんでした。したがって、このスニペットは、括弧で囲まれたコードのセクションを取得できるようにする必要があります。

def extract_code(data):
    """ returns an array of code snippets from a string (data)"""
    start_pos = None
    end_pos = None
    count_open = 0
    count_close = 0
    code_snippets = []
    for i,v in enumerate(data):
        if v =='{':
            count_open+=1
            if not start_pos:
                start_pos= i
        if v=='}':
            count_close +=1
            if count_open == count_close and not end_pos:
                end_pos = i+1
        if start_pos and end_pos:
            code_snippets.append((start_pos,end_pos))
            start_pos = None
            end_pos = None

    return code_snippets

これを使用して、テキストファイルからコードスニペットを抽出しました。


0

ネストされたパターンが発生するこの状況にも行き詰まりました。

正規表現は上記の問題を解決するために正しいことです。以下のパターンを使用

'/(\((?>[^()]+|(?1))*\))/'


-1

これは一部のユーザーに役立つ場合があります。

JavaScriptで関数文字列(ネストされた構造を持つ)からパラメーターを解析します

次のような構造に一致します。
関数文字列からパラメーターを解析する

  • 括弧、角括弧、括弧、一重引用符、二重引用符に一致します

ここでは、生成された正規表現を実際に見ることができます

/**
 * get param content of function string.
 * only params string should be provided without parentheses
 * WORK even if some/all params are not set
 * @return [param1, param2, param3]
 */
exports.getParamsSAFE = (str, nbParams = 3) => {
    const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
    const params = [];
    while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
        str = str.replace(nextParamReg, (full, p1) => {
            params.push(p1);
            return '';
        });
    }
    return params;
};

これはOPの質問に完全に対処するものではありませんが、ネスト構造の正規表現を検索するためにここに来る人には役立つかもしれませんが。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.