talexb has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to get all of the elements of an array except for the last one, but

@{$arrayref}[0..-2]
returns an empty list. My fall back plan is to copy the array, pop off the last element and use that. The array ref comes from DBI and I want to re-use it afterwards which is why I'm not popping off the last element.

Alex / talexb / Toronto

Life is short: get busy!

Replies are listed 'Best First'.
Re: Trying to slice off all array elements but the last one (use splice)
by grinder (Bishop) on Feb 16, 2004 at 21:24 UTC

    All other things being equal, I'd use splice, the code is much more readable:

    my @all_but_one = splice( @$arrayref, 0, -1 );

    As an added bonus, it's also faster:

    #! /usr/bin/perl -w use strict; use Benchmark qw/cmpthese/; my @x = qw/a b c/; sub with_splice { my $aref = shift; splice( @$aref, 0, -1 ); } sub with_range { my $aref = shift; @{$aref}[0 .. ($#{$aref} - 1)] } print 'range ', join( ' ' => with_range(\@x) ), "\n"; print 'splice ', join( ' ' => with_splice(\@x) ), "\n"; my @z = 0 .. 100_000; cmpthese( shift || 2_000_000, { short_splice => sub { with_splice(\@x) }, short_range => sub { with_range (\@x) }, } ); cmpthese( shift || 1_000, { long_splice => sub { with_splice(\@z) }, long_range => sub { with_range (\@z) }, } ); __RESULTS__ range a b splice a b Benchmark: timing 2000000 iterations of short_range, short_splice... short_range: 9 wallclock secs ( 7.83 usr + 0.00 sys = 7.83 CPU) @ 2 +55489.02/s (n=2000000) short_splice: 5 wallclock secs ( 5.06 usr + 0.01 sys = 5.07 CPU) @ +394453.00/s (n=2000000) Rate short_range short_splice short_range 255489/s -- -35% short_splice 394453/s 54% -- Benchmark: timing 1000 iterations of long_range, long_splice... long_range: 40 wallclock secs (39.73 usr + 0.02 sys = 39.74 CPU) @ 25 +.16/s (n=1000) long_splice: 0 wallclock secs ( 0.01 usr + 0.00 sys = 0.01 CPU) @ 1 +28000.00/s (n=1000) (warning: too few iterations for a reliable count) Rate long_range long_splice long_range 25.2/s -- -100% long_splice 128000/s 508600% --

    As should be obvious (assuming I'm benchmarking the right thing :), the longer the array, the faster the splice.

    update: actually, playing around with the code a bit (because arrays with around 100 000 elements are usually few and far between in production code), I reduced the size down to 500 elements, and the splice method shows no degradation, but the range operator does:

    enchmark: timing 300000 iterations of short_range, short_splice... short_range: 2 wallclock secs ( 1.16 usr + 0.00 sys = 1.16 CPU) @ 2 +59459.46/s (n=300000) short_splice: 1 wallclock secs ( 0.75 usr + 0.00 sys = 0.75 CPU) @ +400000.00/s (n=300000) Rate short_range short_splice short_range 259459/s -- -35% short_splice 400000/s 54% -- Benchmark: timing 200000 iterations of long_range, long_splice... long_range: 36 wallclock secs (37.30 usr + 0.00 sys = 37.30 CPU) @ 53 +62.38/s (n=200000) long_splice: 0 wallclock secs ( 0.50 usr + 0.00 sys = 0.50 CPU) @ 4 +00000.00/s (n=200000) Rate long_range long_splice long_range 5362/s -- -99% long_splice 400000/s 7359% --

    Well, that was fun.

Re: Trying to slice off all array elements but the last one
by Paladin (Vicar) on Feb 16, 2004 at 20:54 UTC
    @{$arrayref}[0 .. ($#$arrayref - 1)];

    The $#$arrayref gets the index of the last element, and you subtract 2 from that to get the index of the third to last element (the last one you want). Update: Mis-read the OP as all except last 2 elements. Fixed.

      Thanks .. you reply is almost correct. It's actually

      @{$arrayref}[0 .. ($#{$arrayref} - 1)];
      but thanks for replying, I appreciate it. The solution involving temporary arrays seemed so ugly. And I added extra braces to remind me (in a months time) that I'm dealing with an ARRAY referece.

      Alex / talexb / Toronto

      Life is short: get busy!

Re: Trying to slice off all array elements but the last one
by dws (Chancellor) on Feb 16, 2004 at 22:50 UTC
    My fall back plan is to copy the array, pop off the last element and use that.

    Is there a reason you don't go with the fall back plan and be done with it? Copy/pop is a solution that's very unlikely to confuse whoever picks up the code next.

      As usual, I'm dealing with a list of objects that came from a database. So I ask for one more than the number I really want so that I can tell if there are more to come. Before I actually display the items, I pop the last one off the list -- except in the case that this is the last page, in which case the last one is a real item.

      Aside: I'm thinking about a different approach -- since I know what offset I'm at, how many objects I show on each page and how many objects are in the total list, I shouldn't have to fall back to 'getting an extra one' -- but that's a comment for another node. And I've also read (somewhere) about a module that takes care of all such list related functions, I jsut haven't used any of them yet. So many modules, so little time.

      So, in my final implementation I actually took the array, popped off the extra one (if I wasn't on the last page) and did the search on that.

      But as a somewhat experienced Perl dude, I should have remembered the $#array bit to provide me with the index of the last member of the array and been able to go from there. Sometimes stuff doesn't come loose when I shake my head. :( That's when I resort to Perl Monks. :)

      Thanks for your response.

      Alex / talexb / Toronto

      Life is short: get busy!

Re: Trying to slice off all array elements but the last one
by ChrisR (Hermit) on Feb 16, 2004 at 20:59 UTC
    #!/usr/bin/perl use strict; my @array = (1,2,3,4,5); my $arrayref = \@array; my @array2 = @{$arrayref}[0..$#{$arrayref}-1]; print @array2; print "\n"; exit;
    This will return 1234
Re: Trying to slice off all array elements but the last one
by CountZero (Bishop) on Feb 16, 2004 at 20:59 UTC
    @{$arrayref}[0 .. $#{$arrayref}-1] perhaps?

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Trying to slice off all array elements but the last one
by blue_cowdawg (Monsignor) on Feb 16, 2004 at 21:03 UTC

        I'm trying to get all of the elements of an array except for the last one,

    Just for grins I whipped up the following code:

    use Data::Dumper; use strict; my @foo=qw / a b c d e f / ; printf "Before:\n"; print Dumper(\@foo); $#foo--; printf "After:\n"; print Dumper(\@foo);

    When run it produced:

    Before: $VAR1 = [ 'a', 'b', 'c', 'd', 'e', 'f' ]; After: $VAR1 = [ 'a', 'b', 'c', 'd', 'e' ];
    The key here is the decrement of $#foo which reduces the maximum index value for the array @foo. IIRC the element you are disassocating will be reaped in memory eventually.


    Peter L. Berghold -- Unix Professional
    Peter at Berghold dot Net
       Dog trainer, dog agility exhibitor, brewer of fine Belgian style ales. Happiness is a warm, tired, contented dog curled up at your side and a good Belgian ale in your chalice.
      It's also not what the OP wanted. The idea was to slice the array non-destructively Messing with $#foo is also very mean to your maintainer(s). It's the very definition of action-at-a-distance. In other words, Don't do this unless you know exactly why you shouldn't.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      I found $#foo-- to be both clever and scary at the same time. :)

      Hanlon's Razor - "Never attribute to malice that which can be adequately explained by stupidity"
      IIRC the element you are disassocating will be reaped in memory eventually.
      The Camel Book says it doesn't get reaped (p. 76):
      Truncating an array does not recover its memory. You have to undef($whatever) to free its memory back to your process's memory pool.

      CountZero

      "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Trying to slice off all array elements but the last one
by chromatic (Archbishop) on Feb 16, 2004 at 20:50 UTC

    Negative ranges don't work. Try instead:

    reverse( @{ $arrayref }[ -2 .. 0 ])

    Update: I misread the question, but that doesn't mean that negative ranges don't work. :)

      That gets elements -2, -1, and 0. i.e. the second to last, last, and first elements, then reverses them, giving you the first, last, then second to last elements

      Interesting, but it fails .. I get indices -2, -1 and 0.

      Alex / talexb / Toronto

      Life is short: get busy!