tags:

views:

130

answers:

5

I have a CSV file that I use split to parse into an array of N items, where N is a multiple of 3.

Is there a way i can do this

foreach my ( $a, $b, $c ) ( @d ) {}

similar to Python?

+4  A: 
@z=(1,2,3,4,5,6,7,8,9,0);

for( @tuple=splice(@z,0,3); @tuple; @tuple=splice(@z,0,3) ) 
{ 
  print "$tuple[0] $tuple[1] $tuple[2]\n"; 
}

produces:

1 2 3
4 5 6
7 8 9
0
eruciform
this destroys the `@z` array and is probably better written as a while loop
Eric Strom
@eric: true. this is a quickie solution.
eruciform
+1  A: 

Not easily. You'd be better off making @d an array of three-element tuples, by pushing the elements onto the array as an array reference:

foreach my $line (<>)
    push @d, [ split /,/, $line ];

(Except that you really ought to use one of the CSV modules from CPAN.

JSBangs
thx, it's for a quick internal hacks, didnt think it would be so hard
Timmy
+12  A: 

You can use List::MoreUtils::natatime. From the docs:

my @x = ('a' .. 'g');
my $it = natatime 3, @x;
while (my @vals = $it->()) {
    print "@vals\n";
}

natatime is implemented in XS so you should prefer it for efficiency. Just for illustration purposes, here is how one might implement a three element iterator generator in Perl:

#!/usr/bin/perl

use strict; use warnings;

my @v = ('a' .. 'z' );

my $it = make_3it(\@v);

while ( my @tuple = $it->() ) {
    print "@tuple\n";
}

sub make_3it {
    my ($arr) = @_;
    {
        my $lower = 0;
        return sub {
            return unless $lower < @$arr;
            my $upper = $lower + 2;
            @$arr > $upper or $upper = $#$arr;
            my @ret = @$arr[$lower .. $upper];
            $lower = $upper + 1;
            return @ret;
        }
    }
}
Sinan Ünür
*n* at a time - I like it :-)
Mike
heh funny, didn't know about that one. probably a one-line closure around splice. :-)
eruciform
@eruciform: in logic, yes, but the functions in List::Util and List::MoreUtils are written in XS for maximum speed. It really does pay off to use the exact function you need rather than using the builtin functions, when parsing a huge amount of data.
Ether
@eruciform: Actually, no. The functions in `List::MoreUtils` are implemented in XS (that is, C) to provide the maximum possible efficiency. The use of `splice` introduces a lot of memory overhead and moving pointers around etc which one might notice if *N* is large enough.
Sinan Ünür
@sinan: nice, i didn't know about this utility library. of course xs is faster. :-)
eruciform
@eruciform: the XS module isn't. However, the pure perl version of List::MoreUtils is exactly as you describe: a closure around a copy of the list, with a splice. :)
Robert P
+13  A: 

I addressed this issue in my module List::Gen on CPAN.

use List::Gen qw/by/;

for my $items (by 3 => @list) {

    # do something with @$items which will contain 3 element slices of @list

    # unlike natatime or other common solutions, the elements in @$items are
    # aliased to @list, just like in a normal foreach loop

}

You could also import the mapn function, which is used by List::Gen to implement by:

use List::Gen qw/mapn/;

mapn {

   # do something with the slices in @_

} 3 => @list;
Eric Strom
are they actually aliased in a "for my"? or just in a "for" loop? the "my" is supposed to make a copy. does "by" get around this?
eruciform
the `my` variable in a Perl foreach loop is never a copy, it is always an alias. A lexically scoped alias, but an alias none the less.
Eric Strom
@Eric All I can say is *very nice!*
Sinan Ünür
@Eric Just reiterating Sinan's comment. This module looks really interesting. Great work.
FM
+3  A: 
my @list = (qw(one two three four five six seven eight nine));

while (my ($m, $n, $o) = splice (@list,0,3)) {
  print "$m $n $o\n";
}

this outputs:

one two three
four five six
seven eight nine
dalton