Link to home
Start Free TrialLog in
Avatar of samj
samj

asked on

pop $AoA[x]<[y]

Hello

I have an array of arrays
ARRAY(0X1f...)ARRAY(0x...)ARRAY(0X...)...

I am trying to remove the ARRAY with the lowest value in the subarray[n]
my ($tmp,$i)= (0,0);
for (0 .. @AoA-1) {
   if ( $AoA [$i][0] < $tmp) {
       $tmp = $AoA[$i][0]ñ
       $i = $_;
   }
}
$set[$i]=undef;
but this last line will undef that element and will still be there in the array with the value undef, but I want to remove it all togeather and reduce the array as if it did not have it.

thanks
Avatar of jmcg
jmcg
Flag of United States of America image

In general, when you hear mention of "removing an element of an array", you need to use the 'splice' operator -- unless the element to be removed is the first member of the array, where you'd use 'shift', or the last element of the array, where you'd use 'pop'.
Avatar of josephfluckiger
josephfluckiger

here is some code that does the trick, plus a sample 2d array. It uses a sort operator if you don't mind sorting each row of the two dimensional array.
-------------------------------
@AoA = (
[6,5,4],
[3,2,1],
[9,8,7]);

&printAoA("before sort");#this function is just to print out what we have so far

my ($lowball,$lowindex);
$lowball = 1000000;
for $i (0..$#AoA) {
      @{ $AoA[$i] } = sort @{$AoA[$i]};#sort rows of 2d array so that lowest always comes first.

      if ($AoA[$i][0] < $lowball){#find the first element of each array that is the lowest and keep track of it's index and value.
            $lowball = $AoA[$i][0];
            $lowindex =$i;
      };
}
&printAoA("after sort");
print "lowball:$lowball,lowindex:$lowindex\n";

@AoA = (@AoA[0..$lowindex-1],@AoA[$lowindex+1..$#AoA]);#take a slice of the array, dropping the row with the lowest first value.#here is the slice operation that jmcg was talking about. This is the key trick*****

&printAoA("after sliced");

sub printAoA{
      print "$_[0]:\n";
      for $i ( 0 .. $#AoA ) {
          print "[ @{$AoA[$i]} ],\n";
      }
      print "\n";
}
----------------------------------------
One more note...
You should initialize $tmp to $AoA[0][0].  If everthing is > 0, you will end up with $i = 0 instead of the array with the smallest first value...

Change:
my ($tmp,$i)= (0,0);
to
my ($tmp,$i)=($AoA[0][0],0);
Avatar of samj

ASKER

I feel like I did not explain my problem correctly,
I must not sort the inside array but use it's current first value and delete that array if it has the lowest value compairing to the first value in the rest of the sub arrays.
so
@AoA = (
[6,5,4],
[3,2,1],
[9,8,7]);
the middle array has its first value 3 so that is the subarray I need to delete without sorting it since it also could be [3,9,9] which would be the same thing for my purpose (it's first value is still 3) so delete this.

thanks again
Ok, then I was making this much harder than it had to be.
I found your error, you were using $i instead of $_ in the inner loop. (threw me for a loop for a while)

---------------------

my ($tmp,$i)= ($AoA[0][0],0);
for (0 .. $#AoA) {
   if ( $AoA[$_][0] < $tmp) {
       $tmp = $AoA[$_][0];
       $i = $_;
   }
}
@AoA = (@AoA[0..$i-1],@AoA[$i+1..$#AoA]);

-----------------------
There's a difference between 'slice' and 'splice' and it is the latter that I was recommending.

Now that you've explained a bit more about what you were doing, I think I can see how I'd write it:

my ( $lowindex, $lowval ) = ( 0, $AoA[0][0]);
for (1 .. $#AoA) {
   if ( $AoA [$_][0] < $lowval) {
       ($lowindex, $lowval) = ($_, $AoA[$_][0]);
       $i = $_;
      }
  }

# now remove the selected element

splice( @AoA, $lowindex, 1);

This is fairly close to what you started with, but there are a couple of things to note:
When attempting to find a minimum or maximum, just take the first element as a starting point. Choosing some arbitrary value like 0 or 1000000 as a starting point will eventually be overthrown somewhere far down the road by a data set where that arbitrary value is unexpectedly larger or smaller than all of the data and you won't find the real maximum or minimum. I initialized with the index 0 values, then started the loop at 1.

In your loop, you were indexing with $i where I believe you meant $_.

Oops. There's no longer any need for that line

 $i = $_;
Avatar of samj

ASKER

I am trying to evaluate the comments above and get the fastest code

#!/perl -w
use strict;
use warnings;
use Benchmark qw (:all);

my @AoA = (
         [6,5,4],
         [3,2,1],
         [9,8,7]
         );
cmpthese(2, {
        'a' => 'del1 ( @AoA )',
        'b' => 'del2 ( @AoA )',
      'c' => 'del3 ( @AoA )',
    });

#by josephfluckiger
sub del1 {
    my @AoA = @_;
    my ($lowball, $lowindex) = ( 1000000, 0 );
    for my $i (0..$#AoA) {
      if ($AoA[$i][0] < $lowball){
          $lowball = $AoA[$i][0];
          $lowindex =$i;
      };
    }
    @AoA = (@AoA[0..$lowindex-1],@AoA[$lowindex+1..$#AoA]);
    return @AoA;
}

#by josephfluckiger
sub del2 {
    my @AoA = @_;
    my ($tmp,$i)= ($AoA[0][0],0);
    for (0 .. $#AoA) {
line 36      if ( $AoA[$_][0] < $tmp) {
          $tmp = $AoA[$_][0];
      $i = $_;
      }
    }
    @AoA = (@AoA[0..$i-1],@AoA[$i+1..$#AoA]);
    return @AoA;
}

#by jmcg
sub del3 {
    my @AoA = @_;
    my ( $lowindex, $lowval ) = ( 0, $AoA[0][0]);
    for (1 .. $#AoA) {
      if ( $AoA [$_][0] < $lowval) {
          ($lowindex, $lowval) = ($_, $AoA[$_][0]);
      }
    }
    splice( @AoA, $lowindex, 1);
    return @AoA;
}

Use of uninitialized value in numeric lt (<) at file line 36
A. you should pass your array by reference. Otherwise the marshalling of the lists will be a large component of the time it takes for each iteration.

B. in order to do the comparison, you'll want to reinitialize the input array between tests, since the action of the routine reduces the array on each iteration.
Avatar of samj

ASKER

[Qoute] A. you should pass your array by reference. Otherwise the marshalling of the lists will be a large component of the time it takes for each iteration.
e.g.
cmpthese(2, {
        'a' => 'del1 ( \@AoA )',
        'b' => 'del2 ( \@AoA )',
        'c' => 'del3 ( \@AoA )',
    });
then inside each sub do
my @AoA = @{$_};

[Qoute]
B. in order to do the comparison, you'll want to reinitialize the input array between tests, since the action of the routine reduces the array on each iteration.
being only the second time to use Benchmark, how do you do this step?

thank you
For the passing you have it right.

Inside the sub, I suppose you'd do:

my @AoA = @{$_[0]};

But here we come to a hole in my knowledge of Perl. Under certain circumstances, loop variables and arguments passed to subroutines are _aliases_, so changing their value changes the original variable in the calling scope. But by doing the assignment like this in the subroutine, you are forcing a copy and the modified array is then eventually returned as the result. Passing a reference into a subroutine also gives a way to modify a variable in the calling scope, but you then have to continue to use it as a reference (although you can make a copy of a reference and still have it point to the original data) in the subroutine. So I'm not sure what the fastest method is for what you're trying to do. (I'm hoping to learn from your experiment!)

You've already used Benchmark more than I have. I'm afraid I don't know what provisions it has for reinstating the context of each expression that it repetitively calls. It would not surprise me in the end to learn that all three of these approaches have essentially identical performance. Or at least, that it's too hard to construct a test that shows much of a difference given how much has to be set up for each iteration.

ASKER CERTIFIED SOLUTION
Avatar of terageek
terageek

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of samj

ASKER

all what I had to do to solve this problem was
@tmp = sort { $a->[0] <=> $b->[0]} @tmp;
shift @tmp;

thanks for all the comments
2 lines of code for my 22 lines of code, not bad :)