Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I delete an element of a referenced array?

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

like image 628
Shawn Avatar asked Dec 11 '10 04:12

Shawn


2 Answers

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

like image 190
DVK Avatar answered Oct 19 '22 22:10

DVK


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

for(my $i = $#array; $i >= 0; --$i) {
    #...
}
like image 24
mu is too short Avatar answered Oct 19 '22 23:10

mu is too short