Ekspresi reguler untuk mencocokkan tanda kurung yang seimbang


290

Saya perlu ekspresi reguler untuk memilih semua teks di antara dua kurung luar.

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

Hasil: (text here(possible text)text(possible text(more text)))


3
Pertanyaan ini sangat buruk karena tidak jelas apa yang ditanyakan. Semua jawaban menafsirkannya secara berbeda. @DaveF bisa tolong jelaskan pertanyaannya?
Matt Fenwick

1
Dijawab dalam posting ini: stackoverflow.com/questions/6331065/…
sship21

Jawaban:


144

Ekspresi reguler adalah alat yang salah untuk pekerjaan itu karena Anda berurusan dengan struktur bersarang, yaitu rekursi.

Tetapi ada algoritma sederhana untuk melakukan ini, yang saya jelaskan dalam jawaban untuk pertanyaan sebelumnya .


15
Implementasi .NET memiliki [Balancing Group Definition msdn.microsoft.com/en-us/library/… yang memungkinkan hal semacam ini.
Carl G

22
Saya tidak setuju bahwa ekspresi reguler adalah alat yang salah untuk ini karena beberapa alasan. 1) Kebanyakan implementasi ekspresi reguler memiliki solusi yang bisa diterapkan jika tidak sempurna untuk ini. 2) Seringkali Anda mencoba untuk menemukan pasangan pembatas yang seimbang dalam konteks di mana kriteria lain yang cocok untuk ekspresi reguler juga sedang dimainkan. 3) Seringkali Anda menyerahkan ekspresi reguler ke beberapa API yang hanya menerima ekspresi reguler dan Anda tidak punya pilihan.
Kenneth Baltrinic


20
Regex adalah alat yang BENAR untuk pekerjaan itu. Jawaban ini tidak benar. Lihat jawaban rogal111.
Andrew

4
Sepenuhnya setuju dengan jawabannya. Meskipun ada beberapa implementasi rekursi di regexp, mereka sama dengan mesin negara-terbatas dan tidak disarankan untuk bekerja dengan struktur bersarang, tetapi Tata Bahasa Konteks Gratis melakukan ini. Lihatlah hierarki formal Tata Bahasa Homsky.
Nick Roz

138

Saya ingin menambahkan jawaban ini untuk referensi cepat. Jangan ragu untuk memperbarui.


.NET Regex menggunakan grup penyeimbang .

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

Di mana cdigunakan sebagai penghitung kedalaman.

Demo di Regexstorm.com


PCRE menggunakan pola rekursif .

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

Demo di regex101 ; Atau tanpa pergantian:

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

Demo di regex101 ; Atau belum dibuka untuk kinerja:

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

Demo di regex101 ; Pola disisipkan di (?R)mana mewakili (?0).

Perl, PHP, Notepad ++, R : perl = BENAR , Paket Python : Regex dengan (?V1)untuk perilaku Perl.


Ruby menggunakan panggilan subexpression .

Dengan Ruby 2.0 \g<0>dapat digunakan untuk memanggil pola penuh.

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

Demo di Rubular ; Ruby 1.9 hanya mendukung pengambilan rekursi grup :

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

Demo di Rubular  ( pengelompokan atom sejak Ruby 1.9.3)


JavaScript  API :: XRegExp.matchRecursive

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

JS, Java, dan rasa regex lainnya tanpa rekursi hingga 2 level bersarang:

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

Demo di regex101 . Sarang yang lebih dalam perlu ditambahkan ke pola.
Untuk gagal lebih cepat pada tanda kurung tidak seimbang, jatuhkan +quantifier.


Java : Gagasan menarik menggunakan referensi ke depan oleh @jaytea .


Referensi - Apa arti dari regex ini?


1
Saat Anda mengulangi grup dengan quantifier posesif, tidak ada gunanya membuat grup itu atomik karena semua posisi mundur dalam grup itu dihapus pada setiap pengulangan. Jadi menulis (?>[^)(]+|(?R))*+sama dengan menulis (?:[^)(]+|(?R))*+. Hal yang sama untuk pola selanjutnya. Tentang versi yang belum dibuka, Anda dapat meletakkan quantifier posesif di sini: [^)(]*+untuk mencegah kemunduran (jika tidak ada braket penutup).
Casimir et Hippolyte

Tentang pola Ruby 1.9, alih-alih membuat atom grup berulang (yang memiliki minat terbatas ketika ada banyak tanda kurung bersarang (...(..)..(..)..(..)..(..)..)) dalam string subjek), Anda dapat menggunakan grup non-tangkapan sederhana dan melampirkan semuanya dalam grup atom: (?>(?:[^)(]+|\g<1>)*)( ini berperilaku persis seperti quantifier posesif). Di Ruby 2.x, kuantifier posesif tersedia.
Casimir et Hippolyte

@CasimiretHippolyte Terima kasih! Saya menyesuaikan pola PCRE dan untuk Ruby 1.9, apakah maksud Anda seluruh pola menjadi seperti ini ? Silakan memperbarui diri Anda. Saya mengerti maksud Anda, tetapi tidak yakin apakah ada banyak peningkatan.
gelembung berbandul

117

Anda dapat menggunakan rekursi regex :

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

3
Contoh akan sangat berguna di sini, saya tidak bisa membuat ini berfungsi untuk hal-hal seperti "(1, (2, 3)) (4, 5)".
Andy Hayden

4
@AndyHayden ini karena "(1, (2, 3)) (4, 5)" memiliki dua grup yang dipisahkan dengan ruang. Gunakan regexp saya dengan flag global: / (([^ ()] | (? R)) *) / g. Ini tes online: regex101.com/r/lF0fI1/1
rogal111

1
Saya mengajukan pertanyaan tentang stackoverflow.com/questions/26385984
Andy Hayden

7
NET 4.5 Saya mendapatkan error berikut untuk pola ini: Unrecognized grouping construct.
nam

3
Luar biasa! Ini adalah fitur hebat dari regex. Terima kasih telah menjadi satu-satunya yang benar-benar menjawab pertanyaan. Juga, situs regex101 itu manis.
Andrew

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

[^\(]*cocok dengan semua yang bukan braket pembuka di awal string, (\(.*\))menangkap substring yang diperlukan yang terlampir dalam tanda kurung, dan [^\)]*cocok dengan semua yang bukan braket penutup di akhir string. Perhatikan bahwa ungkapan ini tidak berusaha mencocokkan tanda kurung; pengurai sederhana (lihat jawaban dehmann ) akan lebih cocok untuk itu.


braket di dalam kelas tidak perlu diloloskan. Karena di dalamnya bukan metacharacted.
José Leal

10
Expr ini gagal terhadap sesuatu seperti "teks (teks) teks (teks) teks" kembali "(teks) teks (teks)". Ekspresi reguler tidak dapat menghitung tanda kurung.
Christian Klauser

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

Jika Anda ingin memilih teks di antara dua tanda kurung yang cocok , Anda kurang beruntung dengan ekspresi reguler. Ini tidak mungkin (*) .

Regex ini hanya mengembalikan teks antara pembukaan pertama dan kurung tutup terakhir di string Anda.


(*) Kecuali jika mesin regex Anda memiliki fitur seperti kelompok penyeimbang atau rekursi . Jumlah mesin yang mendukung fitur-fitur tersebut perlahan-lahan bertambah, tetapi mereka masih belum tersedia secara umum.


Apa arti dari tanda-tanda "<=" dan "="? Apa mesin regexp penargetan ekspresi ini?
Christian Klauser

1
Ini adalah melihat-lihat, atau lebih tepatnya "pernyataan nol lebar melihat ke depan / melihat ke belakang". Kebanyakan mesin regex modern mendukungnya.
Tomalak

Menurut contoh OP, ia ingin memasukkan orangtua yang paling luar dalam pertandingan. Regex ini membuangnya.
Alan Moore

1
@Lan M: Anda benar. Tetapi menurut teks pertanyaan, dia menginginkan segalanya di antara orangtua yang paling luar. Pilih pilihan Anda. Dia mengatakan dia telah mencoba selama berjam-jam, jadi bahkan tidak menganggap "segala sesuatu termasuk orangtua terluar" sebagai niat, karena itu sangat sepele: "(. *)".
Tomalak

3
@ ghayes Jawabannya adalah dari 2009. Itu sudah lama sekali; mesin ekspresi reguler yang memungkinkan beberapa bentuk rekursi lebih jarang daripada sekarang (dan mereka masih sangat jarang). Saya akan menyebutkannya dalam jawaban saya.
Tomalak

14

Jawaban ini menjelaskan batasan teoretis mengapa ekspresi reguler bukan alat yang tepat untuk tugas ini.


Ekspresi reguler tidak dapat melakukan ini.

Ekspresi reguler didasarkan pada model komputasi yang dikenal sebagai Finite State Automata (FSA). Seperti namanya, a FSAhanya dapat mengingat keadaan saat ini, ia tidak memiliki informasi tentang keadaan sebelumnya.

OJK

Dalam diagram di atas, S1 dan S2 adalah dua negara di mana S1 adalah langkah awal dan akhir. Jadi jika kita mencoba dengan string 0110, transisi berjalan sebagai berikut:

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

Dalam langkah di atas, ketika kita berada di kedua S2yaitu setelah parsing 01dari 0110, FSA tidak memiliki informasi tentang sebelumnya 0di 01karena hanya bisa mengingat keadaan saat ini dan simbol input berikutnya.

Dalam masalah di atas, kita perlu mengetahui no dari tanda kurung buka; ini berarti harus disimpan di suatu tempat. Tetapi karena FSAstidak bisa melakukan itu, ekspresi reguler tidak dapat ditulis.

Namun, suatu algoritma dapat ditulis untuk melakukan tugas ini. Algoritma umumnya jatuh di bawah Pushdown Automata (PDA). PDAadalah satu tingkat di atas FSA. PDA memiliki tumpukan tambahan untuk menyimpan beberapa informasi tambahan. PDA dapat digunakan untuk memecahkan masalah di atas, karena kita dapat ' push' membuka tanda kurung di tumpukan dan ' pop' mereka begitu kita menemukan tanda kurung penutup. Jika pada akhirnya, tumpukan kosong, lalu buka tanda kurung dan tutup tanda kurung cocok. Kalau tidak, tidak.



1
Ada beberapa jawaban di sini, yang membuktikan, itu mungkin.
Jiří Herník

1
@Marco Jawaban ini berbicara tentang ekspresi reguler dalam perspektif teoretis. Banyak mesin regex sekarang menjadi hari tidak hanya mengandalkan model teoritis ini dan menggunakan beberapa memori tambahan untuk melakukan pekerjaan!
musibs

@ JiříHerník: itu bukan ekspresi reguler dalam arti yang ketat: tidak didefinisikan sebagai ekspresi reguler oleh Kleene . Beberapa mesin ekspresi reguler memang telah menerapkan beberapa kemampuan ekstra, menjadikannya parse lebih dari sekadar bahasa biasa .
Willem Van Onsem

12

Sebenarnya mungkin untuk melakukannya menggunakan .NET regular expressions, tetapi tidak sepele, jadi baca dengan cermat.

Anda dapat membaca artikel yang bagus di sini . Anda juga mungkin perlu membaca di .NET regular expressions. Anda dapat mulai membaca di sini .

Kurung sudut <>digunakan karena mereka tidak perlu keluar.

Ekspresi reguler terlihat seperti ini:

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

4

Ini adalah regex definitif:

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

)*
)
\)

Contoh:

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

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

perhatikan bahwa '(pip'dikelola dengan benar sebagai string. (dicoba di regulator: http://sourceforge.net/projects/regulator/ )


4

Saya telah menulis perpustakaan JavaScript kecil yang disebut seimbang untuk membantu tugas ini. Anda dapat melakukannya dengan melakukan

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

Anda bahkan dapat melakukan penggantian:

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

Berikut ini contoh JSFiddle yang lebih kompleks dan interaktif .


4

Menambahkan ke jawaban gelembung berbandul , ada rasa regex lain di mana konstruksi rekursif didukung.

Lua

Gunakan %b()( %b{}/ %b[]untuk kurung kurawal / kurung kotak):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end(lihat demo )

Perl6 :

Kecocokan beberapa tanda kurung yang tidak tumpang tindih:

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))」)

Tumpang tindih dengan beberapa tanda kurung yang seimbang:

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

Lihat demo .

reSolusi non-regex Python

Lihat jawaban poke untuk Cara mendapatkan ekspresi di antara tanda kurung yang seimbang .

Java solusi non-regex yang dapat disesuaikan

Berikut adalah solusi yang dapat disesuaikan yang memungkinkan pembatas literal karakter tunggal di Jawa:

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;
    }
}

Penggunaan sampel:

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)))]

Lihat demo Java online untuk membuktikannya berfungsi dengan beberapa pertandingan.
Wiktor Stribiżew


3

Anda membutuhkan tanda kurung pertama dan terakhir. Gunakan sesuatu seperti ini:

str.indexOf ('('); - itu akan memberi Anda kejadian pertama

str.lastIndexOf (')'); - terakhir

Jadi, Anda perlu string antara,

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

Jawabannya tergantung pada apakah Anda harus mencocokkan set kurung yang cocok, atau hanya yang terbuka pertama ke penutup terakhir dalam teks input.

Jika Anda harus mencocokkan tanda kurung bersarang, maka Anda memerlukan sesuatu yang lebih dari ekspresi reguler. - lihat @dehmann

Jika hanya terbuka pertama untuk terakhir tutup lihat @ Zach

Putuskan apa yang Anda inginkan terjadi:

abc ( 123 ( foobar ) def ) xyz ) ghij

Anda perlu memutuskan kode apa yang perlu Anda cocokkan dalam kasus ini.


3
Ini bukan jawaban.
Alan Moore

Ya, permintaan untuk perubahan dalam pertanyaan harus diberikan sebagai komentar,
Gangnus

0

karena js regex tidak mendukung kecocokan rekursif, saya tidak dapat membuat pencocokan tanda kurung yang seimbang berfungsi.

jadi ini adalah javascript sederhana untuk versi loop yang membuat string "method (arg)" menjadi array

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)

hasilnya seperti

[ { 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

Sementara begitu banyak jawaban menyebutkan hal ini dalam beberapa bentuk dengan mengatakan bahwa regex tidak mendukung pencocokan rekursif dan sebagainya, alasan utama untuk ini terletak pada akar Teori Komputasi.

Bahasa formulir {a^nb^n | n>=0} is not regular. Regex hanya dapat mencocokkan hal-hal yang merupakan bagian dari rangkaian bahasa reguler.

Baca lebih lanjut @ sini


0

Saya tidak menggunakan regex karena sulit untuk berurusan dengan kode bersarang. Jadi cuplikan ini harus memungkinkan Anda untuk mengambil bagian kode dengan tanda kurung seimbang:

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

Saya menggunakan ini untuk mengekstrak cuplikan kode dari file teks.


0

Saya juga terjebak dalam situasi ini di mana pola bersarang datang.

Ekspresi Reguler adalah hal yang tepat untuk menyelesaikan masalah di atas. Gunakan pola di bawah ini

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


-1

Ini mungkin berguna untuk beberapa:

Parsing parmeter dari string fungsi (dengan struktur bersarang) dalam javascript

Struktur pertandingan seperti:
Parsing parmeter dari string fungsi

  • mencocokkan tanda kurung, tanda kurung siku, tanda kurung, tanda kutip tunggal dan ganda

Di sini Anda dapat melihat regexp yang dihasilkan beraksi

/**
 * 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;
};

Ini tidak sepenuhnya menjawab pertanyaan OP tapi saya pikir mungkin berguna untuk beberapa yang datang ke sini untuk mencari struktur regexp bersarang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.