Greeting Dear Community.
I'm trying to make a sub in perl that takes a hash and a debug flag which defaults to zero. However I keep getting this error Odd number of elements in hash assignment. If I don't use the debug flag, it seems to work.
Thanks for all the help.
Code:
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use POSIX qw(strftime);
#
#file2hash : read the file in k<file_name> e.g.=kconfig & kmem into hash table
#
sub file2hash {
my ($file) = #_;
open(my $data, '<', $file) or die "Could not open '$file' $!\n";
my %HoH;
my $key;
my $value;
my $who;
my $rec;
my $field;
#while ( my $line = <$data>) {
while ( <$data>) {
#print $line;
next unless (s/^(.*?):\s*//); # / turn off editor coloring
$who = $1;
#print $who;
$rec = {};
$HoH{$who} = $rec;
for $field ( split ) {
($key, $value) = split /=/, $field;
$rec->{$key} = $value;
}
}
return %HoH;
}
#
#end file2hash
#
#
#print out hash table in k<file_name> format
#
sub hash2print{
(my %HoH,my $debug) = #_;
#my ($debug)=#_||0;
#my %HoH = shift;
#my $debug = shift || 0;
my $family;
my $role;
for $family ( keys %HoH ) {
#print "$family\n";
for $role ( keys %{ $HoH{$family} } ) {
if ($debug){
print "family:$family\n";
print "role: $role\n";
}
print "$role=$HoH{$family}{$role}";
}
print "\n";
}
}
#
#end hash2print
#
sub dispatch{
my $inc= shift;
my $config_f = shift || "kconfig";
my $memory_f = shift || "kmem";
my %h2=&file2hash($config_f);
my %m2=file2hash($memory_f);
my $today=&getDate();
print "$today\n";
print "$inc\n";
my $inc_cnt = $m2{$today}{$inc} || -999999999;
print "$inc_cnt\n";
#my %config = shift;
#my %mem = shift;
#my $event = shift;
#print $m2{$inc}{$today};
}
sub getDate{
my $date = strftime "%m/%d/%Y", localtime; # "
#print $date;
return $date;
}
my %h2=&file2hash("kconfig");
my %m2=&file2hash("kmem");
&hash2print(%h2,1);
&hash2print(%m2,1);
#print &getDate();
#my $xcnt= &dispatch("event_c3_z2");
#&dispatch("event_c3_z2");
#print $xcnt;
Test file1:
event_a1_x1: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=6
event_a1_x2: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=5
event_b2_y1: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=4
event_b2_y2: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=3
event_c3_z1: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=2
event_c3_z2: email1=ackee0000#gmail.com email2=kym018#gmail.com email1_cnt=1
test file2:
201609230012: event_a1_x1=6
201609230744: event_a1_x2=5
201609230844: event_b2_y1=4
201609230342: event_b2_y2=3
201609230245: event_c3_z1=2
201609230100: event_c3_z2=1
Arguments are passed to a function as one flat list. On the other hand, a hash can be assigned a list, %h = qw(a b), where consecutive elements form key-value pairs, $h{a} is 'b'. So when a hash is the first variable that receives arguments in a function it scoops up all of them. Here is a recent post on how a function returns, where the exact same story applies.
So everything is assigned to the hash, the remaining scalar as well. Thus the hash gets one more element than intended and ends up with an odd number of elements.
A solution -- in this case, only to pass hash by reference, as in the answer by Inferno
sub hash2print{
my ($rHoH, $debug) = #_;
my %HoH = %$rHoH;
# ...
}
hash2print(\%h2, 1);
It is in principle a good idea to pass lists by reference, unless they are very short.
In general you can pass the scalar first then the hash
sub hash2print{
my ($value, %HoH) = #_;
# ...
}
hash2print(1, %h2);
But in your case this doesn't go since the $debug is optional, and if we leave it out when calling the function the first key of the hash would wind up in $value.
A few other comments
Don't use globals unless there is a non-negotiable reason for that. Declare in small scope.
You don't have to do $rec = {};, can just declare it my $rec;.
In general, don't put & in front of a function call but use just file2hash(...);
You want to pass your hashes by value to your subroutine and this creates the problem. Try to pass your hash %h2 by reference instead (notice the \ before the %):
&hash2print(\%h2, 1);
Then in your sub hash2print, you can get the hash back in the following way:
sub hash2print {
(my $hashref, my $debug) = #_;
my %HoH = %$hashref; # dereference $hashref to get back the hash
...
You can read more about references here if you don't understand the concepts behind them.
Related
I have an input file:
id_1 10 15 20:a:4:c
id_2 1 5 2:2:5:c
id_3 0.4 3 12:1:4:1
id_4 18 2 9:1:0/0:1
id_5 a b c:a:foo:2
I have many files of this type that I want to parse in different programs, so I want to make a function that returns a hash with easily accessible.
I've not written a function like this before, and I'm not sure how to properly access the returned hashes. Here's what I've got so far:
Library_SO.pm
#!/urs/bin/perl
package Library_SO;
use strict;
use warnings;
sub tum_norm_gt {
my $file = shift;
open my $in, '<', $file or die $!;
my %SVs;
my %info;
while(<$in>){
chomp;
my ($id, $start, $stop, $score) = split;
my #vals = (split)[1..2];
my #score_fields = split(/:/, $score);
$SVs{$id} = [ $start, $stop, $score ];
push #{$info{$id}}, #score_fields ;
}
return (\%SVs, \%info);
}
1;
And my main script:
get_vals.pl
#!/urs/bin/perl
use Library_SO;
use strict;
use warnings;
use feature qw/ say /;
use Data::Dumper;
my $file = shift or die $!;
my ($SVs, $info) = Library_SO::tum_norm_gt($file);
print Dumper \%$SVs;
print Dumper \%$info;
# for (keys %$SVs){
# say;
# my #vals = #{$SVs{$_}}; <- line 20
# }
I call this with:
perl get_vals.pl test_in.txt
The Dumper output is what I was hoping for, but when I try to iterate over the returned hash(?) and access the values (e.g. as in the commented out section) I get:
Global symbol "%SVs" requires explicit package name at get_vals.pl line 20.
Execution of get_vals.pl aborted due to compilation errors.
Have I got this totally upside down?
Your library function returns two hashrefs. If you now want to access the values you'll have to dereference the hashref:
my ($SVs, $info) = Library_SO::tum_norm_gt($file);
#print Dumper \%$SVs;
# Easier and better readable:
print Dumper $SVs ;
#print Dumper \%$info;
# Easier and better readable:
print Dumper $info ;
for (keys %{ $SVs } ){ # Better visual derefencing
say;
my #vals = #{$SVs->{$_}}; # it is not $SVs{..} but $SVs->{...}
}
I am very new at perl and had discovered the solution at:
Perl: Compare Two CSV Files and Print out differences
I have gone through dozens of other solutions and this comes closest, except that instead of finding the differences between 2 CSV files, I want to find where the second CSV file matches the first one in column and row. How could I modify the following script to find the matches in column/row instead of the differences. I am hoping to dissect this code and learn arrays from there, but wanted to find out the solution to this application. Much thanks.
use strict;
my #arr1;
my #arr2;
my $a;
open(FIL,"a.txt") or die("$!");
while (<FIL>)
{chomp; $a=$_; $a =~ s/[\t;, ]*//g; push #arr1, $a if ($a ne '');};
close(FIL);
open(FIL,"b.txt") or die("$!");
while (<FIL>)
{chomp; $a=$_; $a =~ s/[\t;, ]*//g; push #arr2, $a if ($a ne '');};
close(FIL);
my %arr1hash;
my %arr2hash;
my #diffarr;
foreach(#arr1) {$arr1hash{$_} = 1; }
foreach(#arr2) {$arr2hash{$_} = 1; }
foreach $a(#arr1)
{
if (not defined($arr2hash{$a}))
{
push #diffarr, $a;
}
}
foreach $a(#arr2)
{
if (not defined($arr1hash{$a}))
{
push #diffarr, $a;
}
}
print "Diff:\n";
foreach $a(#diffarr)
{
print "$a\n";
}
# You can print to a file instead, by: print FIL "$a\n";
ok, I realize that this was more what I was looking for:
use strict;
use warnings;
use feature qw(say);
use autodie;
use constant {
FILE_1 => "file1.txt",
FILE_2 => "file2.txt",
};
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = 1;
}
close $file1_fh;
#
# Load Hash #2 with value from File #2
#
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
$hash2{$value} = 1;
}
close $file2_fh;
Now I want to search file2's hash to check if there are ANY matches from file1's hash. That is where I am stuck
With new code suggestion, code now looks like this
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use constant {
FILE_1 => "masterlist.csv",
FILE_2 => "pastebin.csv",
};
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = 1;
}
close $file1_fh;
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
if ( $hash1{$value} ) {
print "Match found $value\n";
$hash2{$value}++;
}
}
close $file2_fh;
print "Matches found:\n";
foreach my $key ( keys %hash2 ) {
print "$key found $hash2{$key} times\n";
}
I updated one part with split() and it seems to work, but have to test more to confirm if it fits the solution I'm looking for or I have more work to do one it
#
# Load Hash #1 with value from File #1
#
my %hash1;
open my $file1_fh, "<", FILE_1;
while ( my $value = <$file1_fh> ) {
chomp $value;
$hash1{$value} = ( %hash1, (split(/,/, $_))[1,2] );
}
close $file1_fh;
So, with your code there - you've read in 'file1' to a hash.
Why not instead of reading file 2 into a hash, do instead:
my %hash2;
open my $file2_fh, "<", FILE_2;
while ( my $value = <$file2_fh> ) {
chomp $value;
if ( $hash1{$value} ) {
print "Match found $value\n";
$hash2{$value}++;
}
}
close $file2_fh;
print "Matches found:\n";
foreach my $key ( keys %hash2 ) {
print "$key found $hash2{$key} times\n";
}
I think this code identifies every place that a data field in file A matches a data field in file B (at least it does on my limited test data):
use strict;
use warnings;
my #arr1;
my #arr2;
# a.txt -> #arr1
my $file_a_name = "poster_a.txt";
open(FIL,$file_a_name) or die("$!");
my $a_line_counter = 0;
while (my $a_line = <FIL>)
{
$a_line_counter = $a_line_counter + 1;
chomp($a_line);
my #fields = (split /,/,$a_line);
my $num_fields = scalar(#fields);
s{^\s+|\s+$}{}g foreach #fields;
push #arr1, \#fields if ( $num_fields ne 0);
};;
close(FIL);
my $file_b_name = "poster_b.txt";
open(FIL,$file_b_name) or die("$!");
while (my $b_line = <FIL>)
{
chomp($b_line);
my #fields = (split /,/,$b_line);
my $num_fields = scalar(#fields);
s{^\s+|\s+$}{}g foreach #fields;
push #arr2, \#fields if ( $num_fields ne 0)
};
close(FIL);
# b.txt -> #arr2
#print "\n",#arr2, "\n";
my #match_array;
my $file_a_line_ctr = 1;
foreach my $file_a_line_fields (#arr1)
{
my $file_a_column_ctr = 1;
foreach my $file_a_line_field (#{$file_a_line_fields})
{
my $file_b_line_ctr = 1;
foreach my $file_b_line_fields(#arr2)
{
my $file_b_column_ctr = 1;
foreach my $file_b_field (#{$file_b_line_fields})
{
if ( $file_b_field eq $file_a_line_field )
{
my $match_info =
"$file_a_name line $file_a_line_ctr column $file_a_column_ctr" .
" (${file_a_line_field}) matches: " .
"$file_b_name line $file_b_line_ctr column $file_b_column_ctr ";
push(#match_array, $match_info);
print "$match_info \n";
}
$file_b_column_ctr = $file_b_column_ctr + 1;
}
$file_b_line_ctr = $file_b_line_ctr + 1;
}
$file_a_column_ctr = $file_a_column_ctr + 1;
}
$file_a_line_ctr = $file_a_line_ctr + 1;
}
print "there were ", scalar(#match_array)," matches\n";
I have the below data and I need to make the second column as the header. Any help is appreciated.
Data:
IBM,Voltality,7,73894756.93897434897
IBM,Market,100,983874.34324
GOOG,Sanity,15,8932748
GOOG,Rate Jump,25,873476378.234234
MBLY,Market,340,23423423432.6783
Output:
PRODUCT|Market|Rate Jump|Sanity|Voltality
IBM|100,983874.34324|||7,73894756.93897434897
GOOG||25,873476378.234234|15,8932748|||
MBLY|340,23423423432.6783|||
Code (incomplete / not sure hot to get to the end):
#!/usr/bin/perl
use strict;
use Getopt::Long;
use warnings;
use Data::Dumper;
my $valsep = ',';
my ( %type, %keys, %ccy, %cnt, %avg );
while (<>) {
chomp;
my ( $product, $reason, $count, $lat ) = split /,/;
my $key = "$product,$reason";
if ( not exists( $type{$reason} ) ) {
$type{$reason} = $reason;
}
$ccy{$key} = $product;
$cnt{$key} = $count;
$avg{$key} = $lat;
}
close(INPUT);
print Dumper ( \%ccy );
print Dumper ( \%type );
my ( %pair, %details );
foreach my $rows ( sort keys %ccy ) {
print "the key is : $rows and $ccy{$rows}\n";
foreach my $res ( sort keys %type ) {
print "The type is : $res and $type{$res}\n";
}
}
You just need to keep track of your columns and row data when parsing the data structure.
The following demonstrates:
#!/usr/bin/perl
use strict;
use warnings;
my $fh = \*DATA;
my %columns;
my %rows;
while (<$fh>) {
chomp;
my ( $company, $col, $vals ) = split ',', $_, 3;
# Track Columns for later labeling
$columns{$col}++;
$rows{$company}{$col} = $vals;
}
my #columns = sort keys %columns;
# Header
print join( '|', 'PRODUCT', #columns ), "\n";
for my $company ( sort keys %rows ) {
print join( '|', $company, map { $_ // '' } #{ $rows{$company} }{#columns} ), "\n";
}
__DATA__
IBM,Voltality,7,73894756.93897434897
IBM,Market,100,983874.34324
GOOG,Sanity,15,8932748
GOOG,Rate Jump,25,873476378.234234
MBLY,Market,340,23423423432.6783
Outputs:
PRODUCT|Market|Rate Jump|Sanity|Voltality
GOOG||25,873476378.234234|15,8932748|
IBM|100,983874.34324|||7,73894756.93897434897
MBLY|340,23423423432.6783|||
The following code will do the job; rather than using several hashes, I've put all the data in a hash of hashes. I've put comments in the script to explain what is happening in case you are not sure. You can, of course, delete them in your script.
#!/usr/bin/perl
use warnings;
use strict;
my %market;
while (<DATA>) {
next unless /\w/;
# remove line endings
chomp;
# split line by commas -- only split into three parts
my #col = split ",", $_, 3;
# save the data as $market{col0}{col1} = col2
$market{$col[0]}{$col[1]} = $col[2];
}
# create an output file
my $outfile = 'output.txt';
open( my $fh, ">", $outfile ) or die "Could not open $outfile: $!";
my #headers = ('Market','Rate Jump','Sanity','Volatility');
# print out the header line, joined by |
print { $fh } join('|', 'PRODUCT', #headers) . "\n";
# for each product in the market data
for my $p (sort keys %market) {
# print the product name
print { $fh } join('|', $p,
# go through the headers using map (map acts like a "for" loop)
# if the relevant property exists in the market data, print it;
# if not, print nothing
map { $market{$p}{$_} // '' } #headers) . "\n";
}
# this is the input data. You might be reading yours in from a file
__DATA__
IBM,Voltality,7,73894756.93897434897
IBM,Market,100,983874.34324
GOOG,Sanity,15,8932748
GOOG,Rate Jump,25,873476378.234234
MBLY,Market,340,23423423432.6783
Output:
PRODUCT|Market|Rate Jump|Sanity|Volatility
GOOG||25,873476378.234234|15,8932748|
IBM|100,983874.34324|||7,73894756.93897434897
MBLY|340,23423423432.6783|||
I have a hash structure and I want to add new value to the existing value (not update with new value ).
here is my code.
use strict;
use warnings;
my %hash;
while(<DATA>){
my $line=$_;
my ($ID)=$line=~/ID=(.*?);/;
#make a hash with ID as key
if (!exists $hash{$ID}){
$hash{$ID}= $line;
}
else{
#add $line to the existing value
}
}
for my $key(keys %hash){
print $key.":".$hash{$key}."\n";
}
__DATA__
ID=13_76; gi|386755343
ID=13_75; gi|383750074
ID=13_75; gi|208434224
ID=13_76; gi|410023515
ID=13_77; gi|499086767
else{
$hash{$ID} .= $line;
}
All you need is $hash{$ID} .= $line;. No if-elses.
If there's no key $ID in the hash it would create one and concatenate $line to empty string, resulting exactly what you need.
You should store your data in a hash of arrays:
#!/usr/bin/env perl
use strict;
use warnings;
# --------------------------------------
use charnames qw( :full :short );
use English qw( -no_match_vars ); # Avoids regex performance penalty
use Data::Dumper;
# Make Data::Dumper pretty
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
# Set maximum depth for Data::Dumper, zero means unlimited
local $Data::Dumper::Maxdepth = 0;
# conditional compile DEBUGging statements
# See http://lookatperl.blogspot.ca/2013/07/a-look-at-conditional-compiling-of.html
use constant DEBUG => $ENV{DEBUG};
# --------------------------------------
my %HoA = ();
while( my $line = <DATA> ){
if( my ( $ID ) = $line =~ m{ ID \= ([^;]+) }msx ){
push #{ $HoA{$ID} }, $line;
}
}
print Dumper \%HoA;
__DATA__
ID=13_76; gi|386755343
ID=13_75; gi|383750074
ID=13_75; gi|208434224
ID=13_76; gi|410023515
ID=13_77; gi|499086767
#!/usr/bin/perl
use strict;
use Data::Dumper;
use warnings;
my #mdsum;
open (IN1,"$ARGV[0]") || die "counldn't open";
open (MYFILE, '>>md5sum-problem.txt');
open (IN2, "mdsumfile.txt");
my %knomexl=();
my %knomemdsum = ();
my #arrfile ;
my $tempkey ;
my $tempval ;
my #values ;
my $val;
my $i;
my #newarra;
my $testxl ;
my $testmdsum;
while(<IN1>){
next if /barcode/;
#arrfile = split('\t', $_);
$knomexl{$arrfile[0]} = $arrfile[2];
}
while(<IN2>){
chomp $_;
#newarra = split(/ {1,}/, $_);
$tempval = $newarra[0];
$tempkey = $newarra[1];
$tempkey=~ s/\t*$//g;
$tempval=~ s/\s*$//g;
$tempkey=~s/.tar.gz//g;
$knomemdsum{$tempkey} = $tempval;
}
#values = keys %knomexl;
foreach $i(#values){
$testxl = $knomexl{$values[$i]};
print $testxl."\n";
$testmdsum = $knomemdsum{$values[$i]};
print $testmdsum."\n";
if ( $testxl ne $testmdsum ) {
if ($testxl ne ""){
print MYFILE "Files hasving md5sum issue $i\n";
}
}
}
close (MYFILE);
I have two files one both having File name and Mdsum values and I need to check that which all file's md5sum values are not matching so I understand that in some case where Value and corresponding values will not be their and I want those cases only. Any work around on this code ? Please. This code is pretty simple but don't know why it's not working!! :( :(
#values = keys %knomexl;
foreach $i(#values){
#print Dumper $knomexl{$values[$i]};
$testxl = $knomexl{$i};
print $testxl."\n";
$testmdsum = $knomemdsum{$i};
print $testmdsum."\n";
$i is an element of #values because of the foreach, not an index, so you shouldn't use $values[$i].