如何删除引用数组的元素?

5

我希望用子程序从一些大数组中删除元素。为了避免在子程序中进行拷贝,我使用引用。

@a=qw(ok now what is hi the matter);

sub zonk {
  $array=shift; # this is a reference of an array
  foreach $i (0..$#$array) { # I saw some say to avoid last element to get size
    #if (@$array[$i] =~ /hi/) { delete @$array[$i]; }
    #if ($array->[$i] =~ /hi/) { delete $array->[$i]; }
    #if ($array->[$i] =~ /hi/) { delete @$array->[$i]; }
    if ($array->[$i] =~ /hi/) { print "FOUND "; }
    print $array->[$i],"\n";
  }
  @$array = grep{$_} @$array; # removes empty elements
}
zonk(\@a);
print join(':',@a);

如果我按照原样运行上述程序,会得到以下结果:
ok
now
what
is
FOUND hi
the
matter
ok:now:what:is:hi:the:matter

但是如果我使用任何被注释掉的行,我会得到以下错误:

delete argument is not a HASH element or slice at hi.pl line 10.

我最初尝试使用splice,但是索引在变化,使迭代混乱。 很高兴能够知道本帖中提到的所有方法,但是我正在寻找最有效的方法:)

补充说明:这在我的Linux机器上完美地运作(Ubuntu 9.10,Perl 5.10),但在我的Windows 7工作机器上使用Perl 5.005_03时出现了上述错误。 升级不是一个选项。

谢谢

5个回答

6
为什么不从一开始就使用grep?
@array = grep { !/hi/ } @array;
# Or, for *referenced* array
@$arrayRef = grep { !/hi/ } @$arrayRef;

以下是对评论中引起疑问的一些说明:

  1. 这种方法(或使用grep的任何方法,包括原始帖子的代码)将增加脚本的内存使用量,其大小为新结果数组的大小。

    例如,如果脚本(不包括第一个数组)占用了10MB的内存,原始数组占用了15MB的内存,而结果数组占用了14MB的内存,则在运行grep时,您的程序的总内存占用量将会增加从25MB到39MB。

  2. 一旦grep完成,原始数组使用的内存将可供垃圾回收使用(与此帖子无关的某些限制除外)。

  3. 然而 - 这很重要 - 即使原始的15MB数据被垃圾回收,那15MB也不会被Perl返回给操作系统 - 例如,脚本的内存占用量将保持39MB,即使在垃圾回收后也不会降至24MB。

  4. 好消息是,这15MB的空闲空间将在程序的其余生命周期中可用于内存分配(忽略内存碎片问题) - 因此,如果您的脚本需要分配额外的1MB、5MB或15MB内存,则其内存占用量不会超过39MB。如果它需要17MB的额外内存,则结果内存占用量仅为41MB,而不是56MB。

  5. 如果这种内存算术对您不满意(例如,如果您的原始数组为500MB,并且您不愿意容忍程序的内存占用量增加到1GB),那么下面Dallaylaen的答案是一个很好的算法,可以在不进行额外的内存分配的情况下完成任务。


这看起来对我来说是答案。如果没有额外的内存使用...下面的评论说是这样的。 - Shawn
@Shawn - 已经修订了答案,并附上了有关内存问题的详细说明。 - DVK

4

将循环顺序反转,就可以使用splice

for(my $i = $#array; $i >= 0; --$i) {
    #...
}

@Shawn - 这是因为这不是指向链表的对象的“迭代器”。我认为Perl除了手动构建LinkedList类之外,没有真正的“迭代器”。 - DVK
@Shawn - 基本上,Perl 数组的行为非常类似于链表,唯一的例外是在列表中间进行随机插入/删除操作时不再是 O(1),正如你所注意到的那样 (但是 shift/unshift/pop/push 操作是 O(1))。 - DVK
2
在我看来,通过 for my $i ( reverse 0 .. $#array ) { 达到了同样的效果,但不那么笨拙。 - ishnid
@ishnid:是的,reverse 会更加优美。主要是整体技巧:在应用更改(删除数组元素、将差异应用于文本片段等)时向后移动,以避免混淆索引。 - mu is too short

2

如果你无论如何都要使用@$array = grep { ... } @$array,为什么不直接使用grep { $_ !~ /hi/ }呢?

但是,如果你真的受到内存限制,你可以尝试从顶部开始:

my $i = @$array;
while ($i-->0) {
    splice @$array, $i, 1 if $array->[$i] =~ /hi/;
}; 

但是这种方法的最坏情况下的性能为n^2,所以与其使用真正的Perl不如使用C-with-dollars来编写:

my $array = [qw(ok now what is hi the matter)];
my $to = 0;
# move elements backwards
for (my $from=0; $from < @$array; $from++) {
     $array->[$from] =~ /hi/ and next;
     $array->[$to++] = $array->[$from];
};
# remove tail 
splice @$array, $to; 
print join ":", @$array;

我仍然不知道为什么delete $array->[$i]不能工作,在我手头的perl 5.10和5.8上可以工作。


我错得离谱:如果使用@array = grep {...} @array,Perl会重复使用内存。加油@DVK! - Dallaylaen
这比那稍微复杂一些,但总体来说是个不错的概括。我会在我的答案下发表有关内存的详细评论。然而,就受到内存限制而言,你的解决方案是我最喜欢的 - +1! - DVK

0
sub zonk {
  $array=shift; # this is a reference of an array
  foreach $i (0..$#$array) { # I saw some say to avoid last element to get size

    print $array->[$i],"\n";

    if ($array->[$i] =~ /hi/) {
      delete @{$array}[$i];
    }

  }
  @$array = grep{$_} @$array; # removes empty elements
}
zonk(\@a);
print join(':',@a);

无法在我的安装有Perl 5.005的Windows 7电脑上运行。我已经在原问题中添加了附录。 - Shawn

0

循环遍历每个键,将每个要删除的项推入数组中,然后使用最终的删除 - 一次完成!

 foreach my $key(keys %$my_array) {     
    my $val= $my_array->{$key};    

    if ($val eq "BAD") {
        push (@unwanted,$key);
    }            
}
delete @{$my_array}{@unwanted};

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