Perl:删除数组项并调整数组大小

4

我正在尝试使用另一个数组来过滤Perl中的术语数组。我在 OS X 上拥有 Perl 5.18.2,但如果我使用 use 5.010,行为是相同的。以下是我的基本设置:

#!/usr/bin/perl
#use strict;
my @terms = ('alpha','beta test','gamma','delta quadrant','epsilon',
             'zeta','eta','theta chi','one iota','kappa');
my @filters = ('beta','gamma','epsilon','iota');
foreach $filter (@filters) {
    for my $ind (0 .. $#terms) {
        if (grep { /$filter/ } $terms[$ind]) {
            splice @terms,$ind,1;
        }
    }
}

这样可以筛选出与各种搜索条件匹配的行,但是数组长度并没有改变。如果我输出结果为@terms的数组,会得到如下内容:

[alpha]
[delta quadrant]
[zeta]
[eta]
[theta chi]
[kappa]
[]
[]
[]
[]

正如您所期望的那样,打印scalar(@terms)会得到10的结果。
我想要的是一个长度为6的结果数组,而不是末尾的四个空项。我该如何获得这个结果?为什么数组没有缩小,因为关于splice的perldoc页面说,“数组会根据需要增长或缩小。”
(我不太擅长Perl,所以如果您在想“为什么不只是……?”,几乎肯定是因为我不知道或者听到时没有理解它。)

1
grep 操作数组并返回匹配的元素。也许你想要使用 $terms[$ind] =~ /$filter/ 来匹配单个元素? - tadman
是的,看起来它按预期工作了 - 谢谢!我仍然困惑于为什么数组在我之前所做的事情中没有缩小。 - Eric A. Meyer
在你正在迭代的数组中删除元素总是很棘手的。这会每次将偏移量向后移动1个位置,因为你在切割某些东西。 - tadman
1
值得一提的是,use VERSION 只指定了所需的最低版本;它并不模拟 Perl 解释器在该版本中的存在。 - Matt Jacob
2个回答

7
您可以随时重新生成不需要的数组。grep作为一个过滤器,允许您决定哪些元素是您想要的,哪些不是:
#!/usr/bin/perl

use strict;

my @terms = ('alpha','beta test','gamma','delta quadrant','epsilon',
           'zeta','eta','theta chi','one iota','kappa');
my @filters = ('beta','gamma','epsilon','iota');

my %filter_exclusion = map { $_ => 1 } @filters;

my @filtered = grep { !$filter_exclusion{$_} } @terms;

print join(',', @filtered) . "\n";

如果你手头有一个像%filter_exclusion这样简单的结构,那么这将非常容易。

更新:如果你想允许任意子字符串匹配:

my $filter_exclusion = join '|', map quotemeta, @filters;

my @filtered = grep { !/$filter_exclusion/ } @terms;

那个只有部分起作用——它过滤了gammaepsilon,但没有过滤beta testone iota。虽然如此,在未来的项目中保留这个工具还是很有用的! - Eric A. Meyer
添加了一个测试任意子字符串的版本。这个版本再次使用正则表达式,但每个条目只进行一次测试,而不是N次测试。 - tadman
太棒了,谢谢!确实有效。不过,我完全不知道它是如何或为什么有效的。 - Eric A. Meyer
grep 在这里就像是一个针对 @terms 中每个元素的及格或不及格过滤器,因此对于给定的 @terms 中的 $_,它会测试它是否与该模式匹配。该模式只是一个正则表达式,可以将其中任何一个作为子字符串进行匹配。 - tadman

0
为了查看发生了什么,请在每个步骤中打印数组的内容:当您切割数组时,它会缩小,但是您的循环迭代范围为0 ..$#terms,因此在循环结束时,$ind将指向数组末尾之后。当您使用grep { ... } $array[ $too_large ]时,Perl需要将不存在的元素别名到$_内部的grep块中,因此它在数组中创建一个undef元素。
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my @terms = ('alpha', 'beta test', 'gamma', 'delta quadrant', 'epsilon',
             'zeta', 'eta', 'theta chi', 'one iota', 'kappa');
my @filters = qw( beta gamma epsilon iota );

for my $filter (@filters) {
    say $filter;
    for my $ind (0 .. $#terms) {
        if (grep { do {
            no warnings 'uninitialized';
            /$filter/
        } } $terms[$ind]
        ) {
            splice @terms, $ind, 1;
        }
        say "\t$ind\t", join ' ', map $_ || '-', @terms;
    }
}

如果你使用 $terms[$ind] =~ /$filter/ 而不是 grep,你仍然会得到未初始化警告,但由于没有必要为元素创建别名,因此它也不会被创建。

@ikegami:我在输出中没有看到“gamma”。此外,这不是一个“修复”,它只应该演示为什么和何时创建尾随元素-因此,它们仍然存在。 - choroba
@ikegami:如果我打印“@terms”,我会看到“alpha delta quadrant zeta eta theta chi kappa”。 - choroba
抱歉,如果您从 @terms = qw( gamma gamma kappa ); 开始,则会出现错误。第二个 gamma 被移动到 $terms[0] 中,而该位置不会被重新访问。 - ikegami
1
@ikegami:没错,你说得对。但我只是想解释一下为什么 undef 存在。 - choroba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接