Можно ли использовать регулярные выражения для сопоставления с вложенными шаблонами? [Дубликат]

216

Можно ли написать регулярное выражение, которое соответствует вложенному шаблону, который встречается неизвестным числом раз? Например, может ли регулярное выражение соответствовать открывающей и закрывающейся скобке, если в внешних фигурных скобках имеется неизвестное количество открытых/закрывающих скобок?

Например:

public MyMethod()
{
  if (test)
  {
    // More { }
  }

  // More { }
} // End

Должно соответствовать:

{
  if (test)
  {
    // More { }
  }

  // More { }
}
  • 23
    Чтобы однозначно ответить на этот вопрос, сначала нужно определить термин: «регулярное выражение».
  • 3
    Из книг регулярные выражения не могут сделать это, но выражения без контекста могут. Из инструментов современные парсеры выражений будут вызывать regular expression чем-то, использующим внешний стек, то есть способным возвращаться назад, то есть способным к повторению: на практике это context-free expressions зависящие от context-free expressions и поэтому вы можете сделать это как однострочник с simili- PCRE2 (PHP, Java, .NET, Perl, ...) или ICU- совместимые (Obj-C / Swift) инструменты, часто с синтаксисом (?>...) , или альтернативы, такие как (?R) или (?0) синтаксис.
Теги:
nested
finite-automata

13 ответов

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

Нет. Это так просто. Конечный автомат (который является структурой данных, лежащей в основе регулярного выражения) не имеет памяти, кроме состояния, в котором он находится, и если у вас есть произвольно глубокое вложение, вам нужен произвольно большой автомат, который сталкивается с понятием конечного автомата.

Вы можете сопоставить вложенные/парные элементы с фиксированной глубиной, где глубина ограничена только вашей памятью, потому что автомат становится очень большим. На практике, однако, вы должны использовать push-down automaton, т.е. Синтаксический анализатор для контекстно-свободной грамматики, например LL (сверху вниз) или LR (снизу вверх). Вы должны учитывать худшее поведение во время выполнения: O (n ^ 3) по сравнению с O (n), с n = длина (ввод).

Существует много генераторов синтаксического анализатора, например ANTLR для Java. Найти существующую грамматику для Java (или C) также не сложно.
Для получения дополнительной информации: Теория автоматов в Википедии

  • 55
    Торстен прав в теории. На практике многие реализации имеют некоторые хитрости, чтобы позволить вам выполнять рекурсивные «регулярные выражения». Например, см. Главу «Рекурсивные шаблоны» в php.net/manual/en/regexp.reference.php.
  • 2
    Я избалован моим воспитанием в области обработки естественного языка и включенной в него теорией автоматов.
Показать ещё 7 комментариев
33

Возможно, работающее решение Perl, если строка находится в одной строке:

my $NesteD ;
$NesteD = qr/ \{( [^{}] | (??{ $NesteD }) )* \} /x ;

if ( $Stringy =~ m/\b( \w+$NesteD )/x ) {
    print "Found: $1\n" ;
  }

НТН

РЕДАКТИРОВАТЬ: проверить:

И еще одна вещь Torsten Marek (кто правильно указал, что это не регулярное выражение):

  • 9
    Ага. «Регулярные выражения» в Perl - нет (и не очень давно). Следует отметить, что рекурсивные регулярные выражения являются новой функцией в Perl 5.10 и что даже если вы можете сделать это, вам, вероятно, не следует делать это в большинстве случаев, которые обычно встречаются (например, при разборе HTML).
  • 0
    perldoc.perl.org/perlretut.html
30

Использование регулярных выражений для проверки вложенных шаблонов очень просто.

'/(\((?>[^()]+|(?1))*\))/'
  • 2
    Согласен. Однако одна проблема с синтаксисом атомарной группы (?>...) (в PHP 5.2) заключается в том, что часть ?> Интерпретируется как: «конец сценария»! Вот как бы я это написал: /\((?:[^()]++|(?R))*+\)/ . Это немного более эффективно для сопоставления и несоответствия. В своей минимальной форме /\(([^()]|(?R))*\)/ это действительно прекрасная вещь!
  • 1
    Двойной +? Я использовал (?1) чтобы комментарии были в другом тексте (я разорвал его и упростил из регулярного выражения моего адреса электронной почты). И (?> Был использован, потому что я считаю, что это заставляет его работать быстрее (если требуется). Это не правильно?
Показать ещё 6 комментариев
18

Да, если это .NET RegEx-движок..Net поддерживает конечный конечный автомат, поставляемый с внешним стеклом. см. подробности

  • 9
    Как уже упоминали другие, .NET не единственный способный движок регулярных выражений, чтобы сделать это.
14

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

Сгенерированный автомат будет иметь конечное число состояний, скажем k, поэтому строка из k + 1 открывающих фигурных скобок связана с тем, что состояние повторяется где-то (поскольку автомат обрабатывает символы). Часть строки между одним и тем же состоянием может быть продублирована бесконечно много раз, и автомат не узнает разницу.

В частности, если он принимает k + 1 открывающих скобок, за которыми следуют k + 1 закрывающие скобки (что он должен), он также примет количество накачиваемых открывающих фигурных скобок, за которым следуют неизменные k + 1 замыкающие браки (что не должно).

12

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

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

Например, Lua в регулярных выражениях есть распознаватель <%b(), который будет соответствовать сбалансированным скобкам. В вашем случае вы будете использовать "%b{}"

Другим сложным инструментом, похожим на sed, является gema, где вы легко сопоставляете сбалансированные фигурные фигурные скобки с помощью {#}.

Итак, в зависимости от инструментов, которые у вас есть, ваше "регулярное выражение" (в более широком смысле) может соответствовать вложенным скобкам.

5

Использование рекурсивного соответствия в PHP-регулярном выражении значительно быстрее процедурного соответствия скобок. особенно с более длинными строками.

http://php.net/manual/en/regexp.reference.recursive.php

например.

$patt = '!\( (?: (?: (?>[^()]+) | (?R) )* ) \)!x';

preg_match_all( $patt, $str, $m );

против.

matchBrackets( $str );

function matchBrackets ( $str, $offset = 0 ) {

    $matches = array();

    list( $opener, $closer ) = array( '(', ')' );

    // Return early if there no match
    if ( false === ( $first_offset = strpos( $str, $opener, $offset ) ) ) {
        return $matches;
    }

    // Step through the string one character at a time storing offsets
    $paren_score = -1;
    $inside_paren = false;
    $match_start = 0;
    $offsets = array();

    for ( $index = $first_offset; $index < strlen( $str ); $index++ ) {
        $char = $str[ $index ];

        if ( $opener === $char ) {
            if ( ! $inside_paren ) {
                $paren_score = 1;
                $match_start = $index;
            }
            else {
                $paren_score++;
            }
            $inside_paren = true;
        }
        elseif ( $closer === $char ) {
            $paren_score--;
        }

        if ( 0 === $paren_score ) {
            $inside_paren = false;
            $paren_score = -1;
            $offsets[] = array( $match_start, $index + 1 );
        }
    }

    while ( $offset = array_shift( $offsets ) ) {

        list( $start, $finish ) = $offset;

        $match = substr( $str, $start, $finish - $start );
        $matches[] = $match;
    }

    return $matches;
}
3

Как упоминалось в zsolt, некоторые двигатели регулярных выражений поддерживают рекурсию - конечно, обычно это те, которые используют алгоритм обратного отслеживания, поэтому он не будет особенно эффективен. Пример: /(?>[^{}]*){(?>[^{}]*)(?R)*(?>[^{}]*)}/sm

2

ДА

... предполагая, что есть некоторое максимальное количество гнезд, с которыми вы были бы счастливы остановиться.

Позволь мне объяснить.


@torsten-marek прав, что регулярное выражение не может проверять вложенные шаблоны, подобные этому, НО можно определить вложенный шаблон регулярного выражения, который позволит вам фиксировать вложенные структуры, подобные этому, до некоторой максимальной глубины. Я создал один, чтобы записать комментарии в стиле EBNF (попробуйте здесь), например:

(* This is a comment (* this is nested inside (* another level! *) hey *) yo *)

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

m{1} = \(+\*+(?:[^*(]|(?:\*+[^)*])|(?:\(+[^*(]))*\*+\)+

Это можно легко адаптировать для ваших целей, заменив \(+\*+ и \*+\)+ на { и } и заменив все между простыми [^{}]:

p{1} = \{(?:[^{}])*\}

(Здесь ссылка, чтобы попробовать это.)

Чтобы гнездо, просто разрешите этот шаблон внутри самого блока:

p{2} = \{(?:(?:p{1})|(?:[^{}]))*\}
  ...or...
p{2} = \{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\}

Чтобы найти тройные вложенные блоки, используйте:

p{3} = \{(?:(?:p{2})|(?:[^{}]))*\}
  ...or...
p{3} = \{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}

Появилась ясная картина. Чтобы найти комментарии, вложенные в глубину N, просто используйте регулярное выражение:

p{N} = \{(?:(?:p{N-1})|(?:[^{}]))*\}

  where N > 1 and
  p{1} = \{(?:[^{}])*\}

Сценарий можно записать для рекурсивного генерации этих регулярных выражений, но это выходит за рамки того, что мне нужно для этого. (Это остается как упражнение для читателя. )

2

Нет, вы попадаете в область Контекстные бесплатные грамматики в этой точке.

0

Мой вопрос + ответ связан, и я делаю выражение и мета-выражение, которое может соответствовать произвольным (конечным) уровням вложенности. Это довольно изящно, но что еще вы можете ожидать? Используйте обратные ссылки в матче, если ваш движок поддерживает его.

0

Это работает: /(\{(?:\{.*\}|[^\{])*\})/m

  • 2
    Кажется, он также соответствует {{} что не должно
-3

Нет. Вам нужен полноразмерный парсер для этого типа проблем.

Ещё вопросы

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