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

A friend told me that there is some sort of new list slice range syntax in Perl 5.10 that would allow getting a range starting at some offset continuing thru to the last element in a list - no matter how many things there were in the list. More specifically supposedly there is something that can be put in place of the ??? below that would have the same effect as [3..7]. I haven't found anything like that, but maybe somebody here has!
#!/usr/bin/perl -w use strict; use 5.10.0 my $x = "a b c d e f g h"; my @set = (split /\s+/, $x)[3..???]; # to act like:[3..7] print "@set\n";
Of course if I have something like this:
my @x = qw (0 1 2 3 4 5 6 7 8 9); my @subset = @x[4..@x-1]; print "@subset\n"; # prints 4 5 6 7 8 9
It works because I have the @x array and scalar value is the number of elements. Putting in some large number doesn't work because that generates a lot of undef's that have to be filtered out, ala: (pretty darn ugly!)
@set = grep{defined $_}(split /\s+/, $x)[3..99]; print "@set\n";
So although creating some @temp and then slicing that or probably using splice to get rid of some elements works, the assertion is that there is some clean way to do this in one step. I haven't found it. Any ideas?

Replies are listed 'Best First'.
Re: Getting range from N..end with list slice
by BrowserUk (Patriarch) on Nov 27, 2010 at 13:57 UTC

    You could use a do block to isolate the temporary:

    $s = join'', 'a'..'z';; @bits = do{ local @_ = split'', $s; @_[3..$#_]};; print @bits;; d e f g h i j k l m n o p q r s t u v w x y z

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Getting range from N..end with list slice
by jwkrahn (Abbot) on Nov 27, 2010 at 15:16 UTC
    $ perl -le' use warnings; use strict; use 5.10.0; my $x = "a b c d e f g h"; my @set = splice @{ [ split " ", $x ] }, 3; print "@set"; ' d e f g h

    Update:

    $ perl -le' use warnings; use strict; use 5.10.0; my $x = "a b c d e f g h"; my @set = split " ", ( split " ", $x, 4 )[ -1 ]; print "@set"; ' d e f g h
Re: Getting range from N..end with list slice
by BrowserUk (Patriarch) on Nov 27, 2010 at 15:12 UTC

    Or, if efficiency is sufficient concern to override clarity:

    my @bits = sub{ @_[3..$#_] }->( split'', $s );

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Getting range from N..end with list slice
by CountZero (Bishop) on Nov 27, 2010 at 14:51 UTC
    This is all I can find in Perl51000delta about list slices:
    Subscripts of slices
    You can now use a non-arrowed form for chained subscripts after a list slice, like in:
    ({foo => "bar"})[0]{foo}
    This used to be a syntax error; a -> was required.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: Getting range from N..end with list slice
by JavaFan (Canon) on Nov 27, 2010 at 12:04 UTC
    Not ideal:
    (undef, undef, undef, @set) = (split ' ', $x);
      Good thought. The problem is that this doesn't scale very well. Some of these files can have lines with hundreds of fields. I'm working with file like that now and I wouldn't be surprised if I find some field that means "name of user's first dog"! :)

      Something like this would scale better and run fast, but of course it is two statements and doesn't use list slice.

      my $x = "a b c d e f g h"; my @set = (split /\s+/, $x); splice(@set,0,3); print "@set\n"; #prints d e f g h
        Personally, I wouldn't fret on it. If the point is you want to do it all in one statement:
        my @set = (split /\s+/, $x)[3 .. -1 + split /\s+/, $x];
        But that splits twice.
Re: Getting range from N..end with list slice
by ambrus (Abbot) on Nov 27, 2010 at 18:12 UTC

    Just a note: take care with using grep { defined } that way, for it might have odd side effects:

    $ perl -wE '@x = qw"a b c d e f g h"; say 0+@x; say join ":", grep { d +efined } @x[4 .. 99]; say 0+@x;' 8 e:f:g:h 100 $

      It's very hard to twist what the OP has into something that's a problem, so I guess you're just providing trivia. In that case, let me add that any lvalue context will suffice.

      >perl -E"@a[4..99] = (); say 0+@a;" 100 >perl -E"for (@a[4..99]) {} say 0+@a;" 100 >perl -E"sub {}->(@a[4..99]); say 0+@a;" 100 >perl -E"grep 1, @a[4..99]; say 0+@a;" 100
Re: Getting range from N..end with list slice
by ikegami (Patriarch) on Nov 27, 2010 at 20:41 UTC

    ".." is the range operator. It can create a list of ascending numbers. You need to know the start and end of the range. In general, there's nothing you can count to determine the start and end of the range when doing a list slice.

    And here's the doozy. This probably isn't documented, but in a list slice, the index expression is evaluated before the list expression. This means you need to know how many elements split will return before split is even called. That means you need to find an alternate code arrangement.

    Some solutions:

    (undef, undef, undef, my @set) = split /\s+/, $x; my @set = split /\s+/, $x; splice(@set, 0, 3); my @set = sub { @_[3..$#_] }->( split /\s+/, $x );

    The last one hasn't been mentioned yet.

    Update: Changed HTML coding so that users of "Enforce proper nesting" could see the DELeleted text.

      And here's the doozy. This probably isn't documented, but in a list slice, the index expression is evaluated before the list expression. This means you need to know how many elements split will return before split is even called. That means you need to find an alternate code arrangement.

      Thanks Ikegami! That was exactly the sort of info that I was hoping to find out in the absence of a "sure, its easy..just do X" answer). This is as close as one can get to proving a negative. So unless other info is forthcoming, I would say that my friend is mistaken because this claimed new feature just can't be implemented due to the way Perl evaluates the expression. And yes this was a trivia question. For me personally, I think the splice() solution is the winner in terms of clarity and performance.

        To back up what I said,

        >perl -MO=Concise,-exec -e"(LIST_EXPR)[INDEX_EXPR]" 1 <0> enter 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <$> const[PV "INDEX_EXPR"] s/BARE 5 <0> pushmark s 6 <$> const[PV "LIST_EXPR"] s/BARE 7 <2> lslice vK/2 8 <@> leave[1 ref] vKP/REFC -e syntax OK

        Or a more à propos example,

        >perl -MO=Concise,-exec -e"(split)[$x..$y]" 1 <0> enter 2 <;> nextstate(main 1 -e:1) v:{ 3 <0> pushmark s 4 <|> range(other->5)[t5] lK/1 <-------- 5 <#> gvsv[*y] s 6 <1> flop lK goto 7 e <#> gvsv[*x] s f <1> flip[t6] lK 7 <0> pushmark s 8 </> pushre(/" "/) s*/64 9 <#> gvsv[*_] s a <$> const[IV 0] s b <@> split[t2] lK <-------- c <2> lslice vK/2 d <@> leave[1 ref] vKP/REFC -e syntax OK

        Now, I suspect the relative order in which the expressions are evaluated is arbitrary. It could be changed if it would be deemed useful to evaluate them in the other order.

      The last one hasn't been mentioned yet.

      All of them had been mentioned, including the last one. Quite why you feel the need to repeat others...

        The OP found great value in my post, so your poor opinion of it doesn't count for much to me.
Re: Getting range from N..end with list slice
by ikegami (Patriarch) on Nov 28, 2010 at 04:37 UTC

    Maybe your friend is thinking of negative indexes.

    $a[-1] # last element $a[-2] # second-last element

    This is much older than 5.10. It also works on slices

    @a[-2, -1] # second-last and last element

    But you have to keep in mind that ".." has nothing to do with slices. If the LHS is greater than the RHS, it returns an empty list.

    >perl -E"@a = qw( a b c d e f ); say for @a[4..-1]" >perl -E"@a = qw( a b c d e f ); say for @a[-3..-1]" d e f >perl -E"@a = qw( a b c d e f ); say for @a[-1..-3]" >perl -E"@a = qw( a b c d e f ); say for @a[reverse -3..-1]" f e d >perl -E"@a = qw( a b c d e f ); say for reverse @a[-3..-1]" f e d
      I also played around with negative indicies, but the problem is that say with [-3..-1], in general we don't know what this -3 value is supposed to be because it is counted backwards from the right. All we know is that we don't want [0,1,2](counted from the left).

      This >perl -E"@a = qw( a b c d e f ); say for @a[4..-1]" would work except for that pesky LHS > RHS returns empty list rule! My friend may be thinking that there is some new syntax like: @a[4..eol], where "eol" is some stand-in for the largest positive index whatever that happens to be. Or alternately some way to determine, not the value at [-1], but the positive index that it would correspond to.

      I also wondered if there was some sort of new syntax that would express the idea of "give me everything except the stuff at these particular indicies...", but was unable to find anything like that.

        ... that pesky LHS > RHS returns empty list rule!

        Not exactly pretty, and still won't work with a list, but:

        >perl -wMstrict -le "my @ra = qw(a b c d e f g h); my $i = 4; print qq{'$_'} for @ra[$i - @ra .. -1]; " 'e' 'f' 'g' 'h'