Регулярное выражение для соответствия сбалансированным скобкам

214

Мне нужно регулярное выражение для выбора всего текста между двумя внешними скобками.

Пример: 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/…
Теги:

15 ответов

109
Лучший ответ

Регулярные выражения являются неправильным инструментом для задания, потому что вы имеете дело с вложенными структурами, то есть с рекурсией.

Но для этого есть простой алгоритм, который я описал в этом ответе на предыдущем вопросе.

  • 0
    Я играл с этой идеей, но думал, что смогу сделать это с RegExp. Вернусь к моему первоначальному плану. Спасибо всем
  • 12
    Реализация .NET имеет [Определения балансирующей группы msdn.microsoft.com/en-us/library/…, которые допускают подобные вещи.
Показать ещё 6 комментариев
73

Вы можете использовать рекурсию регулярных выражений:

\(([^()]|(?R))*\)
  • 3
    Пример был бы действительно полезен здесь, я не могу заставить это работать для таких вещей, как "(1, (2, 3)) (4, 5)".
  • 1
    @AndyHayden это потому, что "(1, (2, 3)) (4, 5)" имеет две группы, разделенные пробелом. Используйте мое регулярное выражение с глобальным флагом: / (([^ ()] | (? R)) *) / g. Вот онлайн тест: regex101.com/r/lF0fI1/1
Показать ещё 10 комментариев
67

Я хочу добавить этот ответ для быстрой ссылки. Не стесняйтесь обновлять.


.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, используя вызовы подвыражения.

С 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.


Ссылка. Что означает это регулярное выражение?

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

[^\(]* соответствует всем, что не является открывающей скобкой в ​​начале строки, (\(.*\)) фиксирует требуемую подстроку, заключенную в скобки, а [^\)]* соответствует всем, что не является закрывающей скобкой в ​​конце Струна. Обратите внимание, что это выражение не пытается сопоставить скобки; простой парсер (см. ответ дехмана) был бы более подходящим для этого.

  • 0
    скобка внутри класса не нуждается в экранировании. Так как внутри это не метасимвол.
  • 9
    Этот expr не работает с чем-то вроде «text (text) text (text) text« return »(text) text (text)». Регулярные выражения не могут считать скобки.
14
(?<=\().*(?=\))

Если вы хотите выбрать текст между двумя совпадающими скобками, вам не повезло с регулярными выражениями. Это невозможно (*).

Это регулярное выражение просто возвращает текст между первым открытием и последними закрывающимися скобками в вашей строке.


(*) Если ваш механизм регулярного выражения не имеет таких функций, как балансировочные группы или рекурсия. Количество двигателей, поддерживающих такие функции, медленно растет, но они по-прежнему не являются общедоступными.

  • 0
    Что означают знаки «<=» и «=»? На какой механизм регулярных выражений нацелено это выражение?
  • 1
    Это осмотр, или, точнее, «утверждения упреждения / упущения нулевой ширины». Большинство современных двигателей регулярных выражений поддерживают их.
Показать ещё 4 комментария
11

На самом деле это можно сделать с использованием регулярных выражений .NET, но это не так, но читайте внимательно.

Здесь вы можете прочитать хорошую статью здесь. Вам также может потребоваться прочитать регулярные выражения .NET. Вы можете начать читать здесь.

Угловые скобки <> использовались, потому что они не требуют экранирования.

Регулярное выражение выглядит следующим образом:

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

Этот ответ объясняет теоретическое ограничение того, почему регулярные выражения не являются подходящим инструментом для этой задачи.


Регулярные выражения не могут этого сделать.

Регулярные выражения основаны на вычислительной модели, известной как Finite State Automata (FSA). Как указано в названии, a FSA может помнить только текущее состояние, оно не имеет информации о предыдущих состояниях.

Изображение 4757

На приведенной выше диаграмме S1 и S2 являются двумя состояниями, где S1 является начальным и конечным шагами. Поэтому, если мы попытаемся использовать строку 0110, переход будет выглядеть следующим образом:

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

В приведенных выше шагах, когда мы находимся во втором S2, то есть после разбора 01 of 0110, FSA не имеет информации о предыдущем 0 в 01, поскольку он может только помнить текущее состояние и следующий входной символ.

В вышеприведенной задаче нам нужно знать отсутствие открывающей скобки; это означает, что он должен храниться в каком-либо месте. Но так как FSAs не может этого сделать, регулярное выражение не может быть записано.

Однако для достижения цели можно написать алгоритм. Алгоритмы обычно подпадают под Pushdown Automata (PDA). PDA - это один уровень выше FSA. У КПК есть дополнительный стек, чтобы что-то хранить. КПК могут быть использованы для решения вышеуказанной проблемы, потому что мы можем "push" открывать скобки в стеке и "pop" их, как только мы сталкиваемся с закрывающей скобкой. Если в конце стопка пуста, откроются скобки и закрывающая скобка. В противном случае нет.

Подробное обсуждение можно найти здесь.

  • 0
    Push и pop возможны в регулярном выражении stackoverflow.com/questions/17003799/… .: регулярные выражения.info/ balancing.html
  • 0
    Здесь есть несколько ответов, которые доказывают, что это возможно.
3

Это окончательное регулярное выражение:

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

)*
)
\)

Пример:

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

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

обратите внимание, что '(pip' правильно управляется как строка. (проверено в регуляторе: http://sourceforge.net/projects/regulator/)

2

Я написал небольшую библиотеку javascript под названием balanced, чтобы справиться с этой задачей, вы можете сделать это, выполнив

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

вы можете даже выполнять замены

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

heres более сложный и интерактивный пример JSFiddle

2

Регулярное выражение с использованием Ruby (версия 1.9.3 или выше):

/(?<match>\((?:\g<match>|[^()]++)*\))/

Демо на рублевом

1

так что вам нужна первая и последняя родительская, используйте smth, как это str.indexOf('('); - это даст вам первое появление str.lastIndexOf( ')'); - последний

так что вам нужна строка между ними, Строка searchString = str.substring(str1.indexOf('('), str1.lastIndexOf(')');

  • 0
    просто и читабельно!
1

Вот настраиваемое решение, позволяющее одиночные символьные литералы в 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)))]
  • 0
    Посмотрите онлайн демо Java для доказательства того, что он работает с несколькими совпадениями.
0

Этот тоже работал

re.findall(r'\(.+\)', s)
0
"""
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

Вам нужно решить, что ваш код должен соответствовать в этом случае.

  • 1
    Это не ответ.
  • 0
    Да, требование об изменении в вопросе должно быть дано в качестве комментария,

Ещё вопросы

Сообщество Overcoder
Наверх
Меню