4

I find myself wanting to find the index of the last non-zero element in an array. So, given:

my @array = (0,0,5,9,0,0,0,7,0,3,0,0);
my $indexLastNonZero = insertElegantMethodHere(@array);
# expect $indexLastNonZero to be equal to 9;

I've done this:

for my $i (0 .. $#array) {
    $indexLastNonZero = $i if $array[$i] != 0;
};

I works but somehow I can't help feel there must be a super elegant (smarter? nice? more efficient?) way of doing this in perl. I've looked into List::Utils but not found a nice way there and would like a non-core-module independent method.

Any thoughts?

Cheers

moigescr
  • 87
  • 6

6 Answers6

13

Use List::MoreUtils for such tasks:

use warnings;
use strict;

use List::MoreUtils;

my @array = (0,0,5,9,0,0,0,7,0,3,0,0);

print List::MoreUtils::lastidx { $_ } @array
René Nyffenegger
  • 39,402
  • 33
  • 158
  • 293
  • Thanks. I did look into List::MoreUtils but (and do correct me if I'm wrong) but I think it is not a core module and my questions specified that "would like a non-core-module independent method" (because the box I run on perl is not mine and I for various reasons, I can't add modules!). – moigescr Jul 28 '14 at 15:38
  • 1
    @moigescr Then download the `List::MoreUtils` source and include it with your script. – ThisSuitIsBlackNot Jul 28 '14 at 15:43
  • @moigescr, That makes no sense. If you can install Perl from SO, you can install code from CPAN. – ikegami Jul 28 '14 at 15:58
  • @ikegami, I can't install Perl (I use a linux box running Perl on which I am granted an account with limited privileges)... but I think we might be straying off-topic! – moigescr Jul 28 '14 at 16:03
  • 1
    @moigescr ikegami's point was that if you can use Perl *code* from Stack Overflow, you can use Perl code from CPAN. Installing CPAN modules [does not require root access](http://stackoverflow.com/questions/3735836/how-can-i-install-perl-modules-without-root-privileges); worst case, simply download the source and include it in your project (most CPAN modules are licensed under the GPL or the Artistic License). You're significantly limiting yourself by avoiding CPAN. Why re-invent wheels when others have already created, refined, and road-tested thousands of them for you? – ThisSuitIsBlackNot Jul 28 '14 at 17:25
5
my @array = (0,0,5,9,0,0,0,7,0,3,0,0);
my ($indexLastNonZero) = grep $array[$_], reverse 0 .. $#array;
mpapec
  • 50,217
  • 8
  • 67
  • 127
  • my personal favourite of solutions posted so far. It feels nicely "perlish". On the downside, if the array was huge (which in my case it is not) and mostly non-zero, that you'd be creating a large temporary array which @MichaelCarman's method does not. – moigescr Jul 28 '14 at 16:00
  • This does a lot of extra work: It builds a list of indexes of the same size as the array, reverses it (creating a second list), and then finds the index of all non-zero elements (discarding all but the first). – Michael Carman Jul 28 '14 at 16:08
  • @MichealCarman. Indeed, it's far from efficient.. but I think it does look good and my array is small. It's my favourite but I agree it's probably not the best (which at present is still yours ;)) Waiting to see if more ideas come through but close to giving you the nod ;) – moigescr Jul 28 '14 at 16:39
  • 2
    @moigescr your approach is already efficient (`for (0..$#arr)` doesn't generate list internally) and you can shorten it if you like `$array[$_] and $indexLastNonZero = $_ for 0 .. $#array;` – mpapec Jul 28 '14 at 17:19
5

Start at the end of the array and work backwards until you find a non-zero element:

my @array = (0,0,5,9,0,0,0,7,0,3,0,0);

my $i = $#array;
$i-- while $i >= 0 && $array[$i] == 0;

print "The last non-zero element is at index $i\n";

The $i >= 0 test is to guard against the edge case where all elements are zero. In that case the resulting value of $i is -1.

Michael Carman
  • 30,628
  • 10
  • 74
  • 122
  • 1
    my second favourite of the solutions posted. thanks. as many of you have enlightened me, the trick is to start looking from the end of the array! – moigescr Jul 28 '14 at 16:01
1

You could use List::Util, which is in core:

use strict;
use warnings; 

use List::Util qw(first);

my @array = (0,0,5,9,0,0,0,7,0,3,0,0);
my $index = @array;

first { $index-- && $_ } reverse @array;

print "Last index that is non-zero: $index\n"; 
Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170
0

Destructive approach so take a copy of the array first:

my @array2 = @array;
while (!pop @array2) {} # Remove up to and including the last non-zero
print scalar @array2;   # Size of remaining elements is index of last non-zero
RobEarl
  • 7,862
  • 6
  • 35
  • 50
0
sub last_true {
    pop and return scalar @_ while @_;
    undef;
}

my $index = last_true(@foo);
salva
  • 9,943
  • 4
  • 29
  • 57