在Perl中,从数组中删除一个值的最佳方法是什么?

89

这个数组有很多数据,我需要删除其中两个元素。

下面是我正在使用的代码片段:

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;

4
这将删除三个元素。 - Medlock Perlman
需要从目录列表中删除所有非文件项,"array = grep { -f $_ } array" 对我来说非常有效 :) - taiko
15个回答

95

如果您已经知道要删除的元素的索引,可以使用splice。

如果您正在搜索,可以使用Grep。

如果您需要频繁进行此操作,则保持数组排序后,可以更好地提高性能,因为可以使用二分查找来找到所需的索引。

如果在您的上下文中有意义,您可能希望考虑使用“魔法值”来代替删除记录,以节省数据移动 - 例如将已删除的元素设置为undef。 当然,这也有其自身的问题(如果您需要知道“活动”元素的数量,您需要单独跟踪等),但根据您的应用程序可能值得一试。

编辑 实际上现在我再看一眼--不要使用上面的grep代码。 找到要删除的元素的索引,然后使用splice删除它会更有效率(您现在的代码会积累所有不匹配的结果..)

my $index = 0;
$index++ until $arr[$index] eq 'foo';
splice(@arr, $index, 1);

这将删除第一次出现的内容。 删除所有出现的内容也非常类似,只是您需要在一个步骤中获取所有索引:

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

剩下的部分留给读者自己练习--记住,当你使用splice函数时,数组会发生变化!

编辑2 John Siracusa正确指出我在示例中有一个错误..已修复,对此感到抱歉。


15
如果找不到该字符串,循环将会停滞。因此,我的代码中加入了 $index = 0; 以避免这种情况发生。 首先,通过使用 scalar @arr 来获取数组 @arr 的长度,我们可以得到需要遍历的元素数量。 然后,使用 until 循环语句来对数组进行遍历,直到找到目标字符串 'foo' 或者遍历完整个数组。 最后,使用 splice 函数来删除找到的字符串 'foo' 在数组 @arr 中的元素。 - Amir.F
2
或者 my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); } - 对于第一个匹配 - Reflective

15

splice会按索引从数组中删除一个或多个元素。在您的示例中可以使用grep来搜索和删除。


感谢spoulson。我不知道要删除哪些索引,所以不得不使用grep。 - user21246

12

你可以简单地这样做:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";

8

您是否经常需要执行此操作?如果是,您可能需要考虑使用不同的数据结构。每次都要搜索整个数组可能会非常耗时,特别是对于大型数组。如果速度是一个问题,那么您可能需要考虑使用哈希表。

在您的示例中,键将是数字,而值将是该数字元素的计数。


5
您可以使用数组切片而不是插入。使用grep返回要保留的索引,然后使用切片:
my @arr = ...;
# run through each item.
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indicesToKeep];

我特别喜欢这种方法的逻辑和优雅。 - Keve
确实可以,你甚至可以将其写成一行代码,如:@arr = @arr[grep ...],我特别喜欢这种方式。虽然我不确定它的效率如何,但我会开始使用它,因为它肯定不会比其他解决方案更糟糕。 - soger

5

如果你改变了

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

to

my @del_indexes = reverse(grep { $arr[$_] eq 'foo' } 0..$#arr);

这样做是为了避免数组重新编号的问题,首先从数组末尾开始删除元素。 在foreach循环中加入splice()可以清理@arr。相对简单和易读...
foreach $item (@del_indexes) {
   splice (@arr,$item,1);
}

4
我发现最好的方法是将 "undef" 和 "grep" 结合使用:
foreach $index ( @list_of_indexes_to_be_skiped ) {
      undef($array[$index]);
}
@array = grep { defined($_) } @array;

这就是诀窍! 费德里科


将元素值设置为 null。总元素数(大小)仍然相同。 - Boontawee Home
1
@BoontaweeHome,最后的 grep 命令会将它们删除。 - Deanna
1
如果您已经知道索引,只需使用 splice - brian d foy

3

我认为你的解决方案是最简单且易于维护的。

本文的其余部分记录了将元素的测试转换为splice偏移量所遇到的困难,从而使答案更具完整性

看看您必须经历的曲折过程,才能拥有一个高效(即一次通过)的算法,将列表项上的测试转换为索引。这并不直观。

sub array_remove ( \@& ) { 
    my ( $arr_ref, $test_block ) = @_;
    my $sp_start  = 0;
    my $sp_len    = 0;
    for ( my $inx = 0; $inx <= $#$arr_ref; $inx++ ) {
        local $_ = $arr_ref->[$inx];
        next unless $test_block->( $_ );
        if ( $sp_len > 0 && $inx > $sp_start + $sp_len ) {
            splice( @$arr_ref, $sp_start, $sp_len );
            $inx    = $inx - $sp_len;
            $sp_len = 0;
        }
        $sp_start = $inx if ++$sp_len == 1;
    }
    splice( @$arr_ref, $sp_start, $sp_len ) if $sp_len > 0;
    return;
}

3
一个简单的“grep”比那个更易理解和更有效率。 - Randal Schwartz
5
有人删除了我的评论,你显然没有读懂文本。 - Axeman

2

如果数组中存在“something”,则删除所有出现的内容。

根据SquareCog的答案:

my @arr = ('1','2','3','4','3','2', '3','4','3');
my @dix = grep { $arr[$_] eq '4' } 0..$#arr;
my $o = 0;
for (@dix) {
    splice(@arr, $_-$o, 1);
    $o++;
}
print join("\n", @arr);

每次我们从@arr中删除索引时,下一个正确的要删除的索引将是$_-current_loop_step

2
您可以使用非捕获组和 '|' 分隔符列表来删除内容。

perl -le '@ar=(1 .. 20);@x=(8,10,3,17);$x=join("|",@x);@ar=grep{!/^(?:$x)$/o} @ar;print "@ar"'

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