I got a hash:
%hash = (
Honda.Blue => '10',
Honda.Red => '10',
Honda.Yellow => '60',
Ford.Blue => '20',
Ford.Red => '25',
Ford.Yellow => '26',
Toyota.Blue => '17',
Toyota.Red => '16',
Toyota.Yellow => '18',
);
Need to convert this hash into a csv file with the following headers (make,blue_volume,red_volume,yellow_volume) and fill it with data
#Make,blue_volume,red_volume,yellow_volume
#Honda,10,10,60
#Ford,20,25,26
#Toyota,17,16,18
loop over %hash
#array = split('.',$key);
$make=$array[0];
$line = "$make,$hash{'$make.Blue'},$hash{'$make.Red'},$hash{'$make.Yellow'}";
push(#lines,$line);
foreach (#lines)
{
open (LOG, '>>summary.csv');
print LOG "$_";
close (LOG);
}
Need help figuring out this code.
First step:
use strict; says:
Bareword "Honda" not allowed while "strict subs" in use at xx.pl line 4.
That is not an approved way of creating the hash. I suggest:
use strict;
use warnings;
my %hash = (
Honda => { Blue => '10', Red => '10', Yellow => '60' },
Ford => { Blue => '20', Red => '25', Yellow => '26' },
Toyota => { Blue => '17', Red => '16', Yellow => '18' },
);
Then, you should probably use Text::CSV. However, it is not all that hard to do output with simple manipulation. We can exploit the fact that you've asked for blue, red, yellow which happen to be in alphabetic order:
print "make,blue_volume, red_volume,yellow_volume\n";
foreach my $make (sort keys %hash)
{
print "$make";
foreach my $colour (sort keys %{$hash{$make}})
{
print ",$hash{$make}{$colour}";
}
print "\n";
}
For the sample hash, the output is:
make,blue_volume, red_volume,yellow_volume
Ford,20,25,26
Honda,10,10,60
Toyota,17,16,18
If there was any risk of needing to use quotes or anything else, I'd use Text::CSV.
A solution with List::MoreUtils.
#!/usr/bin/env perl
use warnings;
use 5.012;
use List::MoreUtils qw/first_index/;
use Text::CSV;
my $file_out = 'my_new_file.csv';
my %hash = (
'Honda.Blue' => '10',
'Honda.Red' => '10',
'Honda.Yellow' => '60',
'Ford.Blue' => '20',
'Ford.Red' => '25',
'Ford.Yellow' => '26',
'Toyota.Blue' => '17',
'Toyota.Red' => '16',
'Toyota.Yellow' => '18',
);
my #brands = qw( Honda Ford Toyota );
my #colors = qw( Blue Red Yellow );
my #array;
for my $key ( keys %hash ) {
my( $brand, $color ) = split /\./, $key, 2;
my $idx_1 = first_index { $_ eq $brand } #brands;
my $idx_2 = first_index { $_ eq $color } #colors;
$array[$idx_1][0] = $brand;
$array[$idx_1][$idx_2+1] = $hash{$key};
}
my $csv = Text::CSV->new ( { binary => 1, eol => $/, auto_diag => 2 } )
or die Text::CSV->error_diag();
my $col_names = [ qw( Make blue_volume red_volume yellow_volume ) ];
open my $fh, '>:encoding(UTF-8)', $file_out or die $!;
$csv->print ( $fh, $col_names );
$csv->print ( $fh, $_ ) for #array;
close $fh;
If you iterate over the hash and make a line for each key, you will have each make repeated three times; instead, create another hash with all the makes by looping over %hash, extracting the make, and setting $makes{$make} = 1. Then loop over that to produce your lines.
When you extract the make from the %hash key, use /\./ as the split pattern; split always uses a pattern, not a simple string (with one odd exception), and you don't want to split on every character, which is what split '.' would do (thanks, codaddict, for pointing this part out).
'$make.Blue' uses single quotes, so it won't interpolate the variable. Use "$make.Blue" instead.
Move the open and close to before and after the #lines loop, respectively. There's no reason to open the file for each line.
Don't forget a "\n" at the end of each line (unless you are using the -l flag or say instead of print).
Check out Text::CSV::Slurp. It will allow you to turn a hash into CSV and vice versa as well.
For those interested I was able to figure it out, thanks for everyone's help!
my %hash;
$hash{'aaa.biz'} = 'domainRegistered';
$hash{'aaa.com'} = 'domainRegistered';
$hash{'aaa.info'} = 'domainRegistered';
$hash{'aaa.net'} = 'domainRegistered';
$hash{'aaa.org'} = 'domainRegistered';
$hash{'bbb.biz'} = 'domainRegistered';
$hash{'bbb.com'} = 'domainRegistered';
$hash{'bbb.info'} = 'domainRegistered';
$hash{'bbb.org'} = 'domainRegistered';
$hash{'bbb.us'} = 'domainRegistered';
foreach $key (sort keys %hash)
{
#array=split("\\.",$key);
push (#names, $array[0]);
}
#Extract unique values and sort
my %seen = ();
my #result = grep { !$seen{$_}++ } #names;
#names = sort { $a <=> $b } #result;
foreach $element (#names)
{
foreach $key (sort keys %hash)
{
#array=split("\\.",$key);
if (#array [0] eq $element){push (#values, $hash{$key});}
}
$values = join(",",#values);
$line = "$element,$values";
undef #values;
push(#lines,$line);
}
print join("\n",#lines);
open (summary, '>>summary.csv');
print summary join("\n",#lines);
close (summary);
Related
I've been learning Perl as of late, and ran into an interesting problem. How would I go about using two arrays in map like so?
use warnings;
use strict;
use Data::Dumper;
my $names->{name} = ['Bill', 'Smith'];
my $cars->{model} = ['Honda', 'Toyota'];
my $obj = {
'Students' => [
map {
'Name' => $_,
'Model' => $_
}, #{$names->{name}}, #{$cars->{model}}
]
};
print Dumper $obj;
This will print, it's producing two too many objects than I would like.
$VAR1 = {
'Students' => [
{
'Model' => 'Bill',
'Name' => 'Bill'
},
{
'Model' => 'Smith',
'Name' => 'Smith'
},
{
'Model' => 'Honda',
'Name' => 'Honda'
},
{
'Model' => 'Toyota',
'Name' => 'Toyota'
}
]
};
What I'd like to do is have map work in a way that it'll produce these results
$VAR1 = {
'Students' => [
{
'Model' => 'Honda',
'Name' => 'Bill'
},
{
'Model' => 'Toyota',
'Name' => 'Smith'
}
]
};
use warnings;
use strict;
use Data::Dumper;
my $names->{name} = [ qw(Bill Smith) ];
my $cars->{model} = [ qw(Honda Toyota) ];
my $obj = {
Students => [
map {
{ Name => $names->{name}[$_], Model => $cars->{model}[$_] }
}
0 .. $#{$names->{name}}
]
};
print Dumper $obj;
The body of the map uses both arrays so you only need to supply indices to it.
For this both arrayrefs must be of same length and items to be paired at same indices.
The syntax $#$arrayref is for the last index in the $arrayref, and since here we have to also dereference first there's an extra pair of {}.
What you have dereferences both arrayrefs, to build a single flat list for the map, producing
my $obj = {
'Students' => [
map {
'Name' => $_,
'Model' => $_
}, qw(Bill Smith Honda Toyota);
]
};
First, this makes no sense:
my $names->{name} = ['Bill', 'Smith'];
my $cars->{model} = ['Honda', 'Toyota'];
I'll use
my #names = ( 'Bill', 'Smith' );
my #models = ( 'Honda', 'Toyota' );
We could iterate over the indexes.
map { +{ Name => $names[$_], Model => $cars[$_] } }
0..$#names
Alternatively, we could enlist zip.
use List::Util qw( zip );
map { +{ Name => $_->[0], Model => $_->[1] } }
zip \#names, \#models
This is probably better written as a while loop:
my #names = qw( Bill Smith );
my #models = qw( Honda Toyota );
my #students;
push #students, { 'name' => shift #names, 'model' => shift #models } while #names;
I have 2 variables that are returned on form submission. I need to merge the values into 1 Variable that I can use to create SQL later on.
How can I get results that is union of all the values:
i.e. location_setting = '409','405' and Status = '501', '137', '124'
Here is my current code, but spits only values from h2
use Data::Dumper;
my $h1 = { 'location_setting' => [ '409' ], 'status' => [ '501' ] };
my $h2 = { 'status' => [ '137', '124' ], 'location_setting' => ['405'], 'classification' => ['0']};
my $x = {%$h1, %$h2};
print Dumper $x;
use List::Util qw( uniq );
my %h =
map {
$_ => [
uniq
$h1->{$_} ? #{ $h1->{$_} } : (),
$h2->{$_} ? #{ $h2->{$_} } : (),
]
}
uniq
keys(%$h1), keys(%$h2);
If you have lots of hashes or a variable number of hashes,
use List::Util qw( uniq );
my #hashes = ( $h1, $h2, ... );
my %h =
map {
my $key = $_;
$key => [ uniq map #$_, grep $_, map $_->{$key}, #hashes ]
}
uniq map keys(%$_), #hashes;
use Data::Dumper;
use List::Util qw( uniq );
my $h1 = { 'location_setting' => [ '409' ], 'status' => [ '501' ] };
my $h2 = { 'status' => [ '137', '124' ], 'location_setting' => [], 'classification' => ['0']};
my %x;
foreach my $h1key (keys %{$h1}) {
push #{$x{$h1key}}, #{${$h1}{$h1key}};
}
foreach my $h2key (keys %{$h2}) {
push #{$x{$h2key}}, #{${$h2}{$h2key}};
}
#$_ = uniq #$_
for values(%x);
my $x = \%x;
print Dumper $x;
Hash of Hash of values were printing correctly to csv but hash of hash of array values are messing up my csv file. Please find my code below which is not working.
< use strict;
use warnings;
use Data::Dumper;
my %student_hash;
my $student_report = "StudentReport.csv";
%student_hash = (
'RollNummber1' => {
'studentname' => 'Boris',
'address' => ['Vietnam',
'local'
],
'DOB' => '5june2000'
},
'RollNummber2' => {
'studentname' => 'John',
'address' => [
'4th/floor',
'Culverdown/Street',
'WestHam',
'UK.',
],
'DOB' => '2feb2000'
},
'RollNummber3' => {
'studentname' => 'Karen',
'DOB' => '5march2000'
}
);
print "StudentHash:".Dumper(\%student_hash);
open(my $fh, '>', $student_report) or die("Couldn't open ile");
print $fh "DOB \t,ADDRESS \t,NAME \t\n";
foreach my $key(keys %student_hash){
foreach my $secondkeys (keys %{$student_hash{$key}}){
if($secondkeys =~ /DOB || studentname/) {
print $fh "$student_hash{$key}{$secondkeys} \t,"; }
if($secondkeys =~ /address/) {
print $fh Dumper $student_hash{$key}{$secondkeys} };
}
print $fh "\n";
}
close($fh);>
If I comment out printing the array part then the code works fine.Can someone please suggest how to print the array elements into csv either in one cell or in multiple cells.Thanks
Use Text::CSV - you can either use print if you've got an array reference to print. (It handles quoting for you).
Or you can use column_names to set the column ordering, and print_hr to output a hash ref in that order.
It looks like you're not worried about RollNumber so you can cheat and do:
foreach my $student ( values %student_hash ) {
}
To iterate. I'm unclear what you're trying to do with address. But a simple join would probably do the trick there. (either on linefeed or whitespace, depending on the desired output).
#!/usr/bin/env perl
use strict;
use warnings;
use Text::CSV;
my %student_hash = (
'RollNummber1' => {
'studentname' => 'Boris',
'address' => [ 'Vietnam', 'local' ],
'DOB' => '5june2000'
},
'RollNummber2' => {
'studentname' => 'John',
'address' => [ '4th/floor', 'Culverdown/Street', 'WestHam', 'UK.', ],
'DOB' => '2feb2000'
},
'RollNummber3' => {
'studentname' => 'Karen',
'DOB' => '5march2000'
}
);
my $csv = Text::CSV->new( { sep_char => ',', eol => "\n" } );
$csv->column_names( 'studentname', 'DOB', 'address' );
foreach my $student ( values %student_hash ) {
if ( defined $student -> {address} ) {
$student -> {address} = join (" ", #{$student->{address}});
}
$csv->print_hr( \*STDOUT, $student );
}
I have a hash reference that contains nested key/value pairs, hash references and/or array references.
I'd like to emulate the result of Data::Dumper into a string, but with:
Remove 'quotes' from keys.
Remove white spaces from structures (but not values)
Bonus: sort keys alphabetically.
Bonus Bonus: print key/value pairs first, followed by hash references, followed by array references.
For example:
#!/usr/bin/perl -w
use strict;
use warnings;
use Data::Dumper;
my $hash_ref = {
'private' => {
'locked' => 'FALSE',
'allowedAuth' => 'Digest'
},
'profile' => 'Default',
'id' => '123456',
'privacy' => 'FALSE',
'public' => [
{
'allowed' => 'FALSE',
'configured' => {
'profileId' => 'hello world'
},
'isDefault' => 'TRUE',
'maxSessions' => '3'
},
{
'isDefault' => 'FALSE',
'privateId' => 'foo#bar.com',
'maxSessions' => '3',
'allowed' => 'FALSE',
'implicit' => '1',
}
],
'indicator' => 'FALSE'
};
print STDERR Dumper ($hash_ref);
Ideally, I'd like the output to be:
my $str = "id=>'123456',indicator=>'FALSE',profile=>'Default',privacy=>'FALSE',private=>{allowedAuth=>'Digest',locked=>'FALSE'},public=>[{allowed=>'FALSE',configured=>{profileId=>'hello world'},isDefault=>'TRUE',maxSessions=>'3'},{allowed=>'FALSE',implicit=>'1',isDefault=>'FALSE',maxSessions=>'3',privateId=>'foo#bar.com'}]";
I've attempted a recursive function; however, I'm not sure how to get rid of the commas at the end (especially of hash references - for array ref I can use an index and check if it's the last one). Also, sorting keys seems too difficult.
sub recHash
{
my ($hash_ref) = #_;
my $response = "";
for my $k (keys %$hash_ref) {
my $v = $hash_ref->{$k};
if (ref($v) eq "HASH") {
$response .= "$k=>{" . recHash($v) . "}"; # recurse through the hash references.
}
elsif (ref($v) eq "ARRAY") {
$response .= "$k=>[";
# recurse through the array references.
foreach my $item (#$v) {
$response .= "{".recHash($item)."},";
}
$response .= "],";
return $response;
}
else {
$response .= "$k=>'$v',";
}
}
return $response;
}
print recHash($hash_ref);
My output is (which I think is flawed when I keep running it):
private=>{allowedAuth=>'Digest',locked=>'FALSE',}profile=>'Default',id=>'123456',indicator=>'FALSE',privacy=>'FALSE',public=>[{configured=>{profileId=>'hello world',}maxSessions=>'3',allowed=>'FALSE',isDefault=>'TRUE',},{allowed=>'FALSE',maxSessions=>'3',implicit=>'1',privateId=>'foo#bar.com',isDefault=>'FALSE',},],
Out-of-the-box $Data::Dumper::Indent and $Data::Dumper::Sortkeys values will get you most of the way there.
use Data::Dumper;
my $hash_ref = { ... };
$Data::Dumper::Indent = 0;
$Data::Dumper::Sortkeys = sub {
my ($hash) = #_;
my %refval = ('' => -3, 'HASH' => -2, 'ARRAY' => -1);
return [ sort {
# prefer ref(val) "" to "HASH" to "ARRAY" to anything else
$refval{ref $hash->{$a}} <=> $refval{ref $hash->{$b}}
# and then sort lexicographically
|| $a cmp $b
} keys %$hash ];
};
my $rec_hash = Dumper($hash_ref);
$rec_hash =~ s/'(\w+)' => /$1=>/g;
$rec_hash =~ s/^\$VAR1 = //;
print $rec_hash;
Result:
{id=>'123456',indicator=>'FALSE',privacy=>'FALSE',profile=>'Default',
private=>{allowedAuth=>'Digest',locked=>'FALSE'},public=>
[{allowed=>'FALSE',isDefault=>'TRUE',maxSessions=>'3',configured=>
{profileId=>'hello world'}},allowed=>'FALSE',implicit=>'1',
isDefault=>'FALSE',maxSessions=>'3',privateId=>'foo#bar.com'}]};
You just need to sort the keys as follows:
for my $k (sort keys %$hash_ref) {
You are getting different outputs at every run as the hash keys are accessed in random order.
I have got this part from a Perl plugin. I don't understand what it does. Is it an array of associative arrays? If so, then shouldn't it be started with #? Can anyone shed some light on this issue?
my $arguments =
[ { 'name' => "process_exp",
'desc' => "{BasePlugin.process_exp}",
'type' => "regexp",
'deft' => &get_default_process_exp(),
'reqd' => "no" },
{ 'name' => "assoc_images",
'desc' => "{MP4Plugin.assoc_images}",
'type' => "flag",
'deft' => "",
'reqd' => "no" },
{ 'name' => "applet_metadata",
'desc' => "{MP4Plugin.applet_metadata}",
'type' => "flag",
'deft' => "" },
{ 'name' => "metadata_fields",
'desc' => "{MP4Plugin.metadata_fields}",
'type' => "string",
'deft' => "Title,Artist,Genre" },
{ 'name' => "file_rename_method",
'desc' => "{BasePlugin.file_rename_method}",
'type' => "enum",
'deft' => &get_default_file_rename_method(), # by default rename imported files and assoc files using this encoding
'list' => $BasePlugin::file_rename_method_list,
'reqd' => "no"
} ];
As Bwmat said it's a reference to an Array of Hash references. Read
$ man perlref
or
$ man perlreftut # this is a bit more straightforward
for if you want to know more about references.
By the way in fiew words in Perl you can do:
#array = ( 1, 2 ); # declare an array
$array_reference = \#array; # take the reference to that array
$array_reference->[0] = 2; # overwrite 1st position of #array
$numbers = [ 3, 4 ]; # this is another valid array ref declaration. Note [ ] instead of ( )
the same thing happens with hashes.
By the way in fiew words in Perl you can do:
%hash = ( foo => 1, bar => 2 );
$hash_reference = \%hash;
$hash_reference->{foo} = 2;
$langs = { perl => 'cool', php => 'ugly' }; # this is another valid hash ref declaration. Note { } instead of ( )
And... yes, you can dereference these references.
%{ $hash_reference }
will be treated as it was a hash, so if you want to print the keys of $langs above, you can do:
print $_, "\n" foreach ( keys %{ $langs } );
To dereference an array ref use #{ } instead of %{ }. Even sub can be dereferenced.
sub foo
{
print "hello world\n";
}
my %hash = ( call => \&foo );
&{ $hash{call} }; # this allows you to call the sub foo
$arguments is an array reference (a reference/pointer to an array)
You initialize arrays with () and array references with []
my #array = ( 1, 2, 3 );
my $array_ref = [ 1, 2, 3 ];
You can create a reference with \
my $other_array_ref = \#array;
When you use an array reference you will then to dereference it when using:
for my $element ( #{$array_ref} )
or
print ${$array_ref}[0];
See man perlref
Back to your question: $arguments is a reference to an array of hash references (initialized with {})
Looks like a hash of hashes reference.
you may need to dereference like
%newhash = %{$arguments}
and print the data as
print $newhash{'name'}