8

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?

6
  • 7
    Don't use $a and $b for variable names. Their are specially packaged scope variables for use with sort. Commented Jul 8, 2010 at 22:22
  • That would be cool if you could do that, though. Commented Jul 8, 2010 at 22:28
  • if you're outside of sort, it's fine. but if it's a one-liner you're going to reuse, possibly in sort later, then be careful, true. :-) Commented Jul 8, 2010 at 22:39
  • 4
    Please don't parse CSV files "by hand" -- use Text::CSV, which takes care of all the annoying little edge cases that will eventually bite you in the behind. Commented Jul 8, 2010 at 22:41
  • 7
    @eruciform => the danger is if you lexicalize $a or $b with my, and then later call sort in the same scope, sort will blow up, complaining that it can't localize a lexical variable Commented Jul 8, 2010 at 23:02

6 Answers 6

14

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;
Sign up to request clarification or add additional context in comments.

4 Comments

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?
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.
All I can say is very nice!
Just reiterating Sinan's comment. This module looks really interesting. Great work.
13

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;
        }
    }
}

5 Comments

heh funny, didn't know about that one. probably a one-line closure around splice. :-)
@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.
@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: nice, i didn't know about this utility library. of course xs is faster. :-)
@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. :)
5
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

Comments

4
@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

2 Comments

this destroys the @z array and is probably better written as a while loop
@eric: true. this is a quickie solution.
1

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.

Comments

1

As of Perl v5.36 you can do exactly that:

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

It's implemented as for_list experimental feature, so you can ignore the warning the usual way with use experimental qw(for_list);

For versions before v5.36 we'll rely on while/splice as mentioned above.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.