Захват <thisPartOnly>и (thisPartOnly) одной и той же группой - PullRequest
4 голосов
/ 02 июля 2010

Допустим, у нас есть следующие входные данные:

<amy>
(bob)
<carol)
(dean>

У нас также есть следующее регулярное выражение:

<(\w+)>|\((\w+)\)

Теперь мы получаем два совпадения (, как видно на Rubular.com ):

  • <amy> соответствует, \1 захватывает amy, \2 терпит неудачу
  • (bob) соответствует, \2захватывает bob, \1 терпит неудачу

Это регулярное выражение выполняет большую часть того, что мы хотим, а именно:

  • Соответствует открывающим и закрывающим скобкам (т.е. нетмикширование)
  • Он захватывает интересующую нас часть

Однако у него есть несколько недостатков:

  • Схема захвата (т.е.«основная» часть) повторяется
    • Это всего лишь \w+ в этом случае, но, вообще говоря, это может быть довольно сложным,
      • Если это связано с обратными ссылками, то они должны быть перенумерованы для каждой альтернативы!
      • Повторение делает обслуживание кошмаром!(что, если это изменится?)
  • Группы по сути дублируются
    • В зависимости от того, какие альтернативные совпадения, мы должны запросить разные группы
      • В данном случае это всего лишь \1 или \2, но обычно "основная" часть может иметь собственные группы захвата!
    • Это не только неудобно, но имогут быть ситуации, когда это невозможно (например, когда мы используем пользовательскую структуру регулярных выражений, ограниченную запросом только одной группы)
  • Ситуация быстро ухудшается, если мы также хотимmatch {...}, [...] и т. д.

Таким образом, вопрос очевиден: как мы можем это сделать, не повторяя «основной» шаблон?

Примечание: по большей части меня интересует аромат java.util.regex, но приветствуются другие вкусы.


Приложение

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

Давайте рассмотрим приведенный выше пример на следующем шаге: теперь мы хотим сопоставить их:

<amy=amy>
(bob=bob)
[carol=carol]

Но не эти:

<amy=amy)   # non-matching bracket
<amy=bob>   # left hand side not equal to right hand side

Используя альтернативную технику, у нас работает следующее ( как видно на rubular.com ):

<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\]

Как объяснено выше:

  • Основной шаблон не может просто повторяться;обратные ссылки должны быть перенумерованы
  • Повтор также означает кошмар обслуживания, если он когда-либо изменится
  • В зависимости от того, какие альтернативные совпадения, мы должны запросить либо \1 \2, \3 \4, либо \5 \6

Ответы [ 6 ]

5 голосов
/ 02 июля 2010

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

String s = "<amy=amy>(bob=bob)[carol=carol]";
Pattern p = Pattern.compile(
  "(?=[<(\\[]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\])");
Matcher m = p.matcher(s);

while(m.find())
{
  System.out.printf("found %s in %s%n", m.group(2), m.group());
}

output:

found amy in <amy=amy>
found bob in (bob=bob)
found carol in [carol=carol]

Это все равно ужасно, но выне нужно пересчитывать все номера групп каждый раз, когда вы вносите изменения.Например, чтобы добавить поддержку фигурных скобок, просто:

"(?=[<(\\[{]((\\w+)=\\2))(?:<\\1>|\\(\\1\\)|\\[\\1\\]|\\{\\1\\})"
3 голосов
/ 02 июля 2010

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

Таким образом, этот шаблон работает для второго примера (, как видно на rubular.com ):

                  __main__
                 /        \
(?:<()|\(()|\[())((\w+)=\5)(\1>|\2\)|\3\])
\_______________/          \_____________/
    \1   \2   \3

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

«Основная» часть не должна повторяться, но в Java обратные ссылки могутбыть перенумерованнымЭто не будет проблемой для разновидностей, которые поддерживают именованные группы.

3 голосов
/ 02 июля 2010

В preg (библиотека Perl Regex) это будет соответствовать вашему примеру, и \3 поймает внутренности:

((<)|\()(\w+)(?(2)>|\))

Это не будет работать в JS, хотя вы не указали диалект ...

Это зависит от условного оператора (?(2)...|...), который в основном говорит, что если 2 является ненулевым захватом, то совпадать перед каналом, иначе совпадать после канала. В этой форме труба не чередование ("или").

ОБНОВЛЕНИЕ Извините, я полностью пропустил бит Java :) В любом случае, очевидно, Java не поддерживает условную конструкцию; и я понятия не имею, как еще я мог бы пойти на это: (

Кроме того, для вашего Приложения (даже если это неправильный диалект):

(?:(<)|(\()|\[)(\w+)=\3(?(1)>|(?(2)\)|]))

Имя снова введено в \3 (я избавился от первого взятия парена, но мне пришлось добавить еще один для одной дополнительной вступительной проверки парена)

0 голосов
/ 02 июля 2010

Если не существует простого способа написать это регулярное выражение вручную, почему бы не оставить его на компьютере?У вас может быть функция, например, как показано ниже (здесь я использую синтаксис C #, так как здесь я немного больше знаком с регулярными выражениями, чем с Java, но не должно быть слишком сложно адаптировать ее к Java).

Обратите внимание, что я оставил функцию AdaptBackreferences () более или менее неосуществленной в качестве упражнения для читателя .Следует просто адаптировать нумерацию обратных ссылок.

    struct BracketPair {public string Open; public string Close;};

    static string[] MatchTextInBrackets(string text, string innerPattern, BracketPair[] bracketPairs) {
        StringBuilder sb  = new StringBuilder();

        // count number of catching parentheses of innerPattern here:
        int numberOfInnerCapturingParentheses = Regex.Match("", innerPattern).Groups.Count - 1;

        bool firstTime = true;
        foreach (BracketPair pair in bracketPairs) {
            // apply logic to change backreference numbering:
            string adaptedInnerPattern = AdaptBackreferences(innerPattern);
            if (firstTime) { firstTime = false; } else { sb.Append('|'); }
            sb.Append(pair.Open).Append("(").Append(adaptedInnerPattern).Append(")").Append(pair.Close);
        }
        string myPattern = sb.ToString();
        MatchCollection matches = Regex.Matches(text, myPattern);
        string[] result = new string[matches.Count];
        for(int i=0; i < matches.Count; i++) {
            StringBuilder mb = new StringBuilder();
            for(int j=0; j < bracketPairs.Length; j++) {
                mb.Append(matches[i].Groups[1 + j * (numberOfInnerCapturingParentheses + 1)]); // append them all together, assuming all exept one are empty
            }
            result[i] = mb.ToString();
        }
        return result;
    }

    static string AdaptBackreferences(string pattern) { return pattern; } // to be written
0 голосов
/ 02 июля 2010

Может быть, этот пример в Perl заинтересует вас:

$str = q/<amy=amy> (bob=bob) [carol=carol] <amy=amy) <amy=bob>/;
$re = qr/(?:<((\w+)=\2)>|\(((\w+)=\4)\)|\[((\w+)=\6)\])+/;
@list = ($str =~ /$re/g);
for(@list) {
    say $i++," = ",$_;
}

Я просто окружаю ваше регулярное выражение (?: Регулярное выражение) +

0 голосов
/ 02 июля 2010

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

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

Вот некоторый псевдокод:

Brackets = "<>,(),[]"
CoreRegex = "(\w+)=\1"

loop CurBracket in Brackets.split(',')
{
    Input.match( Regex.quote(CurBracket.left(1)) & CoreRegex & Regex.quote(CurBracket.right(1)) )
}


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

...