Регулярне вираження, щоб відповідати збалансованим дужкам


290

Мені потрібен регулярний вираз, щоб вибрати весь текст між двома зовнішніми дужками.

Приклад: some text(text here(possible text)text(possible text(more text)))end text

Результат: (text here(possible text)text(possible text(more text)))


3
Це питання дуже бідне, тому що не ясно, про що він задається. Усі відповіді тлумачили це по-різному. @DaveF Ви можете, будь ласка, уточнити питання?
Метт Фенвік

1
Відповів в цьому пості: stackoverflow.com/questions/6331065 / ...
sship21

Відповіді:


144

Регулярні вирази - неправильний інструмент для роботи, оскільки ви маєте справу з вкладеними структурами, тобто рекурсією.

Але для цього є простий алгоритм, який я описав у цій відповіді на попереднє запитання .


15
Реалізація .NET має [Визначення групи балансування msdn.microsoft.com/en-us/library/…, які дозволяють подібні речі.
Карл G

22
Я не погоджуюся, що регулярні вирази є неправильним інструментом для цього з кількох причин. 1) Більшість регулярних реалізацій виразів мають працездатне, якщо не ідеальне рішення. 2) Часто ви намагаєтеся знайти збалансовані пари роздільників у контексті, коли також грають інші критерії, які добре підходять до регулярних виразів. 3) Часто ви передаєте регулярний вираз в якийсь API, який приймає лише регулярні вирази, і у вас немає вибору.
Кеннет Балтрінік


20
Regex - це ПРАВИЛЬНИЙ інструмент для роботи. Ця відповідь неправильна. Дивіться відповідь rogal111.
Андрій

4
Абсолютно згоден з відповіддю. Хоча є кілька реалізацій рекурсії в regexp, вони дорівнюють машинам з кінцевим станом і не передбачають роботи з вкладеними структурами, але це роблять контекстні вільні граматики. Подивіться на ієрархію Хомського формальних граматик.
Нік Роз

138

Я хочу додати цю відповідь для швидкого перегляду. Не соромтеся оновлюватись.


.NET Regex з використанням балансуючих груп .

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

Де cвикористовується як лічильник глибини.

Демонстрація на Regexstorm.com


PCRE з використанням рекурсивної схеми .

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

Демонстрація на regex101 ; Або без чергування:

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

Демонстрація на regex101 ; Або скасовано для виконання:

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

Демонстрація на regex101 ; Візерунок вставляється, на (?R)якому зображено (?0).

Perl, PHP, Notepad ++, R : perl = TRUE , Python : пакет Regex з (?V1)для поведінки Perl.


Рубін, використовуючи дзвінки під вираженою експресією .

З 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))*+. Те саме для наступного шаблону. Щодо розкрученої версії, ви можете поставити тут присвійний кількісний коефіцієнт: [^)(]*+щоб запобігти зворотному відстеженню (у випадку, якщо немає дужки закриття).
Казимир та Іполіт

Щодо шаблону Ruby 1.9, замість того щоб робити повторну групу атомною (що має обмежений інтерес, коли є багато вкладених круглих дужок (...(..)..(..)..(..)..(..)..)) в предметній рядку), ви можете використовувати просту групу, яка не захоплює, і вкладати все в атомну групу: (?>(?:[^)(]+|\g<1>)*)( це поводиться точно як присвійний квантор). У Ruby 2.x доступний кількісний коефіцієнт.
Казимир та Іполит

@CasimiretHippolyte Дякую! Я скоригував шаблони PCRE і для Ruby 1.9, ви маєте на увазі, що весь візерунок такий ? Будь ласка, не соромтеся оновити себе. Я розумію, що ви маєте на увазі, але не впевнений, чи буде багато покращення.
пузир міхура

117

Ви можете використовувати регекс :

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

3
Приклад був би дуже корисним тут, я не можу змусити це працювати для таких речей, як "(1, (2, 3)) (4, 5)".
Енді Хайден

4
@AndyHayden це тому, що "(1, (2, 3)) (4, 5)" має дві групи, розділені пробілом. Використовуйте мій регулярний вираз з глобальним прапором: / (([^ ()] | (? R)) *) / g. Ось онлайн тест: regex101.com/r/lF0fI1/1
rogal111

1
Я поставив запитання про це минулого тижня stackoverflow.com/questions/26385984/recursive-pattern-in-regex
Енді Хейден

7
В .NET 4.5 Я отримую наступне повідомлення про помилку для цієї моделі: Unrecognized grouping construct.
нам

3
Дивовижно! Це чудова особливість регексу. Дякую, що єдиний, хто насправді відповів на питання. Крім того, цей сайт regex101 милий.
Андрій

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

[^\(]*відповідає всім, що не є початковим дужкою на початку рядка, (\(.*\))захоплює необхідну підрядку, укладену в дужки, і [^\)]*відповідає всім, що не є дужкою, що закривається в кінці рядка. Зауважте, що цей вираз не намагається відповідати дужкам; простий аналізатор (див . відповідь Демана ) був би більш підходящим для цього.


дужку всередині класу не потрібно уникати. Оскільки всередині це не метахарактер.
Хосе Ліл

10
Цей експрес не відповідає чомусь на зразок "текст (текст) текст (текст) текст" повернення "(текст) текст (текст)". Регулярні вирази не можуть рахувати дужки.
Крістіан Клаузер

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

Якщо ви хочете вибрати текст між двома відповідніми дужками, вам не пощастить з регулярними виразами. Це неможливо (*) .

Цей регулярний вираз просто повертає текст між першим відкриттям та останніми дужками, що закриваються, у вашому рядку.


(*) Якщо ваш механізм регулярних вивірок не має таких функцій, як балансуючі групи або рекурсія . Кількість двигунів, які підтримують такі функції, повільно зростає, але вони все ще не є загальнодоступними.


Що означають знаки "<=" та "="? Який механізм regexp орієнтується на це вираження?
Крістіан Клаузер

1
Це оглядові або, вірніше, "нульові ширини твердження вперед / позаду". Більшість сучасних двигунів регексу підтримують їх.
Томалак

Відповідно до прикладу ОП, він хоче включити у матч найвіддаленіші парени. Цей регулярний викид їх викидає.
Алан Мур

1
@Alan M: Ти маєш рацію. Але відповідно до тексту запитання, він хоче, щоб все було між найвіддаленішими паренами. Оберіть свій вибір. Він сказав, що намагається годинами, тому навіть не розглядав "все, включаючи найвіддаленіші парени", як намір, оскільки це так тривіально: "(. *)".
Томалак

3
@ghayes Відповідь з 2009 року. Це давно ; мотори регулярної експресії, які дозволяють отримати певну форму рекурсії, були більш рідкісними, ніж зараз (і вони все ще є досить рідкісними). Я згадаю це у своїй відповіді.
Томалак

14

Ця відповідь пояснює теоретичне обмеження того, чому регулярні вирази не є правильним інструментом для цього завдання.


Регулярні вирази цього не можуть зробити.

Регулярні вирази засновані на обчислювальній моделі, відомій як Finite State Automata (FSA). Як вказує назва, a FSAможе запам'ятати тільки поточний стан, він не має інформації про попередні стани.

FSA

На наведеній діаграмі S1 і S2 - це два стани, де S1 є початковим і заключним кроком. Отже, якщо ми спробуємо з рядком 0110, перехід відбувається наступним чином:

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

У наведених вище кроків, коли ми знаходимося на другому , S2тобто після розбору 01з 0110, АФН не має ніякої інформації про попередню 0в 01як він може згадати тільки поточний стан і наступний вхідний символ.

У наведеній вище проблемі нам потрібно знати відсутність дужок, що відкриваються; це означає, що його потрібно зберігати в якомусь місці. Але оскільки цього FSAsне можна зробити, регулярного виразу не можна писати.

Однак можна написати алгоритм для виконання цього завдання. Алгоритми, як правило, підпадають під Pushdown Automata (PDA). PDAзнаходиться на один рівень вище FSA. PDA має додатковий стек для зберігання деякої додаткової інформації. КПК можна використовувати для вирішення вищезазначеної проблеми, тому що ми можемо ' push' відкриваючі дужки в стеку і ' pop' їх, коли ми зустрінемо дужки, що закриваються. Якщо в кінці, стек порожній, тоді відкриваються дужки та закриваються дужки збігаються. Інакше ні.



1
Тут є кілька відповідей, що підтверджує, що це можливо.
Jiří Herník

1
@Marco Ця відповідь говорить про регулярні вирази в теоретичній перспективі. Багато двигунів регулярних викидів зараз цілі дні не покладаються тільки на цю теоретичну модель і використовують деяку додаткову пам'ять, щоб зробити цю роботу!
musibs

@ JiříHerník: це не регулярні вирази в строгому значенні: не визначені як регулярні вирази Kleene . Деякі двигуни регулярного вираження дійсно реалізували додаткові можливості, завдяки чому вони розбирають більше, ніж лише звичайні мови .
Віллем Ван Онсем

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

Я написав невелику бібліотеку 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[]для фігурних дужок / квадратних дужок):

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

Дивіться демонстрацію .

reНерегексичне рішення Python

Дивіться відповідь Poke для того, як отримати вираз між збалансованими дужками .

Налаштування Java, що не піддається повторному вибору

Ось налаштоване рішення, що дозволяє однозначні обмежувачі символів на Java:

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 для підтвердження того, що вона працює з кількома збігами.
Wiktor Stribiżew


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 не підтримує рекурсивну відповідність, я не можу зробити збалансовані дужки, які відповідають роботі.

тож це простий javascript для версії циклу, яка перетворює рядок "method (arg)" в масив

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 . Regex може відповідати лише тим, що входять до звичайного набору мов.

Детальніше @ тут


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

Це не повністю стосується питання щодо ОП, але я хоч може бути корисним для деяких, хто приїжджає сюди, щоб шукати вкладену структуру regexp.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.