tags:

views:

99

answers:

6

I am trying to write a simple Perl script that reads a *.csv, places the rows of the *.csv file in a two dimensional array, and then prints on item out of the array and then prints a row of the array.

#!/usr/bin/perl
use strict;
use warnings;

open(CSV, $ARGV[0]) || die("Cannot open the $ARGV[0] file: $!");
my @row;
my @table;

while(<CSV>) {
        @row = split(/\s*,\s*/, $_);
        push(@table, @row);
}
close CSV || die $!;

foreach my $element ( @{ $table[0] } ) {
    print $element, "\n";
}

print "$table[0][1]\n";

When I run this script I receive the following error and nothing prints:

Can't use string ("1") as an ARRAY ref while "strict refs" in use at ./scripts.pl line 16.

I have looked in a number of other forums and am still not sure how to fix this issue. Can anyone help me fix this script?

+1  A: 
my @arr = ( [a, b, c],
            [d, e, f],
            [g, h, i],
          );

for my $row (@arr) {
    print join(",", @{$row}), "\n";
}

prints

a,b,c
d,e,f
g,h,i

Edit: I'll let others get the credit for catching the wrong push.

Oesor
+2  A: 

You need 2 changes:

  1. use local variable for row
  2. use references for array you put into @table

So your program should look this:

#!/usr/bin/perl
use strict;
use warnings;

open(CSV, $ARGV[0]) || die("Cannot open the $ARGV[0] file: $!");
my @table;

while(<CSV>) {
    my @row = split(/\s*,\s*/, $_);
    push(@table, \@row);
}
close CSV || die $!;

foreach my $element ( @{ $table[0] } ) {
    print $element, "\n";
}

print "$table[0][1]\n";       
MBO
+1  A: 

Maybe this is what you actually want:

#!/usr/bin/perl
use strict;
use warnings;

open(CSV, $ARGV[0]) || die("Cannot open the $ARGV[0] file: $!");
my @table;

while(<CSV>) {
        my @row = split(/\s*,\s*/, $_);
        push(@table, \@row);
}
close CSV || die $!;

foreach my $element ( @{ $table[0] } ) {
    print $element, "\n";
}

print "$table[0][1]\n";
Curd
A: 

Change

#push(@table, @row);
push(@table, \@row);  #push a reference to the array into each cell in @table.

Then it prints out ok.

LanceH
...but does the wrong thing of `@row` isn't created inside the loop.
Michael Carman
... but it could be fixed if you say `push @table, [@row]` to use a reference to a copy of `@row`.
mobrule
+6  A: 

You aren't creating a two-dimensional array (an AoA or "Array of Arrays" in Perl-parlance). This line:

push(@table, @row);

appends the data in @row to @table. You need to push a reference instead, and create a new variable each time through the loop so that you don't push the same reference repeatedly:

my @table;
while(<CSV>) {
    my @row = split(/\s*,\s*/, $_);
    push(@table, \@row);
}

While using split is okay for trivial CSV files, it's woefully inadequate for anything else. Use a module like Text::CSV_XS instead:

use strict;
use warnings;
use Text::CSV_XS;

my $csv  = Text::CSV_XS->new() or die "Can't create CSV parser.\n";
my $file = shift @ARGV         or die "No input file.\n";
open my $fh, '<', $file        or die "Can't read file '$file' [$!]\n";

my @table;
while (my $row = $csv->getline($fh)) {
    push @table, $row;
}
close $fh;

foreach my $row (@table) {
    foreach my $element (@$row) {
        print $element, "\n";
    }
}

print $table[0][1], "\n";
Michael Carman
Array-of-array is the correct nomenclature. There's really no such thing as a list-of-lists in Perl. (The name of the `perllol` manpage notwithstanding. Sigh.)
friedo
+1 for using Text::CSV; it really is silly to not.
Robert P
@friedo: Point taken. I've seen LoL used many times but that's no excuse for spreading misconceptions. Fixed.
Michael Carman
+1  A: 

If you call push with list arguments, you append the first list with the remaining list(s) in stack wise fashion. Read about push at Perldoc. So your call of push(@table, @row); is creating a longer @table list, not a two dimensional array.

You have received several posts that pushing a list reference to @row as \@row will create a list of rows, and indeed that works. I tend to do it a little differently. Of course, with Perl, there is always another way to do it!

Syntactically, you can also push an anonymous array reference into the scalar element of a list to create multi dimension list. The most important thing to know about references in Perl is: 1) they are a scalar and 2) they can refer to anything in Perl - code, array, hash, another reference. Spend some time with the Perl Ref Tutorial and this will become more clear. With your code, just add [ ] around the element you want to be the 2nd dimension in your list, so push(@table, @row); should be push(@table, [ @row ]); In the same sense, you put [ ] around your split so that it becomes push(@table, [ split(/\s*,\s*/, $_) ]); This will simultaneously perform the split and create an anonymous array to the result.

The specific issue that you have, how to create and access a multi dimensional list, is also treated very well in Tom Christensen's perllol tutorial The solutions to your specific issues with your code are directly dealt with here.

Rewriting your code with the the exact code from Tom's example in perllol, it becomes this:

#!/usr/bin/perl
use strict;
use warnings;

my (@row, @table, $n, $rowref);

while(<DATA>) {
        chomp;
        # regex to separate CSV (use of a cpan module for CSV STRONGLY advised...
        @row = /(?:^|,)("(?:[^"]+|"")*"|[^,]*)/g;
        for (@row) {
            if (s/^"//) { s/"$//; s/""/"/g; }
        }
        push(@table, [ @row ]); #Note the [ ] around the list
}

# Now the table is created, print it:
my $rowcnt=0;
foreach $rowref (@table) {
    print "row $rowcnt:\n";
    $rowcnt++;
    print "  [ @$rowref ], \n";
}   

# You can access the table in the classic [i][j] form:
for my $i ( 0 .. $#table ) {
    $rowref = $table[$i];
    $n = @$rowref - 1;
    for my $j ( 0 .. $n ) {
        print "element $i, $j of table is $table[$i][$j]\n";
    }
}

# You can format it:
for my $i ( 0 .. $#table ) {
    print "$table[$i][0] $table[$i][1]\n";
    print "$table[$i][2]\n";
    print "$table[$i][3], $table[$i][4] $table[$i][5]\n\n";
}


__DATA__
Mac,Doe,120 jefferson st.,Riverside, NJ, 08075
Jack,McGinnis,220 hobo Av.,Phila, PA,09119
"John ""Da Man""",Repici,120 Jefferson St.,Riverside, NJ,08075
Stephen,Tyler,"7452 Terrace ""At the Plaza"" road",SomeTown,SD, 91234
,Blankman,,SomeTown, SD, 00298
"Joan ""Joan, the bone""",Jett,"9th, at Terrace plc",Desert City,CO,00123
drewk