在正则表达式中使用交替时 Perl 未初始化的值

Perl uninitialized value when using alternation in regex

我有一个带有 if 语句的 for 循环,如下所示:

   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]+//;
   }

我收到一条错误消息,指出在 if 语句的行上显示 "Use of uninitialized value within @array in pattern match...."。

当我从该行的正则表达式中删除交替时,错误消失了。如果我注释掉整个 if 语句,则注释“#Get rid of numbers at the end”下的正则表达式不会产生任何错误。

我已经打印出@array 的所有值,一切看起来都很好。我试过不用括号和括号代替表达式中的括号,没有任何变化。知道是什么原因造成的吗?

这是同一问题的简单演示。

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)。在第一次 for 循环迭代结束时,您递增 $i(从 0 到 1),将其与 $size(仍然是 2)进行比较,然后决定继续循环。

那你又在第4行了。您正在对 $array[1] 执行操作。但是@array只有一个元素,$array[1]没有定义,Perl给你一个警告

如果您在迭代数据结构的同时修改数据结构,请务必小心。

--

考虑使用这种替代的 Perlish 方法来解决问题的第一部分:

@array = grep { !m/_(B|P|BC|PM)/ } @array

即找出@array中所有满足某种条件的元素(这里的条件是不匹配模式),然后更新@array使其只持有那些好的元素. zdim 还有另一个好方法。

从数组中删除元素原则上是昂贵的,即使 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.

开始from 5.14.0我们可以在替换中使用非破坏性/r修饰符,returns改变的字符串并留下原来不变。这是一个完美的用例

@array = map { s/_[0-9]+//r } grep { defined && !/_(B|P|BC|PM)/ } @array;

原始数组被覆盖的地方。


这会处理数据两次。一个更有效的版本是遍历数组并push(复制)要保留的元素,适当地改变,到新数组中。