Java группы Matcher: Понимание различия между “(?: X|Y)” и “(?: X) | (?: Y)”

Может любой объяснять:

  1. Почему эти два шаблона, используемые ниже, дают различные результаты? (отвеченный ниже)
  2. Почему 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
7
задан ekad 30 November 2017 в 10:13
поделиться

4 ответа

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

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

  • (?: Да | Нет) (. *) Конец совпадений (Да или Нет), за которым следует. * Конец
  • (?: Да) | (?: Нет) (. *) Конец совпадений (Да) или (Нет, за которым следует. * End)

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

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

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

5
ответ дан 6 December 2019 в 11:46
поделиться
  1. Разница в том, что во втором шаблоне "(?:Yes)|(?:No)(. *)End", конкатенация ("X с Y" в "XY") имеет более высокий приоритет, чем выбор ("Either X or Y" в "X|Y"), как умножение имеет более высокий приоритет, чем сложение, поэтому шаблон эквивалентен

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

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

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

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

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

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

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

    (?:X) X, как не захватывающая группа.
    

    и объясняет:

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

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

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

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

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

8
ответ дан 6 December 2019 в 11:46
поделиться

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

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

Вы хотите

(?:(?:Yes)|(?:No))(.*)End
3
ответ дан 6 December 2019 в 11:46
поделиться

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

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

Это И имеет приоритет над ИЛИ во втором шаблоне. Второй шаблон эквивалентен
(?: Да) | (? :( ?: Нет) (. *) End) .
Чтобы он был эквивалентен первому паттерну, его необходимо изменить на
(? :( ?: Да) | (?: Нет)) (. *) Конец

1
ответ дан 6 December 2019 в 11:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: