Поиск 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.
Такой подход имеет свои преимущества — он ограничивается одним шаблоном. Впрочем, имеются и недостатки.
Он однозначно труднее записывается и воспринимается – впрочем, если общий принцип понятен,
все выглядит не так плохо. К тому же это решение медленнее работает — для протестированного набора
данных быстродействие снижается примерно в два раза.
См. также
Proverte kod v komentariyah gde pro list tam oshibki detskie
Оставить комментарий:
|
|