How can I delete an element of a referenced array?

烂漫一生 提交于 2019-12-12 10:52:57

问题


I want to remove elements from a few large arrays with a subroutine. I use a reference to avoid a copy into the sub.

@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);

If I run the program above as is I get:

ok
now
what
is
FOUND hi
the
matter
ok:now:what:is:hi:the:matter

But if I use any of the commented lines instead I get:

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

I tried splice originally but then the indices were shifting and confused the iteration. Would be nice to know all the methods mentioned in this post, however the most efficient is what I am looking for :)

Addendum: This works perfectly (I mean every commented line) on my linux machine (ubuntu 9.10, perl 5.10) but the above error is on my Windows 7 box at work using perl 5.005_03. Upgrading is not an option.

Thanks


回答1:


Why not grep from the get-go?

@array = grep { !/hi/ } @array;
# Or, for *referenced* array
@$arrayRef = grep { !/hi/ } @$arrayRef;

A small set of notes to clarify questions that arose in comments:

  1. This method (or any method using grep including original poster's code) will increase the memory usage of the script by the size of the newly resultant array.

    E.g. if the script (sans the first array) took up 10MB of memory, the original array took 15MB of memory, and the resulting array took 14MB of memory, then the total memory footprint of your program will increase from 25MB to 39MB while grep is running.

  2. Once the grep comlpetes, the memory used by the original array will become available for garbage collection (with some caveats irrelevant to this post).

  3. However - and this is important - even if the original 15MB of data are garbage collected, that 15MB will not be returned by Perl to operating system - e.g. the script's memory footprint will remain 39MB and won't drop to 24MB even after garbage collection.

  4. On the good side, that freed-up 15MB will be available for memory allocation throughout the rest of your program's lifetime (leaving aside memory fragmentation issues) - therefore, if your script will require allocation of additionnal 1MB, 5MB, or 15MB of memory, its memory footprint will NOT grow beyond the high-point of 39MB. And if it requires 17MB of additional memory, the resulting memory footprint will only be only 41MB, not 56MB.

  5. If this memory arithmetic is not satisfactory to you (e.g. if your original array was 500MB and you aren't willing to tolerate the program memory footprint rising to 1GB), then Dallaylaen's answer below is a great algorithm for doing the task without extra memory allocation




回答2:


Reverse the order of your loop and you can use splice:

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



回答3:


If you do @$array = grep { ... } @$array anyway, why not just stick with grep { $_ !~ /hi/ }?

However, if you are really memory-bound, you may try to go from the top:

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

But this has a worst-case performance of n^2, so it may be even better to write in C-with-dollars instead of real Perl:

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;

Still I don't know why delete $array->[$i] won't work, it works on perl 5.10 and 5.8 I currently have at hand.




回答4:


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);



回答5:


Loop through each key, push each item to remove into an array, then use a final delete - one swoop!

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

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


来源:https://stackoverflow.com/questions/4415287/how-can-i-delete-an-element-of-a-referenced-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!