Reputation: 595
Consider the below CSV
Column1,Column2,Column3
John,Doe,Developer
Joey,Doe,Manager
Joe,Doe,Developer
I need to be able to read the CSV in perl to be able to render the following:
---- My list ----
-> Person 1
-> Name: John Doe
-> Role: Developer
-> Person 2
-> Name: Joey Doe
-> Role: Manager
-> Person 3
-> Name: Joe Doe
-> Role: Developer
--- Groups ---
-> Developer
-> Members: John Doe, Joe Doe
-> Manager
-> Members: Joey Doe
--- Roles ---
-> Developer, Manager
The end environment has limitations - Perl 5.10 and can't be upgraded - Cannot install additional modules - Had to use "print" instead of "say"
Upvotes: 1
Views: 197
Reputation: 6798
Following code reads data, splits on fields and build hash with keys person, position, role.
Once hash formed the data output to console in accordance with hash keys.
use strict;
use warnings;
use feature 'say';
my %hash;
my %seen;
my @header = split ',', <DATA>;
chomp @header;
while(<DATA>) {
next if /^$/;
chomp;
my %data;
@data{@header} = split ',';
push @{$hash{person}}, \%data;
push @{$hash{Position}{$data{Position}}}, "$data{Last} $data{First}";
if( ! $seen{$data{Position}} ) {
$seen{$data{Position}} = 1;
push @{$hash{Role}}, $data{Position};
}
}
say "--- My list ----\n";
my $count = 0;
for my $person ( @{$hash{person}} ) {
$count++;
say "-> Person: $count";
say "-> Name: $person->{First} $person->{Last}";
say "-> Role: $person->{Position}\n";
}
say "---- Groups ----\n";
while( my($p,$m) = each %{$hash{Position}} ) {
say "-> $p: ";
say '-> Members: ' . join(', ',@{$m}) . "\n";
}
say "---- Roles ----";
say '-> ' . join(', ',@{$hash{Role}});
__DATA__
First,Last,Position
John,Doe,Developer
Mary,Fox,Manager
Anna,Gulaby,Developer
Output
--- My list ----
-> Person: 1
-> Name: John Doe
-> Role: Developer
-> Person: 2
-> Name: Mary Fox
-> Role: Manager
-> Person: 3
-> Name: Anna Gulaby
-> Role: Developer
---- Groups ----
-> Manager:
-> Members: Fox Mary
-> Developer:
-> Members: Doe John, Gulaby Anna
---- Roles ----
-> Developer, Manager
OP brought to my attention that he had a problem with the code.
It was found cause of the problem was input data eol in DOS form \r\n
on Linux system. In Linux for some versions of perl [v5.22.1] - chomp
removes only \n
and leaves \r
which stays part of key for Position\r field. Thanks goes to Shawn for pointing it out.
It was found that not all versions of perl experience this issue. New post was initiated to demonstrate the problem.
Following fix works for Linux/Windows (not tested on other platforms).
use strict;
use warnings;
use feature 'say';
my $debug = 0;
say "
Perl: $^V
OS: $^O
-------------------
" if $debug; # for debug purpose to show perl version and OS
my %hash;
my %seen;
my @header = split ',', <DATA>;
$header[2] = snip_eol($header[2]); # problem fix
while(<DATA>) {
next if /^\s*$/;
my $line = snip_eol($_); # problem fix
my %data;
@data{@header} = split ',',$line;
push @{$hash{person}}, \%data;
push @{$hash{Position}{$data{Position}}}, "$data{First} $data{Last}";
if( ! $seen{$data{Position}} ) {
$seen{$data{Position}} = 1;
push @{$hash{Role}}, $data{Position};
}
}
#say Dumper($hash{Position});
my $count = 0;
for my $person ( @{$hash{person}} ) {
$count++;
say "-> Name: $person->{First} $person->{Last}";
say "-> Role: $person->{Position}\n";
}
say "---- Groups ----\n";
while( my($p,$m) = each %{$hash{Position}} ) {
say "-> $p";
my $members = join(',',@{$m});
say "-> Members: $members\n";
}
say "---- Roles ----";
say '-> ' . join(', ',@{$hash{Role}});
sub snip_eol { # problem fix
my $data = shift;
#map{ say "$_ => " . ord } split '', $data if $debug;
$data =~ s/\r// if $^O eq 'linux';
chomp $data;
#map{ say "$_ => " . ord } split '', $data if $debug;
return $data;
}
__DATA__
First,Last,Position
John,Doe,Developer
Mary,Fox,Manager
Anna,Gulaby,Developer
Upvotes: 2
Reputation: 69264
I'm not 100% sure what you want, but this seems to produce something like your data structure.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my @headers = split (/,/, <DATA>);
chomp(@headers);
my %data;
while (<DATA>) {
chomp;
my %config;
@config{@headers} = split /,/;
push @{$data{$config{Column3}}}, [ $config{Column1}, $config{Column2} ];
}
say Dumper \%data;
__DATA__
Column1,Column2,Column3
John,Doe,Developer
Joey,Doe,Manager
Joe,Doe,Developer
The output is:
$VAR1 = {
'Developer' => [
[
'John',
'Doe'
],
[
'Joe',
'Doe'
]
],
'Manager' => [
[
'Joey',
'Doe'
]
]
};
And I only needed to read through the file once.
Update: And here's a version with all of your required output.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my @headers = split (/,/, <DATA>);
chomp(@headers);
my %data;
say "---- My list ----\n";
while (<DATA>) {
chomp;
my %config;
@config{@headers} = split /,/;
push @{$data{$config{Column3}}}, [ $config{Column1}, $config{Column2} ];
say "-> Person ", $. - 1;
say "-> Name: $config{Column1}, $config{Column2}";
say "-> Role: $config{Column3}\n";
}
say "--- Groups ---\n";
for (keys %data) {
say "-> $_";
say "-> Members: ", join ', ', map { "@$_" } @{$data{$_}};
say '';
}
say "--- Roles ---\n";
say join ', ', keys %data;
say '';
__DATA__
Column1,Column2,Column3
John,Doe,Developer
Joey,Doe,Manager
Joe,Doe,Developer
Upvotes: 2
Reputation: 52409
I don't see why you want to read the file twice. Read it once, doing stuff for each line, and storing the groups at the same time, and then loop over them? Example:
#!/usr/bin/env perl
use warnings;
use strict;
use feature qw/say postderef/;
no warnings qw/experimental::postderef/;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new({binary => 1, auto_diag => 1});
my %roles;
# Read the header line
$csv->column_names($csv->getline(\*DATA));
say "---- My list ----";
my $n = 0;
while (my $row = $csv->getline(\*DATA)) {
# Do stuff for the row:
$n += 1;
say "-> Person $n";
say "-> name $row->[0] $row->[1]";
say "-> Role: $row->[2]\n";
# Group for later
push @{$roles{$row->[2]}}, [ $row->@[0,1] ];
}
say "--- Groups ---\n";
for my $role (sort keys %roles) {
say "-> $role";
say "-> Members: ", join(", ", map { "@$_" } $roles{$role}->@*), "\n";
}
say "--- Roles ---";
say "-> ", join(", ", sort keys %roles);
__DATA__
Column1,Column2,Column3
John,Doe,Developer
Joey,Doe,Manager
Joe,Doe,Developer
(Note use of the Text::CSV_XS module to parse CSV data instead of using split
, to make it more robust, and postderef array reference access if you haven't seen that syntax before. Personally, I think it's cleaner than the traditional de-reference syntax in some cases).
Upvotes: 2