Группы Java Matcher: понимание разницы между "(?: X | Y)" и "(?: X) | (?: Y)" - PullRequest
7 голосов
/ 04 июня 2010

Может кто-нибудь объяснить:

  1. Почему два шаблона, приведенные ниже, дают разные результаты? (ответил ниже)
  2. Почему во втором примере число групп равно 1, но указано начало и конец группы 1 равен -1?
 public void testGroups() throws Exception
 {
  String TEST_STRING = "After Yes is group 1 End";
  {
   Pattern p;
   Matcher m;
   String pattern="(?:Yes|No)(.*)End";
   p=Pattern.compile(pattern);
   m=p.matcher(TEST_STRING);
   boolean f=m.find();
   int count=m.groupCount();
   int start=m.start(1);
   int end=m.end(1);

   System.out.println("Pattern=" + pattern + "\t Found=" + f + " Group count=" + count + 
     " Start of group 1=" + start + " End of group 1=" + end );
  }

  {
   Pattern p;
   Matcher m;

   String pattern="(?:Yes)|(?:No)(.*)End";
   p=Pattern.compile(pattern);
   m=p.matcher(TEST_STRING);
   boolean f=m.find();
   int count=m.groupCount();
   int start=m.start(1);
   int end=m.end(1);

   System.out.println("Pattern=" + pattern + "\t Found=" + f + " Group count=" + count + 
     " Start of group 1=" + start + " End of group 1=" + end );
  }

 }

Что дает следующий вывод:

Pattern=(?:Yes|No)(.*)End  Found=true Group count=1 Start of group 1=9 End of group 1=21
Pattern=(?:Yes)|(?:No)(.*)End  Found=true Group count=1 Start of group 1=-1 End of group 1=-1

Ответы [ 4 ]

8 голосов
/ 05 июня 2010
  1. Разница в том, что во втором шаблоне "(?:Yes)|(?:No)(.*)End" конкатенация ("X и Y" в "XY") имеет более высокий приоритет , чем выбор ("либо X, либо Y" "в" X | Y "), как умножение имеет более высокий приоритет, чем сложение, поэтому шаблон эквивалентен

    "(?:Yes)|(?:(?:No)(.*)End)"
    

    То, что вы хотели получить, это следующий шаблон:

    "(?:(?:Yes)|(?:No))(.*)End"
    

    Это дает тот же результат, что и ваш первый паттерн.

    В вашем тесте второй шаблон имеет группу 1 в (пустом) диапазоне [-1, -1[, поскольку эта группа не совпадает (включается начало -1, исключается конец -1, что составляет половину -открытый интервал пуст).

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

  3. Количество групп, возвращаемое Matcher.groupCount(), получается только путем подсчета скобок группировки групп захвата , независимо от того, может ли какая-либо из них соответствовать любой данный вклад. Ваш шаблон имеет ровно одну группу захвата: (.*). Это группа 1. Документация гласит :

    (?:X)    X, as a non-capturing group
    

    и объясняет :

    Группы, начинающиеся с (?, являются либо чистыми, не захватывающими группами, которые не захватывают текст и не учитываются в общем количестве групп, либо именованными группами захвата.

    Соответствует ли какая-либо конкретная группа заданному входу, не имеет значения для этого определения. Например, в шаблоне (Yes)|(No) есть две группы ((Yes) - это группа 1, (No) - это группа 2), но только одна из них может соответствовать любому заданному входу.

  4. Вызов Matcher.find() возвращает true, если регулярное выражение найдено в некоторой подстроке. Вы можете определить, какие группы соответствуют, посмотрев их начало: если это -1, то группа не совпадает. В этом случае конец также равен -1. Не существует встроенного метода, который бы указывал, сколько групп захвата фактически совпало после вызова find() или match(). Вы должны посчитать их сами, посмотрев на начало каждой группы.

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

    * * 1068

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

5 голосов
/ 07 июня 2010

Подводя итог,

1) Два шаблона дают разные результаты из-за правил приоритета операторов.

  • (?:Yes|No)(.*)End совпадений (да или Нет) с последующим. * Конец
  • (?:Yes)|(?:No)(.*)End совпадений (да) или (Нет, а затем. * Конец)

2) Второй шаблон дает число групп 1, но начало и конец -1 из-за (не обязательно интуитивного) значения результатов, возвращаемых вызовами метода Matcher.

  • Matcher.find() возвращает true, если совпадение найдено. В вашем случае совпадение было на (?:Yes) части шаблона.
  • Matcher.groupCount() возвращает количество групп захвата в шаблоне независимо от того, действительно ли группы захвата участвовали в матче . В вашем случае в матче участвовала только не захватывающая часть шаблона (?:Yes), но группа захвата (.*) все еще была частью шаблона, поэтому количество групп равно 1.
  • Matcher.start(n) и Matcher.end(n) возвращают начальный и конечный индексы подпоследовательности, совпадающей с n -й группой захвата. В вашем случае, хотя общее совпадение было найдено, группа захвата (.*) не участвовала в совпадении и поэтому не захватила подпоследовательность, следовательно, результат -1.

3) (Вопрос задан в комментарии.) Чтобы определить, сколько групп захвата фактически захватило подпоследовательность, выполните итерацию Matcher.start(n) от 0 до Matcher.groupCount(), считая количество результатов, отличных от -1. (Обратите внимание, что Matcher.start(0) - это группа захвата, представляющая весь шаблон, который вы, возможно, захотите исключить для своих целей.)

3 голосов
/ 05 июня 2010

Из-за приоритета "|" оператор в шаблоне, второй шаблон эквивалентен:

(?:Yes)|((?:No)(.*)End)

То, что вы хотите, это

(?:(?:Yes)|(?:No))(.*)End
1 голос
/ 05 июня 2010

При использовании регулярного выражения важно помнить, что там есть неявный оператор AND в работе. Это видно из JavaDoc для java.util.regex.Pattern, охватывающего логические операторы:

Логические операторы
XY X с последующим Y
X | Y Либо X, либо Y
(X) X в качестве группы захвата

Этот AND имеет приоритет над OR во втором паттерне. Второй паттерн эквивалентен
(?:Yes)|(?:(?:No)(.*)End).
Чтобы он был эквивалентен первому шаблону, его необходимо изменить на
(?:(?:Yes)|(?:No))(.*)End

...