Неинициализированное значение 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 (скопировать) элементы, которые должны быть сохранены, соответственно изменены, в новый массив.

Другие вопросы по тегам