Perl - assign a hash ref in a hash - perl

use 5.010;
use strict;
use warnings;
use JSON::XS;
use YAML::XS;
my %data = ();
my $content = <<HERE;
{
"name":"BLAHBLAH","contact":{"phone":"12345","twitter":"BLAHBLAH"},
"location": {"address":"NOTTELLING","lat":10,"lng":10,"postalCode":"1234",
"city":"BLAH","state":"BLAH","country":"BLAH"},
"categories":[{"id":"BLAH","name":"BLAH"}]
}
HERE
my $id = "name1";
sub function {
my ( $id, $data, $content ) = #_;
my %data = %$data;
my $out = decode_json($content);
say "out:", Dump $out;
$data{$id} = $out;
}
function( $id, \%data, $content );
say "data:", Dump %data;
This doesn't work as the way I expected. Can you please tell me why and how it will work?

"This doesn't work as the way i expected."
What were you expecting? Let's step through the errors:
1) date != data
2) $content=~m!(,*)! will leave $1 empty, since $content doesn't contain any commas.
3) decode_json($1) will throw a runtime error, since $1 is empty and decode_json() can only be applied to a properly formatted JSON string.
4) $id is not defined.
"Can you please tell me why and how it will work?"
It won't work, if that isn't clear yet. There are more errors than code there.
"how do I assign a hash ref into hash?"
Use the \ unary reference operator, eg:
my %h = ();
my %h2 = (
a => 10
);
$h{h2} = \%h2;
print $h{h2}->{a};
You can also declare a scalar ($) as a reference to an anonymous (unnamed) hash; here $hr is a reference, the hash itself has no symbol or name associated with it:
my $hr = {
n => 42
};
# as an existing hash member:
$h{h3} = {
x => 666,
# some other examples:
hr => \%h2,
hr2 => {
x => 1024
}
};
Notice curly braces {} used in the declaration instead of (). When you are nesting (anonymous) hashes as with hr2, always use that form.
If you search for perl hash tutorial you'll find more in-depth things.

The reason that you're not finding anything in the package-scoped %data (the one defined just after use YAML::XS) is because you're creating a brand-new and completely independent %data inside of function with the line
my %data = %$data;
This creates a new hash and copies the contents of the hash referenced by $data into it.
Try this instead:
sub function {
my ($id, $data, $content) = #_;
my $out = decode_json($content);
say "out:", Dump $out;
$data->{$id} = $out;
}

I think you have a typo:
function($id,/%data,$content);
must be
function($id,\%data,$content);
and $content is not a reference to %data hash, so in your function you should do:
my %data=%$data; # in place of "my %content=%$content;"

Related

String Parsing for nested parenthesis in perl

The issue is when I try to compare the input to the output file, i am unable to handle the nesting of the parenthesis, and the complexity needs to be very low. is there a parsing module for this? compatible to 5.8.4. I found modules but they needed at least 5.10.:(
Input
(K1=V1,K2=V2,K3=V3(K2=V2.K5=V5)K6=V6(K7=V7,K8=V8(K9=V9,K10=V10)K11=V11)K12=V12,K13=V13)
OUTPUT FILE
(K0=V0,K1=V1,K2=V2,K3=V3(K1=V1,K2=V2,K4=V4,K5=V5,K14=V14),K15=V15,K6=V6(K18=V18,K7=V7,K19=V19,K8=V8(K20=V20,K9=V9,K16=V16,K10=V10,K21=V21)K11=V11)K12=V12,K13=V13,K22=V22)
I need to pick up each key value pair from input and one by one verify from the output file that the value is the same. if not
I need to store the key with the existing value.( The issue is with the nesting )
INPUT
K3=V3(K2=V2,K5=V5)
OUTPUT
K3=V3(K1=V1,K2=V2,K4=V4,K5=V5,K14=V14)
The issue is that "K2=V2" inside the V3 value is to be checked inside the V3 value in the output file. So I cannot just use a regular expression to do that as K2=V2 may appear outside the V3 parenthesis too.
I was trying to create a hash of a hash of a hash but failed. could someone suggest a way I could achieve this?
The following code builds the hash of hashes. Note that values (V3) are lost if they contain an inner hash.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub to_hash {
my $string = shift;
$string =~ s/^\( | \)$//gx; # Remove the outer parentheses.
my #stack = {};
my #keys;
while (length $string) {
$string =~ s/^([^,=()]+) = ([^(),]*)//x or die $string;
my ($key, $value) = ($1, $2);
$stack[-1]{$key} = $value;
next if $string =~ s/^,//;
if ($string =~ s/^\(//) {
push #stack, {};
push #keys, $key;
} elsif ($string =~ s/^\),?//) {
my $last = pop #stack;
$stack[-1]{ pop #keys } = $last;
}
}
return $stack[0]
}
my $input = '(K1=V1,K2=V2,K3=V3(K2=V2,K5=V5)K6=V6(K7=V7,K8=V8(K9=V9,K10=V10)K11=V11)K12=V12,K13=V13)';
print Dumper to_hash($input);
Output
$VAR1 = {
'K2' => 'V2',
'K13' => 'V13',
'K6' => {
'K7' => 'V7',
'K8' => {
'K9' => 'V9',
'K10' => 'V10'
},
'K11' => 'V11'
},
'K3' => {
'K2' => 'V2',
'K5' => 'V5'
},
'K12' => 'V12',
'K1' => 'V1'
};
Nested parens either suggests an application of Text::Balanced and its extract_bracketed function, or building yourself a little parser subclass on Parser::MGC. Using the latter to build a little "convert string into data structure" parser is usually pretty straightforward for simple examples like this.

How do I add a new Key,Value pair to a Hash in an array of hash in perl?

Hi I have a need to add a new key,value pair to the hash entries within an array of hashes.
Below is some sample code which does not work(simplified with only 1 array entry) The output of the print statement just contains the 1 entry.
my #AoH;
push #AoH, { TEST1 => 'testvalue' };
for my $hash (#AoH)
{
$hash{'TEST2'} = 'testvalue2';
print Dumper($hash);
}
What am I doing wrong?
Thank you.
This code looks a little strange so I am going to assume it was done like that for the purposes of showing it briefly here, but the main thing you need to do to fix your code is change:
$hash{'TEST2'} = 'testvalue2';
to:
$$hash{'TEST2'} = 'testvalue2';
or:
$hash->{'TEST2'} = 'testvalue2';
The extra '$' or '->' dereferences the hash reference '$hash'. Since neither is there, it treats $hash{'TEST2'} as a different variable: '%hash' (not '$hash') and assigns 'testvalue2' to that. You would have gotten a good error message:
Global symbol "%hash" requires explicit package name at - line XX
if you tried to run this code with:
use strict;
use warnings;
at the beginning... which you should always do, so do that every time from now on.
use strict;
use warnings;
use Data::Dumper;
my #AoH=();
my %data_source_hash=(
TEST1 => 'testvalue1',
TEST2 => 'testvalue2'
);
# adds whole hash as the array element
push #AoH,{ %data_source_hash };
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds each hash $key, $value pair as an element
while ( my ($key, $value) = each %data_source_hash )
{
push #AoH, { $key => $value };
}
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds extra hash entry to each array element
push #AoH, { TEST1 => 'testvalue' };
push #AoH, { TEST3 => 'testvalue3' };
foreach my $el (#AoH)
{
my $key = 'TEST2';
$$el{$key} = $data_source_hash{$key};
}
print Dumper(#AoH);

Reference found where even-sized list expected in Perl - Possible pass-by-reference error?

I have a Perl class/module that I created to display Bible verses. In it there is a hash that stores several verses, with the key being the book/chapter/verse and the value being the text. This hash is returned from the module.
I'm including the Bible class in a controller class, and that connection seems to work. The problem is I keep getting errors on executing. My IDE because I'm following a Lynda tutorial, is Eclipse with the EPIC plugin.
The error is:
Reference found where even-sized list expected at C:/Documents and Settings/nunya/eric.hepperle_codebase/lynda/lamp/perl5/Exercise Files/14 Modules/eh_bibleInspiration_controller.pl line 42.
Use of uninitialized value $value in concatenation (.) or string at C:/Documents and Settings/nunya/eric.hepperle_codebase/lynda/lamp/perl5/Exercise Files/14 Modules/eh_bibleInspiration_controller.pl line 45.
HASH(0x19ad454) =>
Here is the CONTROLLER class:
#!/usr/bin/perl
# eh_bibleInspiration_controller.pl by Eric Hepperle - 06/23/13
#
use strict;
use warnings;
use Data::Dumper;
use EHW_BibleInspiration;
main(#ARGV);
sub main
{
my $o = EHW_BibleInspiration->new; # instantiate new object.
my %bo_ref = $o->getBibleObj();
print "\$o is type: " . ref($o) . ".\n";
print "\%bo_ref is type: " . ref(\%bo_ref) . ".\n";
# exit;
$o->getVerseObj();
listHash(\%bo_ref);
message("Done.");
}
sub message
{
my $m = shift or return;
print("$m\n");
}
sub error
{
my $e = shift || 'unkown error';
print("$0: $e\n");
exit 0;
}
sub listHash
{
my %hash = #_;
foreach my $key (sort keys %hash) {
my $value = $hash{$key};
message("$key => $value\n");
}
}
Here is the class that returns the verses and has the method to pick a random verse:
# EHW_BibleInspiration.pm
# EHW_BibleInspiration.
#
package EHW_BibleInspiration;
use strict;
use warnings;
use IO::File;
use Data::Dumper;
our $VERSION = "0.1";
sub new
{
my $class = shift;
my $self = {};
bless($self, $class); # turns hash into object
return $self;
}
sub getVerseObj
{
my ($self) = #_;
print "My Bible Verse:\n";
my $verses = $self->getBibleObj();
# get random verse
#$knockknocks{(keys %knockknocks)[rand keys %knockknocks]};
# sub mysub {
# my $params = shift;
# my %paramhash = %$params;
# }
# my %verses = %{$verses};
# my $random_value = %verses{(keys %verses)[rand keys %verses]};
# print Dumper(%{$random_value});
}
sub getBibleObj
{
my ($self) = #_;
# create bible verse object (ESV)
my $bibleObj_ref = {
'john 3:16' => 'For God so loved the world,that he gave his only Son, that whoever believes in him should not perish but have eternal life.',
'matt 10:8' => 'Heal the sick, raise the dead, cleanse lepers, cast out demons. You received without paying; give without pay.',
'Luke 6:38' => 'Give, and it will be given to you. Good measure, pressed down, shaken together, running over, will be put into your lap. For with the measure you use it will be measured back to you.',
'John 16:24' => 'Until now you have asked nothing in my name. Ask, and you will receive, that your joy may be full.',
'Psalms 32:7' => 'You are a hiding place for me; you preserve me from trouble; you surround me with shouts of deliverance. Selah',
'Proverbs 3:5-6' => 'Trust in the LORD with all your heart, and do not lean on your own understanding. 6 In all your ways acknowledge him, and he will make straight your paths.',
'John 14:1' => 'Let not your hearts be troubled. Believe in God; believe also in me.'
};
my $out = "The BIBLE is awesome!\n";
return $bibleObj_ref;
}
1;
What am I doing wrong? I suspect it has something to do with hash vs hash reference, but I don't know how to fix it. My dereferencing attempts had failed miserably because I don't really know what I'm doing. I modeled my random getter off of something I saw on perlmonks. #$knockknocks{(keys %knockknocks)[rand keys %knockknocks]};
In the main, you have:
my %bo_ref = $o->getBibleObj();
but, in package EHW_BibleInspiration;, the method getBibleObj returns : return $bibleObj_ref;
You'd do, in the main : my $bo_ref = $o->getBibleObj();
and then call listHash($bo_ref);
Finaly, don't forget to change sub listHash to:
sub listHash
{
my ($hash) = #_;
foreach my $key (sort keys %{$hash}) {
my $value = $hash->{$key};
message("$key => $value\n");
}
}
In your main, you do
listHash(\%bo_ref);
This passes a hash reference to the sub. However, it tries to unpack its arguments like
my %hash = #_;
Oops, it wants a hash.
Either, we pass it a hash: listHash(%bo_ref). This saves us much typing.
Or, we handle the reference inside the sub, like
sub listHash {
my ($hashref) = #_;
foreach my $key (sort keys %$hashref) {
my $value = $hashref->{$key};
print "$key => $value\n";
}
}
Notice how the reference uses the dereference arrow -> to access hash entries, and how it is dereferenced to a hash for keys.
I suspect that your suspicion is correct. In your method sub listHash you're correctly passing in the hash but you're trying to use a hash instead of a hash reference for the internal variable. Try using my ($hash) = #_; instead of my %hash = #_;.
When using references you can use the -> operator to de-reference it to get to the underlying values. The rest of your method should look like this:
sub listHash
{
my ($hash) = #_;
foreach my $key (sort keys %{$hash}) {
my $value = $hash->{$key};
message("$key => $value\n");
}
}
On line 43 of your program I had to tell Perl that the reference should be a hash reference by calling keys %{$hash}. Then on line 44 I de-referenced the hash to get the correct value by calling $hash->{$key}. For more information on Perl and references you can read the through the tutorial.
listHash is expecting a hash and you're passing it a hash reference. Change:
listHash(\%bo_ref);
to:
listHash(%bo_ref);

Push into end of hash in Perl

So what I am trying to do with the following code is push a string, let's say "this string" onto the end of each key in a hash. I'm completely stumped on how to do this. Here's my code:
use warnings;
use strict;
use File::Find;
my #name;
my $filename;
my $line;
my #severity = ();
my #files;
my #info = ();
my $key;
my %hoa;
my $xmlfile;
my $comment;
my #comments;
open( OUTPUT, "> $ARGV[0]" );
my $dir = 'c:/programs/TEST/Test';
while ( defined( $input = glob( $dir . "\\*.txt" ) ) ) {
open( INPUT, "< $input" );
while (<INPUT>) {
chomp;
if (/File/) {
my #line = split /:/;
$key = $line[1];
push #{ $hoa{$key} }, "Filename\n";
}
if ( /XML/ ... /File/ ) {
$xmlfile = $1;
push #{ $hoa{$key} }, "XML file is $xmlfile\n";
}
if (/Important/) {
push #{ $hoa{$key} }, "Severity is $_\n";
}
if (/^\D/) {
next if /Important/;
push #{ $hoa{$key} }, "Given comment is $_\n";
}
push #{ $hoa{$key} }, "this string\n";
}
}
foreach my $k ( keys %hoa ) {
my #list = #{ $hoa{$k} };
foreach my $l (#list) {
print OUTPUT $l, "\n";
}
}
}
close INPUT;
close OUTPUT;
Where I have "this string" is where I was trying to push that string onto the end of the array. However, what ended up happening was that it ended up printing "this string" three times, and not at the end of every key like I wanted. When I tried to put it outside the while() loop, it said that the value of $key was not initialized. So please, any help? And if you need any clarification on what I'm asking, just let me know. Thank you!
No offence, but there are so many issues in this code I don't even know where to start...
First, the 'initialization block' (all these my $something; my #somethings lines at the beginning of this script) is not required in Perl. In fact, it's not just 'redundant' - it's actually confusing: I had to move my focus back and forth every time I encountered a new variable just to check its type. Besides, even with all this $input var is still not declared as local; it's either missing in comments, or the code given has omissions.
Second, why do you declare your intention to use File::Find (good) - but then do not use it at all? It could greatly simplify all this while(glob) { while(<FH>) { ... } } routine.
Third, I'm not sure why you assign something to $key only when the line read is matched by /File/ - but then use its value as a key in all the other cases. Is this an attempt to read the file organized in sections? Then it can be done a bit more simple, either by slurp/splitting or localizing $/ variable...
Anyway, the point is that if the first line of the file scanned is not matched by /File/, the previous (i.e., from the previous file!) value is used - and I'm not quite sure that it's intended. And if the very first line of the first file is not /File/-matched, then an empty string is used as a key - again, it smells like a bug...
Could you please describe your task in more details? Give some test input/output results, perhaps... It'd be great to proceed in short tasks, organizing your code in process.
Your program is ill-conceived and breaks a lot of good practice rules. Rather than enumerate them all, here is an equivalent program with a better structure.
I wonder if you are aware that all of the if statements will be tested and possibly executed? Perhaps you need to make use of elsif?
Aside from the possibility that $key is undefined when it is used, you are also setting $xmlfile to $1 which will never be defined as there are no captures in any of your regular expressions.
It is impossible to tell from your code what you are trying to do, so we can help you only if you show us your output, input and say how to derive one from the other.
use strict;
use warnings;
use File::Find;
my ($outfile) = #ARGV;
my $dir = 'c:/programs/TEST/Test';
my %hoa;
my $key;
while (my $input = glob "$dir/*.txt") {
open my $in, '<', $input or die $!;
while (<$in>) {
chomp;
if (/File/) {
my $key = (split /:/)[1];
push #{ $hoa{$key} }, "Filename\n";
}
if (/XML/ ... /File/) {
my $xmlfile = $1;
push #{ $hoa{$key} }, "XML file is $xmlfile\n";
}
if (/Important/) {
push #{ $hoa{$key} }, "Severity is $_\n";
}
if (/^\D/) {
next if /Important/;
push #{ $hoa{$key} }, "Given comment is $_\n";
}
push #{ $hoa{$key} }, "this string\n";
}
close $in;
}
open my $out, '>', $outfile or die $!;
foreach my $k (keys %hoa) {
foreach my $l (#{ $hoa{$k} }) {
print $out $l, "\n";
}
}
close $out;
I suspect based on your code, that the line where $key is set is not called each time through the loop, and that you do not trigger any of the other if statements.
This would append "this string" to the end of the array. Based on that you are getting 3 of the "this strings" at the end of the array, I would suspect that two lines do not go through the if (/FILE/) or any of the other if statements. This would leave the $key value the same and at the end, you would append "this string" to the array, using whatever the last value of $key was when it was set.
This will append the string "this string" to every element of the hash %hoa, which elements are array refs:
for (values(%hoa)) { push #{$_}, "this string"; }
Put that outside your while loop, and you'll print "this string" at the end of each element of %hoa.
It will autovivify array refs where it finds undefined elements. It will also choke if it cannot dereference an element as an array, and will manipulate arrays by symbolic reference if it finds a simple scalar and is not running under strict:
my %autoviv = ( a => ['foo'], b => undef );
push #$_, "PUSH" for values %autoviv; # ( a => ['foo', 'PUSH'], b => ['PUSH'] )
my %fatal = ( a => {} );
push #$_, "PUSH" for values %fatal; # FATAL: "Not an ARRAY reference at..."
my %dangerous = (a => "foo");
push #$_, "PUSH" for values %dangerous; # Yikes! #foo is now ("PUSH")
use strict;
my %kablam = (a => "foo");
push #$_, "PUSH" for values %kablam; # "Can't use string ("foo") as an ARRAY ref ..."
As I understand it, traverse the hash with a map command to modify its keys. An example:
EDIT: I've edited because I realised that the map command can be assigned to the same hash. No need to create a new one.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash = qw|
key1 value1
key2 value2
key3 value3
|;
my %hash = map { $_ . "this string" => $hash{ $_ } } keys %hash;
print Dump \%hash;
Run it like:
perl script.pl
With following output:
$VAR1 = {
'key3this string' => 'value3',
'key2this string' => 'value2',
'key1this string' => 'value1'
};

Unable to pass a hash and a string to a function, together in perl!

I am basically trying to pass a string and a hash to a subroutine in perl.
sub coru_excel {
my(%pushed_hash, $filename) = #_;
print Dumper(%pushed_hash);
}
But it seems data is getting mixed up. The dumped data also includes the $filename. here is the output.
...................
$VAR7 = 'Address';
$VAR8 = [
'223 VIA DE
................
];
$VAR9 = 'data__a.xls' <----- $filename
$VAR10 = undef;
$VAR11 = 'DBA';
$VAR12 = [
'J & L iNC
..................
];
Here is how I called the subroutine.
coru_excel(%hash, "data_".$first."_".$last.".xls");
Arguments are passed to subroutines as one undifferentiated list.
One solution is to reverse the order of the arguments so that the scalar is first.
sub coru_excel {
my($filename, %pushed_hash) = #_;
}
coru_excel("FILE_NAME", %hash);
Another approach is to pass the hash by reference:
sub coru_excel {
my($pushed_hash_ref, $filename) = #_;
}
coru_excel(\%hash, "FILE_NAME");
You could pass the hash as a reference:
sub coru_excel {
my($pushed_hashref, $filename) = #_;
print Dumper(%$pushed_hashref);
}
coru_excel(\%my_hash, $file);
Or you could give special treatment to the final argument before you initialize the hash:
sub coru_excel {
my $filename = pop #_;
my(%pushed_hash) = #_;
print Dumper(%pushed_hash);
}
You have to pass the hash as a reference:
coru_excel(\%hash, "data_".$first."_".$last.".xls");
You use it like this:
sub coru_excel {
my($pushed_hash_ref, $filename) = #_;
my %pushed_hash = %{$pushed_hash_ref};
print Dumper(%pushed_hash); # better: \%pushed_hash or $pushed_hash_ref
}
See perlreftut for a tutorial on references and perlref for further information.
Dumper also produces better usable information when you pass a hash (or array) reference.
See also the related Perl FAQ. From the command line:
perldoc -q pass
or
perldoc -q hash
Refer to perlfaq7: How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}?
A small program demonstrating how to do this using reference notation when passing the hash and shift in the subroutine to pull out the parameters.
#!/usr/bin/perl -w
use strict;
sub coru_excel(%$);
my %main_hash = ('key1' => 'val1', 'key2' => 'val2');
my $first = "ABC";
my $last = "xyz";
coru_excel(\%main_hash, "data_" . $first . "_" . $last . ".xls");
exit;
sub coru_excel(%$)
{
my %passed_hash = %{(shift)};
my $passed_string = shift;
print "%passed_hash:\n";
for my $k (keys %passed_hash) {
print " $k => $passed_hash{$k}\n";
}
print "\$passed_string = $passed_string\n";
return;
}