Отрицательное расстояние обратного просмотра регулярного выражения

Я пытаюсь использовать регулярное выражение, чтобы найти что-то в более поздней группе B, чего нет в более ранней группе A. Хорошо, если он существует в A, но не в B. Кажется, это подразумевает необходимость отрицательного взгляда назад, если я должен использовать регулярное выражение.

Сильно упрощенный пример:

Текст:

groupA:
tag 789
tag 123

groupB:
Item 1:
... 123
... 456

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

regex:(\.\.\. (?<ID>(\d+)))(?<=(?s).*?tag (\k<ID>))

Моей идеальной целью было бы сопоставить элементы в группе B, которые не существуют в группе A, и я не могу изменить порядок ввода. Правильный вывод примера: (не выполнено предоставленным регулярным выражением)

... 456

.NET поддерживает переменное расстояние просмотра, но, очевидно, я что-то упускаю!


person Kenny    schedule 06.08.2012    source источник


Ответы (2)


Механизм регулярных выражений .NET анализирует символы слева направо, поэтому вы не можете использовать обратную ссылку справа налево. К тому времени, когда вы достигнете группы B, все персонажи в группе A уже израсходованы и не могут быть сопоставлены. Хотя может показаться, что регулярное выражение откатывается назад, на самом деле это предварительное сопоставление или ветвление — синтаксический анализатор никогда не бежит назад.

При опережающем синтаксическом анализаторе вам нужно сначала сопоставить (и сохранить) элементы в группе A, а затем возвращать только элементы из группы B, если они не присутствовали в группе A. Это слишком сложно для детерминированная конечная автоматизация для расчета, так как это невозможно сделать в постоянном пространстве.

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

321 ...
:1 metI
:Bpuorg

321 gat
987 gat
:Apuorg

"((?<id>\d+)(?=\s\.\.\.))(?!.*\k<id>\sgat)"

Результат:

"654"

Вместо этого я бы предложил сделать это проще с чем-то вроде этого:

var groupA = Regex.Matches(text, @"(?<=tag\s)\d+").Cast<Match>().Select(x => x.Value);
var groupB = Regex.Matches(text, @"(?<=\.\.\.\s)\d+").Cast<Match>().Select(x => x.Value);

var bNotA = groupB.Except(groupA);
person Simon MᶜKenzie    schedule 07.08.2012
comment
Это в основном именно то, что я подозревал ранее, но не мог найти ничего, чтобы поддержать мое предположение. Я думал перевернуть струну, но это не совсем правильно. Операция, которую я прошу с требованием одного прямого прохода, обязательно исключит регулярное выражение из рассмотрения в пользу структуры, способной поддерживать массовое хранение. Я поговорю со своим коллегой и посмотрим, сможем ли мы вообще подойти к этому вопросу с другой точки зрения. Большое спасибо за ваше время, сэр. - person Kenny; 07.08.2012
comment
Регулярные выражения, реализованные в большинстве распространенных разновидностей, намного мощнее, чем DFA. Даже когда вы добавляете простые обратные ссылки, вы уже сопоставляете больше, чем обычные языки. Кроме того, упомянутые вами оптимизации, позволяющие избежать обратного отслеживания, применяются только в том случае, если в шаблоне не используются обратные ссылки. Как только появятся обратные ссылки, даже регулярные выражения .NET вернутся к обратному отслеживанию. И, наконец, поисковые запросы сопоставляются справа налево. Помимо всего этого, я предпочитаю ваше решение (не делать все с одним регулярным выражением) для производственного кода. - person Martin Ender; 16.02.2016

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

Ваша идея была на самом деле правильной, но реализация нет. Первая проблема заключается в том, что вы используете положительный ретроспективный анализ, который гарантирует, что то же самое значение появлялось ранее. Вам нужен отрицательный просмотр назад, (?<!...) чтобы убедиться, что значение не появилось.

Однако основная проблема заключается в том, что порядок содержимого вашего просмотра назад неверен. В вашем коде сначала есть подстановочный знак .*?, а затем то, что вы хотите сопоставить с tag (\k<ID>). Но вы ищете tag X где-то слева от вашей текущей позиции, что означает, что подстановочный знак на самом деле должен идти вторым. При просмотреназад текущая позиция механизма регулярных выражений (вне просмотра назад) находится в конце внутреннего шаблона. Представьте, что вы делаете (?<=abc)def. Это соответствует def в abcdef, но не соответствует cbadef. (Более того, если .*? должен был идти впереди, почему бы самому tag (\k<ID>) не перевернуться, как (\k<ID> gat)?

Итак, это отлично работает:

(\.\.\. (?<ID>(\d+)))(?<!(?s)tag (\k<ID>).*?)

Хотя в нем намного больше групп, чем вам на самом деле нужно. Это достаточно:

\.\.\. (?<ID>\d+)(?<!(?s)tag \k<ID>.*?)

Протестируйте здесь.

Что касается комментария Саймона: «Двигатель регулярных выражений .NET анализирует символы слева направо, поэтому вы не можете ссылаться справа налево». Я не знаю об этом. Я не видел кода движка регулярных выражений, но после многих лет экспериментов с разновидностью регулярных выражений .NET и злоупотребления им для всевозможных махинаций я могу сказать, что он, безусловно, похоже отступает, как и следовало ожидать для все практические цели. (Более быстрые алгоритмы, на которые он ссылался, применяются только к шаблонам, которые не используют обратные ссылки, где результаты совершенно неразличимы.) Да, шаблоны сопоставляются слева направо. Но ретроспективный просмотр не обрабатывается до тех пор, пока вы не достигнете ретроспективного просмотра в шаблоне, и он может ссылаться на более ранние захваты, даже если они заканчиваются позже в совпадении.

Два предостережения по этому поводу: а) .NET имеет режим сопоставления справа налево, который фактически обрабатывает шаблон справа налево, поэтому захваты должны появляться после их обратных ссылок в коде и б ) просмотр назад использует указанный режим чтения справа налево (это, к сожалению, не задокументировано), так что движок может на самом деле перемещаться вперед и назад по входной строке, используя просмотр назад в обычных шаблонах слева направо (или просмотр вперед в боковом направлении справа налево). шаблоны -left или просмотр вперед внутри просмотра назад, а что нет, но это, вероятно, не то, что вы хотите использовать в рабочем коде).

Это направление обработки ввода всегда важно при рассмотрении того, когда обратные ссылки допустимы, а когда нет.

person Martin Ender    schedule 16.02.2016