2012-04-27 3 views
7

У меня есть (что я считаю) условного выражением<@> *(?!QQQ), что я ожидал, чтобы соответствовать, если тестируемой строка является <@> следует любому количество пробелов (ноль в том числе) и затем не, а затем QQQ.Отрицательное опережение утверждения с * модификатором в Perl

Тем не менее, если тестируемая строка <@> QQQ соответствует регулярному выражению.

Я не понимаю, почему это так, и был бы признателен за любую помощь по этому вопросу.

Вот тестовый скрипт

use warnings; 
use strict; 

my @strings = ('something <@> QQQ', 
       'something <@> RRR', 
       'something <@>QQQ' , 
       'something <@>RRR'); 


print "$_\n" for map {$_ . " --> " . rep($_) } (@strings); 



sub rep { 

    my $string = shift; 

    $string =~ s,<@> *(?!QQQ),at w/o ,; 
    $string =~ s,<@> *QQQ,at w/ QQQ,; 

    return $string; 
} 

Печатается

something <@> QQQ --> something at w/o QQQ 
something <@> RRR --> something at w/o RRR 
something <@>QQQ --> something at w/ QQQ 
something <@>RRR --> something at w/o RRR 

И я бы ожидал, что первая линия будет something <@> QQQ --> something at w/ QQQ.

ответ

10

Он соответствует тому, что ноль включен в «любое число». Таким образом, никакие пробелы, за которыми следует пробел, не соответствуют «количеству пробелов, за которыми не следует Q».

Вы должны добавить еще одно утверждение о том, что первое, что после ваших пространств не является пространством. Попробуйте это (непроверенные):

<@> *(?!QQQ)(?!) 

ETA Side Примечание: Изменение квантор до + помогло бы только тогда, когда есть ровно один пробел; в общем случае регулярное выражение всегда может захватить еще одно пространство и, следовательно, преуспеть. Regexes хотят соответствовать, и будут наклоняться назад, чтобы сделать это любым возможным способом. Все остальные соображения (самые левые, самые длинные и т. Д.) Занимают заднее сиденье - если оно может соответствовать более чем одному, они определяют, какой путь выбран. Но совпадение всегда выигрывает над несоответствием.

+3

'(? = \ S)' должно быть '(? = [^])' (В случае, если следующий символ является вкладкой). На самом деле это должно быть '(?!)' (В случае, если это конец строки). – ikegami

+0

Спасибо за улов и редактирование, @ikegami. –

7
$string =~ s,<@> *(?!QQQ),at w/o ,; 
$string =~ s,<@> *QQQ,at w/ QQQ,; 

Одна из ваших проблем заключается в том, что вы просматриваете два регулярных выражения отдельно. Сначала попросите заменить строку без QQQ, а затем заменить строку на QQQ. Это фактически проверяет одно и то же дважды, в некотором смысле. Например: if (X==0) { ... } elsif (X!=0) { ... }. Другими словами, этот код может быть лучше написано:

unless ($string =~ s,<@> *QQQ,at w/ QQQ,) { 
    $string =~ s,<@> *,at w/o,; 
} 

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

Отрицательное внешнее утверждение имеет аналогичное качество, в том смысле, что ему нужно найти только одну вещь, которая отличается, чтобы соответствовать. В этом случае он соответствует части "<@> " как <@> + нет пробела + пробел, где пространство, конечно, «не» QQQ. Вы более или менее находитесь в логическом тупике здесь, потому что квантор * и отрицательный внешний вид друг друга.

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

Однако для теоретических целей рабочее кольцо, которое допускает как любое количество пробелов , так и, должно быть привязано к негативному прогнозу. Многое напоминает Mark Reed. Это может быть самым простым.

<@>(?! *QQQ)  # Add the spaces to the look-ahead 

Разница в том, что теперь пространства и Qs привязаны друг к другу, тогда как до того, как они могут совпадать друг с другом. Для того, чтобы ехать домой точку * квантора, а также решить небольшие проблемы удаления дополнительных мест, вы можете использовать:

<@> *(?! *QQQ) 

Это будет работать, потому что либо из кванторов может соответствовать пустой строке. Теоретически вы можете добавить столько из них, сколько хотите, и это не будет иметь никакого значения (кроме производительности): / * * * * * * */ функционально эквивалентен / */. Разница здесь в том, что пространства в сочетании с Qs могут отсутствовать.

+0

+1 для подробного объяснения '*' – flies

4

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

      +--------------- Matches "<@>". 
         | +----------- Matches "" (empty string). 
         | |  +--- Doesn't match " QQQ". 
         | |  | 
         --- ---- --- 
'something <@> QQQ' =~ /<@> [ ]* (?!QQQ)/x 

Все, что вам нужно сделать, это перетасовать вещи вокруг. Заменить

/<@>[ ]*(?!QQQ)/ 

с

/<@>(?![ ]*QQQ)/ 

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

/<@>[ ]*+(?!QQQ)/ 
/<@>[ ]*(?![ ]|QQQ)/ 
/<@>[ ]*(?![ ])(?!QQQ)/ 

PS — пространства трудно увидеть, поэтому я использую [ ] чтобы сделать их более заметными. В любом случае, он оптимизирован.

+0

добавление '+' исправляет совпадение, но я не могу сказать почему. – flies

+0

Подождите, я думаю, у меня это есть. '[] * +' гарантирует, что все доступные пространства захватываются, даже если он нарушает совпадение, тогда как '[] *' будет захватывать как можно больше, не нарушая совпадения. – flies

+0

@flies, потому что '' "= ~/* + /' может соответствовать только '' ''. Он не будет возвращаться, чтобы соответствовать '' '', поэтому он больше не может найти совпадение '/ * /'. – ikegami