Dengeli parantezlerle eşleşecek düzenli ifade


290

İki dış parantez arasındaki tüm metni seçmek için normal bir ifade gerekir.

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

Sonuç: (text here(possible text)text(possible text(more text)))


3
Bu soru çok zayıf çünkü ne istediğini belli değil. Bütün cevaplar bunu farklı yorumladı. @DaveF soruyu netleştirebilir misiniz?
Matt Fenwick

1
Bu mesaja
cevap

Yanıtlar:


145

Düzenli ifadeler iş için yanlış araçtır, çünkü iç içe geçmiş yapılarla, yani özyineleme ile uğraşıyorsunuz.

Ama bunu yapmak için basit bir algoritma var, ki bu da önceki bir sorunun cevabında tanımladım .


16
.NET'in uygulaması, bu tür şeylere izin veren [Balancing Group Definitions msdn.microsoft.com/en-us/library/… 'a sahiptir.
Carl G

23
Birkaç nedenden dolayı düzenli ifadelerin bunun için yanlış araç olduğunu kabul etmiyorum. 1) Çoğu düzenli ifade uygulaması bunun için mükemmel olmasa bile uygulanabilir bir çözüme sahiptir. 2) Genellikle düzenli ifadelere çok uygun diğer kriterlerin de bulunduğu bir bağlamda dengeli sınırlayıcı çiftleri bulmaya çalışıyorsunuz. 3) Genellikle, düzenli ifadeleri yalnızca düzenli ifadeleri kabul eden bazı API'lara veriyorsunuz ve başka seçeneğiniz yok.
Kenneth Baltrinic


20
Regex iş için SAĞ araçtır. Bu cevap doğru değil. Bkz. Rogal111'in cevabı.
Andrew

4
Cevaba kesinlikle katılıyorum. Regexp'de bazı özyineleme uygulamaları olmasına rağmen, bunlar sonlu durumlu makinelere eşittir ve iç içe yapılarla çalıştığı varsayılmaz, ancak Bağlam Serbest Dilbilgileri bunu yapar. Homsky'nin Resmi Gramer hiyerarşisine bakın.
Nick Roz

138

Bu cevabı çabuk başvurmak için eklemek istiyorum. Güncellemek için çekinmeyin.


Dengeleme gruplarını kullanarak .NET Regex .

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

cDerinlik sayacı olarak nerede kullanılır.

Regexstorm.com'da Demo


PCRE yinelemeli bir desen kullanarak .

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

Regex101'de demo ; Veya değişmeden:

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

Regex101'de demo ; Veya performans için unrolled :

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

Regex101'de demo ; Desen, (?R)temsil edilen yapıştırılır (?0).

Perl, PHP, Notepad ++, R : perl = DOĞRU , Python : Regex paketi ile (?V1)Perl davranış.


Alt ifade çağrılarını kullanarak Ruby .

Ruby 2.0 ile \g<0>tam desen çağırmak için kullanılabilir.

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

Rubular'da Demo ; Ruby 1.9 yalnızca grup yinelemesini yakalamayı destekler :

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

Demo'da Rubular  ( Ruby 1.9.3'ten beri atom gruplaması )


JavaScript  API :: XRegExp.matchRecursive

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

JS, Java ve diğer regex lezzetleri 2 seviyeye kadar iç içe geçme olmadan tekrarlanır:

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

Regex101'de demo . Desene daha derin yuvalama yapılması gerekir .
Dengesiz parantez üzerinde daha hızlı başarısız olmak için +nicelik belirtecini düşürün.


Java : @jaytea'nın ileri referanslarını kullanan ilginç bir fikir .


Referans - Bu normal ifade ne anlama geliyor?


1
Bir grubu iyelik nicelik belirteciyle tekrarladığınızda, bu gruptaki tüm geri izleme konumları her tekrarda silindiğinden bu grubu atomik yapmak işe yaramaz. Yani yazmak yazmakla (?>[^)(]+|(?R))*+aynı şeydir (?:[^)(]+|(?R))*+. Bir sonraki model için aynı şey. Kaydedilmemiş sürüm hakkında, buraya sahipli bir nicelik belirleyici koyabilirsiniz: [^)(]*+geri izlemeyi önlemek için (kapanma braketi yoksa).
Casimir et Hippolyte

Ruby 1.9 modeli hakkında, (...(..)..(..)..(..)..(..)..)konu dizesinde tekrarlanan grup atomunu (çok sayıda iç içe parantez olduğunda sınırlı bir ilgisi vardır ) yapmak yerine, basit bir yakalamayan grup kullanabilir ve tümünü bir atom grubuna dahil edebilirsiniz: (?>(?:[^)(]+|\g<1>)*)( bu tam olarak iyelikçi bir niceleyici gibi davranır). Ruby 2.x'te iyelik nicelik belirteci kullanılabilir.
Casimir et Hippolyte

@CasimiretHippolyte Teşekkürler! Ben PCRE desenleri ayarlandı ve Ruby 1.9 için, söz konusu yöntemi bütün desen demek böyle ? Lütfen kendinizi güncellemekten çekinmeyin. Ne demek istediğinizi anlıyorum, ancak çok fazla gelişme olup olmadığından emin değilim.
bobble kabarcık

118

Normal ifade yinelemesini kullanabilirsiniz :

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

3
Burada bir örnek gerçekten yararlı olacaktır, bunu "(1, (2, 3)) (4, 5)" gibi şeyler için kullanamıyorum.
Andy Hayden

4
@AndyHayden bunun nedeni "(1, (2, 3)) (4, 5)" in boşlukla ayrılmış iki grubu olmasıdır. Normal ifademi global bayrakla kullan: / (([^ ()] | (? R)) *) / g. Online test: regex101.com/r/lF0fI1/1
rogal111


7
.NET 4.5 ben bu modeli için aşağıdaki hatayı alıyorum: Unrecognized grouping construct.
nam

3
Müthiş! Bu normal ifadenin harika bir özelliğidir. Soruyu cevaplayan tek kişi olduğunuz için teşekkür ederiz. Ayrıca, bu regex101 bölgesi tatlıdır.
Andrew

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

[^\(]*dizenin başındaki açılış köşeli ayracı olmayan her şeyle eşleşir (\(.*\)), köşeli ayraç içine alınmış gerekli alt dizeyi yakalar ve dizenin [^\)]*sonunda kapatma köşeli ayraç olmayan her şeyi eşleştirir. Bu ifadenin köşeli ayraçları eşleştirmeye çalışmadığını unutmayın; bunun için basit bir ayrıştırıcı ( dehmann'ın cevabına bakınız ) daha uygun olacaktır.


sınıfın içindeki köşeli parantezin kaçmasına gerek yoktur. İçinde bir metacharacted olmadığı için.
José Leal

10
Bu ifade "metin (metin) metin (metin) metin" dönen "(metin) metin (metin)" gibi bir şeye karşı başarısız olur. Normal ifadeler parantezleri sayamaz.
Christian Klauser

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

Eşleşen iki parantez arasında metin seçmek isterseniz, normal ifadelerle şansınız kalmaz. Bu imkansız (*) .

Bu normal ifade, dizenizdeki ilk açılış ile son kapanış parantezleri arasındaki metni döndürür.


(*) Normal ifade motorunuzda dengeleme grupları veya özyineleme gibi özellikler yoksa . Bu özellikleri destekleyen motorların sayısı yavaş yavaş artıyor, ancak hala yaygın olarak mevcut değiller.


"<=" Ve "=" işaretleri ne anlama geliyor? Bu ifade hangi regexp motorunu hedefliyor?
Christian Klauser

1
Bu, etrafa bakın veya daha doğru bir şekilde "sıfır genişlik ileriye bakma / geriye bakma iddiaları" dır. Modern regex motorlarının çoğu bunları destekliyor.
Tomalak

OP örneğine göre, maçın en dışındaki ebeveynleri dahil etmek istiyor. Bu normal ifade onları uzaklaştırıyor.
Alan Moore

1
@Alan M: Haklısın. Ancak soru metnine göre , en dış ebeveynler arasında her şeyi istiyor . Seçiminizi yapın. Saatlerce çalıştığını söyledi, bu yüzden "en dış ebeveynleri de içeren her şeyi" niyet olarak görmedi, çünkü bu çok önemsiz: "(. *)".
Tomalak

3
@ghayes Cevap 2009 yılından. Uzun zaman önceydi; bir tür özyinelemeye izin veren düzenli ifade motorları şu anda olduğundan daha nadirdir (ve hala oldukça nadirdir). Cevabımda anlatacağım.
Tomalak

14

Bu cevap, düzenli ifadelerin bu görev için neden doğru araç olmadığının teorik sınırlamasını açıklamaktadır.


Düzenli ifadeler bunu yapamaz.

Normal ifadeler, olarak bilinen bir hesaplama modelini temel alır Finite State Automata (FSA). Adından da anlaşılacağı gibi, a FSAyalnızca geçerli durumu hatırlayabilir, önceki durumlar hakkında hiçbir bilgisi yoktur.

FSA

Yukarıdaki şemada, S1 ve S2, S1'in başlangıç ​​ve son adım olduğu iki durumdur. Dize ile denersek 0110, geçiş şu şekilde olur:

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

İkinci altındadır Yukarıdaki adımlarda, S2ayrıştırma sonra yani 01ait 0110, FSA önceki hakkında bilgisi bulunmadığını 0içinde 01sadece mevcut durumu ve bir sonraki girdi sembolü hatırladığım kadarıyla.

Yukarıdaki problemde, açılış parantezinin numarasını bilmemiz gerekir; bu , bir yerde saklanması gerektiği anlamına gelir . Ancak FSAsbunu yapamadığından, düzenli bir ifade yazılamaz.

Ancak, bu görevi yerine getirmek için bir algoritma yazılabilir. Algoritmalar genellikle düşer Pushdown Automata (PDA). PDA, bir düzey üzerindedir FSA. PDA'nın bazı ek bilgileri depolamak için ek bir yığını vardır. PDA'lar yukarıdaki problemi çözmek için kullanılabilir, çünkü ' push' yığındaki açılış parantezini ' pop' ve kapanış paranteziyle karşılaştığımızda bunları yapabiliriz. Sonunda yığın boşsa parantez açılır ve parantez kapanır. Aksi halde değil.



1
Burada kanıtlayan birkaç cevap var, bu mümkün.
Jiří Herník

1
@Marco Bu cevap teorik açıdan düzenli ifadelerden bahsediyor. Birçok regex motoru şimdi bir gün sadece bu teorik modele güvenmiyor ve işi yapmak için ek bellek kullanıyor!
musibs

@ JiříHerník: bunlar katı anlamda düzenli ifadeler değildir: Kleene tarafından düzenli ifadeler olarak tanımlanmamıştır . Bazı normal ifade motorları, bazı ekstra yetenekleri uygulayarak normal dillerden daha fazla ayrıştırmalarını sağlamıştır .
Willem Van Onsem

12

Aslında .NET düzenli ifadeleri kullanarak bunu yapmak mümkündür, ancak önemsiz değildir, bu yüzden dikkatlice okuyun.

Burada güzel bir makale okuyabilirsiniz . Ayrıca .NET düzenli ifadelerini de okumanız gerekebilir. Buradan okumaya başlayabilirsiniz .

Köşebentler <>, kaçmayı gerektirmedikleri için kullanıldı.

Normal ifade şöyle görünür:

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

4

Bu kesin regex:

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

)*
)
\)

Misal:

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

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

'(pip'dize olarak doğru yönetildiğini unutmayın . (regülatörde denendi: http://sourceforge.net/projects/regulator/ )


4

Bu işe yardımcı olmak için dengeli adlı küçük bir JavaScript kütüphanesi yazdım . Bunu yaparak bunu başarabilirsiniz.

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

Değiştirmeler bile yapabilirsiniz:

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

İşte daha karmaşık ve etkileşimli bir örnek JSFiddle .


4

Ekleme Bobble balonun cevap , özyinelemeli yapılar desteklenen diğer regex tatlar vardır.

Lua

Kullanım %b()( %b{}/ %b[]kıvırcık parantez / köşeli parantez için):

  • for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end( demoya bakınız )

Perl6 :

Çakışmayan çoklu dengeli parantez eşleşmeleri:

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

Çakışan birden çok dengeli parantez eşleşmesi:

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

Demoya bakınız .

Python reregex olmayan çözüm

Bkz Poke cevabı için dengeli parantez arasına bir ifade almak nasıl .

Java özelleştirilebilir regex olmayan çözüm

Java'da tek karakterli değişmez sınırlayıcılara izin veren özelleştirilebilir bir çözüm:

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

Örnek kullanım:

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

Birden fazla eşleşmeyle çalıştığına dair bir kanıt için çevrimiçi bir Java demosuna bakın .
Wiktor Stribiżew


3

İlk ve son parantezlere ihtiyacınız var. Bunun gibi bir şey kullanın:

str.indexOf ('('); - size ilk gelişmeyi verecektir

str.lastIndexOf ( ')'); - sonuncusu

Yani arasında bir dizeye ihtiyacınız var,

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

Yanıt, eşleşen köşeli ayraç kümelerini mi yoksa yalnızca ilk açıklığı giriş metnindeki son kapamaya mı uydurmanız gerektiğine bağlıdır.

Eşleşen iç içe parantezleri eşleştirmeniz gerekiyorsa, normal ifadelerden daha fazlasına ihtiyacınız vardır. - görmek @dehmann

Eğer son kapanışa ilk açıksa bkz. kapanışa açıldıysa .

Ne olmak istediğinize karar verin:

abc ( 123 ( foobar ) def ) xyz ) ghij

Bu durumda kodunuzun neyle eşleşmesi gerektiğine karar vermeniz gerekir.


3
Bu bir cevap değil.
Alan Moore

Evet, sorudaki değişiklik talebi bir yorum olarak verilmelidir,
Gangnus

0

js regex özyinelemeli eşleşmeyi desteklemediğinden, dengeli parantezlerin eşleştirilmesini sağlayamıyorum.

yani bu, dizi yöntemi için "method (arg)" dizesini diziye dönüştüren basit bir javascripttir

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)

sonuç şöyle

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

Bu kadar çok cevap, bir şekilde, normal ifadenin özyinelemeli eşleşmeyi desteklemediğini söyleyerek söylese de, bunun temel nedeni Hesaplama Teorisinin köklerinde yatmaktadır.

Formun dili {a^nb^n | n>=0} is not regular. Normal ifade yalnızca normal dil grubunun bir parçasını oluşturan şeylerle eşleşebilir.

Daha fazla bilgi için @ buradan


0

İç içe kod ile uğraşmak zor olduğundan regex kullanmadım. Dolayısıyla bu snippet, kod bölümlerini dengeli köşeli parantezlerle almanıza izin verebilmelidir:

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

Bunu bir metin dosyasından kod parçacıkları ayıklamak için kullandım.


0

İç içe kalıpların geldiği bu durumda da sıkışmıştım.

Düzenli İfade yukarıdaki sorunu çözmek için doğru bir şeydir. Aşağıdaki desen kullanın

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


-1

Bu, bazıları için yararlı olabilir:

JavaScript'teki işlev dizesinden (iç içe yapılarla) ayrıştırıcıları ayrıştırma

Aşağıdaki gibi yapıları eşleştirin:
İşlev dizesindeki ayrıştırıcıları ayrıştırma

  • köşeli ayraç, köşeli ayraç, parantez, tek ve çift tırnak işaretleri

Burada oluşturulan normal ifade eylemini görebilirsiniz

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

Bu tamamen OP sorusunu ele almaz ama ben bazı buraya iç içe yapı regexp aramak için yararlı olabilir olsa da.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.