简体   繁体   中英

How can I print a Perl two-dimensional array?

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, prints an item out of the array, and 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. How can fix I this?

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

You need two changes:

  1. use a local variable for row
  2. use references for the 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";

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

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

Change

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

Then it prints out ok.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM