Поиск N-го совпадения

Проблема

Требуется найти не первое, a N-e совпадение шаблона в строке. Допустим, вы хотите узнать, какое слово предшествует третьему экземпляру слова fish:
One  fish  two  fish   red  fish  blue  fish

Решение

Воспользуйтесь модификатором /g и считайте совпадения в цикле while:
$WANT = 3;
$count = 0:
while (/(\w+)\s+fish\b/gi) {
  if (++$count == $WANT) {
    print "The third fish is a $1 one.\n";
    # Предупреждение: не выходите из этого цикла с помощью last
  }
}
The  third  fish  is  a   red  one.
Или воспользуйтесь счетчиком и шаблоном следующего вида:
/(?:\w+\s+fish\s+){2}(\w+)\s+fish/i;

Комментарий

Как объяснялось во введении к этой главе, при наличии модификатора /g в скалярном контексте происходит многократный поиск. Его удобно использовать в циклах while — например, для подсчета совпадений в строке:
# Простой вариант с циклом while
$count = 0;
while ($string =~ /PAT/g) {
  $count++;       # Или что-нибудь другое
}
# То же с завершающим циклом while
$count = 0;
$count++ while $string =~ /PAT/g;
# С циклом for
for ($count = 0; $string = ~/PAT/g; $count++) { }
# Аналогично, но с подсчетом перекрывающихся совпадений
$count++ while $string =~ /(?=PAT)/g;
Чтобы найти N-й экземпляр, проще всего завести отдельный счетчик. Когда он достигнет N, сделайте то, что считаете нужным. Аналогичная методика может применяться и для поиска каждого N-го совпадения — в этом случае проверяется кратность счетчика N посредством вычисления остатка при делении. Например, проверка (++$count % 3) == 0 находит каждое третье совпадение.
Если вам не хочется брать на себя дополнительные хлопоты, всегда можно извлечь все совпадения и затем выбрать из них то, что вас интересует.
$pond = 'One fish two fish red fish blue fish';
# С применением временного массива 
@colors = ($pond =~ /(w+)\s+fish\b\gi);              # Найти все совпадения
$color = $colors[2];                                 # Выбрать одно
                                                     # интересующее нас
# Без временного массива
$color = ( $pond =~ /(\w+)\s+fish\b/gi )[2];         # Выбрать третий элемент
print "The third fish is the pond is $color.\n";
The  third  fish  in  the  pond  is   red.
В другом примере находятся все нечетные совпадения:
$count = 0;
$_ = 'One fish two fish red fish blue fish';
@evens = grep {$count++ %2 == 1} /(\w+)\s+fish\b/gi;
print "Even numbered fish are @evens.\n";
Even numbered fish are two blue.
При подстановке заменяющая строка должна представлять собой программное выражение, которое возвращает соответствующую строку. Не забывайте возвращать оригинал как заменяющую строку в том случае, если замена не нужна. В следующем примере мы ищем четвертый экземпляр "fish" и заменяем предшествующее слово другим:
$count = 0;
s{
   \b
   ( \w+)
   (
     \s+ fish \b
   )
}{
 if (++$count == 4) {
   "sushi".$2;
 } else {
    $1.$2;
   }
}gex;
One  fish  two  fish   red  fish  sushi  fish
Задача поиска последнего совпадения также встречается довольно часто. Простейшее решение — пропустить все начало строки. Например, после /.*\b(\w+)\s+fish\b/ переменная $1 будет содержать слово, предшествующее последнему экземпляру "fish".
Другой способ — глобальный поиск в списковом контексте для получения всех совпадений и последующее извлечение нужного элемента этого списка:
$pond = 'One fish two fish red fish blue fish swim here. ';
$color = ( $pond =~ /\b(\w+)\s+fish\b/gi )[-1];
print "Last fish is $color.\n";
Last fish is blue.
Если потребуется найти последнее совпадение без применения /g, то же самое можно сделать с отрицательной опережающей проверкой (?!НЕЧТО). Если вас интересует последний экземпляр произвольного шаблона А, вы ищете А, сопровождаемый любым количеством «не-А», до конца строки. Обобщенная конструкция имеет вид А(?!.*А)*$, однако для удобства чтения ее можно разделить:
m{
   А              # Найти некоторый шаблон А
  (?!             # При этом не должно находиться
     .*           # что-то другое
     А            # и А
  )
  $               # До конца строки
}х
В результате поиск последнего экземпляра "fish" принимает следующий вид:
$pond = 'One fish two fish red fish blue fish';
if ($pond =~ m {
                 \b ( \w+) \s+ fish \b
                 (?! .* \b fish \b ) *
               }six )
{
print "Last fish is $1/\n";
} else {
  print "Failed!\n";
  }
Last fish is blue.
Такой подход имеет свои преимущества — он ограничивается одним шаблоном. Впрочем, имеются и недостатки. Он однозначно труднее записывается и воспринимается – впрочем, если общий принцип понятен, все выглядит не так плохо. К тому же это решение медленнее работает — для протестированного набора данных быстродействие снижается примерно в два раза.

См. также




2013-09-10 17:05:19

Proverte kod v komentariyah gde pro list tam oshibki detskie




Оставить комментарий:
Ваше Имя:
Email:
Антибот: *  
Ваш комментарий: