Неинициализированное значение Perl при использовании чередования в регулярных выражениях
У меня есть цикл с оператором if, который выглядит следующим образом:
for (my $i=0; $i < $size; $i++) {
if ($array[$i] =~ m/_(B|P|BC|PM)/) {
#Remove from @array
splice(@array, $i, 1);
next;
}
#Get rid of numbers at the end
$array[$i] =~ s/_[0-9]+//;
}
Я получаю сообщение об ошибке, в котором говорится "Использование неинициализированного значения в @array в сопоставлении с шаблоном...." в строке с оператором if.
Когда я удаляю чередование из регулярного выражения в этой строке, ошибка исчезает. Если я закомментирую весь оператор if, регулярное выражение под комментарием "# Избавиться от чисел в конце" не выдает никаких ошибок.
Я распечатал все значения @array, и все выглядит хорошо. Я пробовал без скобок и скобок вместо скобок в выражении без изменений. Есть идеи, что может быть причиной этого?
2 ответа
Вот простая демонстрация той же проблемы.
1: @array = (1,2);
2: $size = 2;
3: for ($i=0; $i<$size; $i++) {
4: if ($array[$i] == 1) {
5: splice @array, $i, 1;
6: }
7: }
Так что же происходит, когда вы выполняете этот код? В строке 5 вы удаляете первый элемент массива, поэтому массив становится (2)
, В конце первой итерации цикла вы увеличиваете $i
(от 0 до 1), сравните его с $size
(который по-прежнему 2), и решили продолжить цикл.
Тогда вы снова на линии 4. Вы выполняете операцию на $array[1]
, Но @array
имеет только один элемент, $array[1]
не определен, и Perl выдает предупреждение.
Важно быть осторожным, если вы изменяете структуру данных в то же время, что итерируете ее.
-
Рассмотрим альтернативный подход Perlish к первой части вашей проблемы:
@array = grep { !m/_(B|P|BC|PM)/ } @array
То есть выявить все элементы @array
которые удовлетворяют некоторому условию (здесь условие не соответствует шаблону), а затем обновляют @array
так что он содержит только те хорошие элементы. У Здима был еще один хороший подход.
Удаление элементов из массива в принципе дорого, хотя splice
оптимизации помогают. Спасибо ysth за комментарии. Более того, правильная работа с этими индексами требует большой осторожности, что раскрывается и анализируется в ответах мафии. Вот еще один способ
my @new_array =
map {
s/_[0-9]+//; #/ cleanup from the last statement in loop
$_ # return this element, not return of s/../../
}
grep { defined && !/_(B|P|BC|PM)/ } # remove elements
@array;
Первый grep
обязательно пропустить undef
элементы, а затем фильтры, что вам нужно. Его выходной список передается как вход map
, который делает переход от последней строки вашего цикла к каждому элементу.
Если вас не волнует старый массив, просто назначьте @array
вместо того, чтобы сделать @new_array
,
Начиная с 5.14.0 мы можем использовать неразрушающий /r
модификатор в подстановке, который возвращает измененную строку и оставляет оригинал без изменений. Это идеальный вариант использования для этого
@array = map { s/_[0-9]+//r } grep { defined && !/_(B|P|BC|PM)/ } @array;
где исходный массив перезаписывается.
Это обрабатывает данные дважды. Более эффективная версия состоит в том, чтобы перебрать массив и push
(скопировать) элементы, которые должны быть сохранены, соответственно изменены, в новый массив.