web-dev-qa-db-ja.com

Python:ネストされた括弧を正規表現と一致させる方法は?

かっこがネストされている数式のような文字列を照合しようとしています。

import re

p = re.compile('\(.+\)')
str = '(((1+0)+1)+1)'
print p.findall(s)

['(((1 + 0)+1)+1)']

(1 + 0)、((1 + 0)+1)など、囲まれたすべての式に一致させたいと思いました。
(((1 + 0)、私はそれらの世話をすることができます)のような不要なものと一致してもかまいません。

なぜそれがまだ行われていないのですか、どうすればそれを行うことができますか?

13
Cinco

正規表現は可能な限り多くのテキストに一致しようとするため、文字列がすべて消費されます。その文字列の一部で正規表現の追加の一致を検索しません。そのため、答えは1つしかありません。

解決策は、正規表現を使用しないことです。実際に数式を解析しようとしている場合は、実際の解析ソリューションを使用してください。本当に括弧内の部分をキャプチャしたいだけの場合は、(と)が表示されたときにカウントしている文字をループして、カウンタをデクリメントします。

12
Winston Ewert

他の人が言及したように、正規表現はネストされた構造に行く方法ではありません。 pyparsing を使用した基本的な例を示します。

import pyparsing # make sure you have this installed

thecontent = pyparsing.Word(pyparsing.alphanums) | '+' | '-'
parens     = pyparsing.nestedExpr( '(', ')', content=thecontent)

使用例は次のとおりです。

>>> parens.parseString("((a + b) + c)")

出力:

(                          # all of str
 [
  (                        # ((a + b) + c)
   [
    (                      #  (a + b)
     ['a', '+', 'b'], {}   
    ),                     #  (a + b)      [closed]
    '+',
    'c'
   ], {}
  )                        # ((a + b) + c) [closed]
 ], {}  
)                          # all of str    [closed]

(新規ライニング/インデント/コメントは手動で行われます)

編集:Paul McGuireの提案に従って、不要なForwardを削除するように変更しました。

ネストされたリスト形式で出力を取得するには:

res = parens.parseString("((12 + 2) + 3)")
res.asList()

出力:

[[['12', '+', '2'], '+', '3']]
27
phooji

新しい通常のエンジンモジュール Pythonの既存のモジュールを置き換える準備ができています。再帰呼び出しなど、多くの新機能が導入されています。

import regex

s = 'aaa(((1+0)+1)+1)bbb'

result = regex.search(r'''
(?<rec> #capturing group rec
 \( #open parenthesis
 (?: #non-capturing group
  [^()]++ #anyting but parenthesis one or more times without backtracking
  | #or
   (?&rec) #recursive substitute of group rec
 )*
 \) #close parenthesis
)
''',s,flags=regex.VERBOSE)


print(result.captures('rec'))

出力:

['(1+0)', '((1+0)+1)', '(((1+0)+1)+1)']

regexの関連バグ: http://code.google.com/p/mrab-regex-hg/issues/detail?id=78

15
ovgolovin

正規表現言語は、任意にネストされた構造に一致するほど強力ではありません。そのためには、プッシュダウンオートマトン(つまり、パーサー)が必要です。 [〜#〜] ply [〜#〜] など、利用可能なツールがいくつかあります。

Pythonは、独自の構文用に パーサーライブラリ も提供します。これにより、必要な処理が実行される場合があります。ただし、出力は非常に詳細であり、頭を包み込むのに時間がかかります。この角度に興味がある場合は、次の説明でできるだけ簡単に説明します。

_>>> import parser, pprint
>>> pprint.pprint(parser.st2list(parser.expr('(((1+0)+1)+1)')))
[258,
 [327,
  [304,
   [305,
    [306,
     [307,
      [308,
       [310,
        [311,
         [312,
          [313,
           [314,
            [315,
             [316,
              [317,
               [318,
                [7, '('],
                [320,
                 [304,
                  [305,
                   [306,
                    [307,
                     [308,
                      [310,
                       [311,
                        [312,
                         [313,
                          [314,
                           [315,
                            [316,
                             [317,
                              [318,
                               [7, '('],
                               [320,
                                [304,
                                 [305,
                                  [306,
                                   [307,
                                    [308,
                                     [310,
                                      [311,
                                       [312,
                                        [313,
                                         [314,
                                          [315,
                                           [316,
                                            [317,
                                             [318,
                                              [7,
                                               '('],
                                              [320,
                                               [304,
                                                [305,
                                                 [306,
                                                  [307,
                                                   [308,
                                                    [310,
                                                     [311,
                                                      [312,
                                                       [313,
                                                        [314,
                                                         [315,
                                                          [316,
                                                           [317,
                                                            [318,
                                                             [2,
                                                              '1']]]]],
                                                         [14,
                                                          '+'],
                                                         [315,
                                                          [316,
                                                           [317,
                                                            [318,
                                                             [2,
                                                              '0']]]]]]]]]]]]]]]],
                                              [8,
                                               ')']]]]],
                                          [14,
                                           '+'],
                                          [315,
                                           [316,
                                            [317,
                                             [318,
                                              [2,
                                               '1']]]]]]]]]]]]]]]],
                               [8, ')']]]]],
                           [14, '+'],
                           [315,
                            [316,
                             [317,
                              [318, [2, '1']]]]]]]]]]]]]]]],
                [8, ')']]]]]]]]]]]]]]]],
 [4, ''],
 [0, '']]
_

この短い機能で痛みを和らげることができます:

_def shallow(ast):
    if not isinstance(ast, list): return ast
    if len(ast) == 2: return shallow(ast[1])
    return [ast[0]] + [shallow(a) for a in ast[1:]]

>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
[258,
 [318,
  '(',
  [314,
   [318, '(', [314, [318, '(', [314, '1', '+', '0'], ')'], '+', '1'], ')'],
   '+',
   '1'],
  ')'],
 '',
 '']
_

数値はPythonモジュールsymbolおよびtokenから取得されます。これらを使用して、数値から名前へのルックアップテーブルを作成できます。

_map = dict(token.tok_name.items() + symbol.sym_name.items())
_

このマッピングをshallow()関数に折りたたんで、数値の代わりに文字列を操作することもできます。

_def shallow(ast):
    if not isinstance(ast, list): return ast
    if len(ast) == 2: return shallow(ast[1])
    return [map[ast[0]]] + [shallow(a) for a in ast[1:]]

>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
['eval_input',
 ['atom',
  '(',
  ['arith_expr',
   ['atom',
    '(',
    ['arith_expr',
     ['atom', '(', ['arith_expr', '1', '+', '0'], ')'],
     '+',
     '1'],
    ')'],
   '+',
   '1'],
  ')'],
 '',
 '']
_
12
Marcelo Cantos

スタックはその仕事に最適なツールです:-

import re
def matches(line, opendelim='(', closedelim=')'):
    stack = []

    for m in re.finditer(r'[{}{}]'.format(opendelim, closedelim), line):
        pos = m.start()

        if line[pos-1] == '\\':
            # skip escape sequence
            continue

        c = line[pos]

        if c == opendelim:
            stack.append(pos+1)

        Elif c == closedelim:
            if len(stack) > 0:
                prevpos = stack.pop()
                # print("matched", prevpos, pos, line[prevpos:pos])
                yield (prevpos, pos, len(stack))
            else:
                # error
                print("encountered extraneous closing quote at pos {}: '{}'".format(pos, line[pos:] ))
                pass

    if len(stack) > 0:
        for pos in stack:
            print("expecting closing quote to match open quote starting at: '{}'"
                  .format(line[pos-1:]))

クライアントコードでは、関数はジェネレーター関数として記述されているため、forループパターンを使用して一致を展開します。

line = '(((1+0)+1)+1)'
for openpos, closepos, level in matches(line):
    print(line[openpos:closepos], level)

このテストコードは、画面に次のように表示されます。印刷出力の2番目のパラメータが括弧のdepthを示していることに気付きました。

1+0 2
(1+0)+1 1
((1+0)+1)+1 0
5
Benny Khoo

リンクされた回答から:

LilyPond convert-lyユーティリティから(そして私自身が書いた/著作権を持っているので、ここでそれを披露することができます):

def paren_matcher (n):
    # poor man's matched paren scanning, gives up
    # after n+1 levels.  Matches any string with balanced
    # parens inside; add the outer parens yourself if needed.
    # Nongreedy.
    return r"[^()]*?(?:\("*n+r"[^()]*?"+r"\)[^()]*?)*?"*n

convert-lyは、これを正規表現でparen_matcher(25)として使用する傾向があり、ほとんどのアプリケーションではやり過ぎになる可能性があります。ただし、Scheme式の照合に使用します。

はい、指定された制限の後で故障しますが、正規表現にプラグインするだけの機能は、使いやすさの無制限の深さの受け渡しをサポートする「正しい」代替手段を上回ります。

3
user3489112

バランスの取れたペア(たとえば、括弧の)は、正規表現では認識できない言語の例です。

以下は、その理由についての数学の簡単な説明です。

正規表現は、有限状態オートマトン(略してFSM)を定義する方法です。このようなデバイスには、情報を格納するための有限量の可能な状態があります。その状態をどのように使用できるかは特に制限されていませんが、認識できる個別の位置の絶対最大数があることを意味します。

たとえば、状態は、たとえば、一致しない左括弧をカウントするために使用できます。ただし、この種のカウントの状態の量は完全に制限されている必要があるため、特定のFSMは最大でn-1までカウントできます。ここでnはは、FSMが入る可能性があることを示しています。nがたとえば10の場合、FSMが一致できる一致しない左括弧の最大数は10です。左括弧をもう1つ持つことは完全に可能であるため、一致する括弧の完全な言語を正しく認識できるFSMはありません。

だから何?本当に大きいものを選んだとしましょうn?問題は、FSMを説明する方法として、正規表現は基本的に、ある状態から別の状態へのすべての遷移を説明することです。任意のNについて、FSMは2つの状態遷移(1つは左括弧を照合するため、もう1つは右括弧を照合するため)を必要とするため、正規表現自体は少なくともnの定数係数で増加する必要があります

比較すると、次に優れたクラスの言語(文脈自由文法)は、この問題を完全にコンパクトな方法で解決できます。これがBNFの例です

expression ::= `(` expression `)` expression
           |    nothing
 

このような式を解析するための適切なパーサーを作成する必要があります(たとえば、pyparsingを使用)。正規表現は、適切なパーサーを作成するための適切なツールではありません。

0
Andreas Jung

私の解決策は次のとおりです。最も外側の括弧内にコンテンツを抽出する関数を定義し、最も内側の括弧内にコンテンツを取得するまでその関数を繰り返し呼び出します。

def get_string_inside_outermost_parentheses(text):
    content_p = re.compile(r"(?<=\().*(?=\))")
    r = content_p.search(text)
    return r.group() 

def get_string_inside_innermost_parentheses(text):
    while '(' in text:
        text = get_string_inside_outermost_parentheses(text)
    return text
0
Marti

正規表現を使用できますが、再帰を自分で行う必要があります。次のようなものがトリックを行います(質問が言うように、括弧で囲まれたすべての式を見つけるだけでよい場合):

import re

def scan(p, string):
    found = p.findall(string)
    for substring in found:
        stripped = substring[1:-1]
        found.extend(scan(p, stripped))
    return found

p = re.compile('\(.+\)')
string = '(((1+0)+1)+1)'
all_found = scan(p, string)
print all_found

ただし、このコードは「正しい」括弧と一致しません。それを行う必要がある場合は、専用のパーサーを使用したほうがよいでしょう。

0

これはあなたの質問のデモですが、それは不器用ですが、それは機能します

import re s = '(((1+0)+1)+1)'

def getContectWithinBraces( x , *args , **kwargs):
    ptn = r'[%(left)s]([^%(left)s%(right)s]*)[%(right)s]' %kwargs
    Res = []
    res = re.findall(ptn , x)
    while res != []:
        Res = Res + res
        xx = x.replace('(%s)' %Res[-1] , '%s')
        res = re.findall(ptn, xx)
        print(res)
        if res != []:
            res[0] = res[0] %('(%s)' %Res[-1])
    return Res

getContectWithinBraces(s , left='\(\[\{' , right = '\)\]\}')
0
Chen