tags:

views:

177

answers:

8

If I have an array myarray in Python, I can use the slice notation

myarray[0::2]

to select only the even-indexed elements. For example:

>>> ar = [ "zero", "one", "two", "three", "four", "five", "six" ]
>>> ar [ 0 : : 2 ]
['zero', 'two', 'four', 'six']

Is there a similar facility in Perl?

Thanks.

+11  A: 

A Perl array slice is the @ in front of the array name, then the list of indices you want:

 @array[@indices];

There's not a built-in syntax to select multiples, but it's not so hard. Use grep() to produce a list of indices that you want:

 my @array = qw( zero one two three four five six );
 my @evens = @array[ grep { ! ($_ % 2) } 0 .. $#array ];

If you are using PDL, there are lots of nice slicing options.

brian d foy
! higher precedence than %
ysth
Yep, that mistake snuck in there. However, you're repped enough to fix those sorts of things :)
brian d foy
I'd rather let you choose whether to use () or ==0.
ysth
Well, I can always edit later if I don't like the change. I try to fix those sorts of thinkos for other people. I'm not terribly picky about the code when it's the difference between right and wrong.
brian d foy
+6  A: 

I'll do this in a two-step process: first generate the desired indices, and then use a slice operation to extract them:

@indices = map { $_ * 2 } (0 .. int($#array / 2));
my @extracted = @array[@indices];

Step-by-step, thats:

  • generate a list of integers from 0 to the last element of the array divided by two
  • multiply each integer by two: now we have even numbers from zero to the index of the last element
  • extract those elements from the original array
Ether
+12  A: 

There's array slices:

my @slice = @array[1,42,23,0];

There's a way to to generate lists between $x and $y:

my @list = $x .. $y

There's a way to build new lists from lists:

my @new = map { $_ * 2 } @list;

And there's a way to get the length of an array:

my $len = $#array;

Put together:

my @even_indexed_elements = @array[map { $_ * 2 } 0 .. int($#array / 2)];

Granted, not quite as nice as the python equivalent, but it does the same job, and you can of course put that in a subroutine if you're using it a lot and want to save yourself from some writing.

Also there's quite possibly something that'd allow writing this in a more natural way in List::AllUtils.

rafl
+3  A: 

Perl 6 will improve things dramatically, but (so far?) Perl 5 has pretty limited slicing capability: you have to explicitly specify the indexes you want, and it can't be open-ended.

So you'd have to do:

@ar = ( "zero", "one", "two", "three", "four", "five", "six" );
print @ar[ grep $_ % 2 == 0, 0..$#ar ]
ysth
+8  A: 

I've written the module List::Gen on CPAN that provides an alternative way to do this:

use List::Gen qw/by/;

my @array = qw/zero one two three four five six/;

my @slice = map {$$_[0]} by 2 => @array;

by partitions @array into groups of two elements and returns an array of array references. map then gets this list, so each $_ in the map will be an array reference. $$_[0] (which could also be written $_->[0]) then grabs the first element of each group that by created.

Or, using the mapn function which by uses internally:

use List::Gen qw/mapn/;

my @slice = mapn {$_[0]} 2 => @array;   

Or, if your source list is huge and you may only need certain elements, you can use List::Gen's lazy lists:

use List::Gen qw/by gen/;

my $slicer = gen {$$_[0]} by 2 => @array;

$slicer is now a lazy list (an array ref) that will generate it's slices on demand without processing anything that you didn't ask for. $slicer also has a bunch of accessor methods if you don't want to use it as an array ref.

Eric Strom
+3  A: 

If you don't care about the order, and if the odd-numbered elements of the list are unique, you can concisely convert the array to a hash and take the values:

@even_elements = values %{{@array}};
@odd_elements = keys %{{@array}};

(No, this is not a serious answer)

mobrule
TMTOWTDI! *filler chars*
Michael Carman
+1  A: 

One way to make this prettier is to wrap it in something like autobox.

For example using autobox::Core:

use autobox::Core;
my @ar = qw/zero one two three four five six/;

# you could do this
@ar->slice_while( sub{ not $_ % 2 } );

# and this
@ar->slice_by(2);

# or even this
@ar->evens;

This is how you can define these autobox methods:

sub autobox::Core::ARRAY::slice_while {
    my ($self, $code) = @_;
    my @array;

    for (my $i = 0; $i <= $#{ $self }; $i++) {
        local $_ = $i;
        push @array, $self->[ $i ] if $code->();
    }

    return wantarray ? @array : \@array;
}

sub autobox::Core::ARRAY::slice_by {
    my ($self, $by) = @_;
    my @array = @$self[ map { $_ * $by } 0 .. int( $#{$self} / $by )];
    return wantarray ? @array : \@array;
}

sub autobox::Core::ARRAY::evens {
    my $self  = shift;
    my @array = $self->slice_by(2);
    return wantarray ? @array : \@array;
}

/I3az/

draegtun
I started working on an unmesh that did something similar, but then I went off to do something else. :)
brian d foy
I *oohed and aahed* for a couple of hours before I said *sod it* and decided that an `autobox` solution would be nice to see :)
draegtun
A: 

Another way would be by using grep:

my @array = qw( zero one two three four five six );

print map { "$_ " } @array[grep { !($_ & 1) } 0 .. $#array];  #even
Output:zero two four six

print map { "$_ " } @array[grep { ($_ & 1) } 0 .. $#array];  #odd
Output:one three five 
Nikhil Jain