views:

218

answers:

6

In Perl, the array index -1 means the last element:

@F=(1,2,3);
print $F[-1]; # result: 3

You can also use the $# notation instead, here $#F:

@F=(1,2,3);
print $F[$#F]; # result: 3

So why don't -1 and $#F give the same result when I want to specify the last element in a range:

print @F[1..$#F]; # 23
print @F[1..-1];  # <empty>

The array @F[1..-1] should really contain all elements from element 1 to the last one, no?

A: 

The [] operator will accept either a single index or a range.

-1 is accepted as index to mean the index of last element. The proper way is $#F.

A range of 1..-1 is empty, and that is why it returns nothing, because the range evaluation is separate from the evaluation of the index.

njsf
There's no [] operator. It's just the brackets that surround the indices. It doesn't do anything to them. It accepts only a list. It doesn't care how you make a list.
brian d foy
+18  A: 

Your problem is the @a[b..c] syntax involves two distinct operations. First b..c is evaluated, returning a list, and then @a[] is evaluated with that list. The range operator .. doesn't know it's being used for array subscripts, so as far as it's concerned there's nothing between 1 and -1. It returns the empty list, so the array returns an empty slice.

ChrisV
I see, that's too bad. I think the Perl maintainers should still make it work as a special case.
It doesn't need to be a special case since it's trivially easy to do it already: $a[1..$#a]. There's no sense making things work differently in the subscripty brackets.
brian d foy
I think it does work differently in Perl 6.
Brad Gilbert
+8  A: 

There's nothing special about .. in an array slice; it just generates the requested range and then the slice of that range is looked up.

So @a[-3..-1] => @a[-3,-2,-1], and @a[1..3] => @a[1,2,3], but @a[1..-1] becomes @a[()].

ysth
+4  A: 

$#F is the last valid index of F, not the number of elements.

@F = (1,2,3); # so $#F is 2

JC1
+1 Yes! You were quicker than me :)
Inshallah
+4  A: 
print join ', ', 1..$#F; # 1, 2, 3, 4, 5, 6, ...
print join ', ', 1..-1;  #

The reason for this is that the '..' operator doesn't do anything special inside of an array subscript.

In list context, it returns a list of values counting (up by ones) from the left value to the right value. If the left value is greater than the right value then it returns the empty list.


$#F is the index of the last element, which is the same as the length minus one '@F -1'. ( If the length is at least one. )

$F[-1] is just a special case, to make it easier to get at elements from the other end, without having to calculate the position manually.

$F[-1] === $F[ @F -1 ] === $F[ $#F ]

@F[ 1 .. (@F -1) ] === @F[ 1 .. $#F ]

@F[ 1 .. (@F -2) ] === @F[ 1 .. ( $#F -1 ) ]

Knowing this you can use variables in a range operator:

use strict;
use warnings;
use feature 'say';

sub list{
  my($arr,$first,$last) = @_;

  $first = @$arr + $first if $first < 0;
  $last  = @$arr + $last  if $last  < 0;

  return @$arr[ $first .. $last ];
}

my @F = 1..3;

say join ', ', list(\@F,1,-1)
2, 3

Note: this is an incomplete example, it won't work correctly for some edge cases

Brad Gilbert
+2  A: 

The rules are:

  • if the array index is non-negative (or, equivalently, greater than $[), start counting at the beginning of the array
  • if it is negative, start counting at the end
  • if given a range, return an array slice

Now, just like the set (a,b) is empty if a > b, the set 1 .. -1 is also empty. Therefore,

@a[ empty set ]

corresponds to an empty array slice.

Sinan Ünür