array to hash in perl - perl

I have a source list from which I am picking up random items and populating the destination list. The item that are in the list have a particular format. For example:
item1{'name'}
item1{'date'}
etc and many more fields.
while inserting into the destination list I check for unique names on items and insert it into that list. For this I have to traverse the entire destination list to check if an item with a given name exists and if not insert it.
I thought it would be nice if I make the destination list as hash instead of a list again so that I can look up for the item faster and efficiently. I am new to Perl and am not getting how to do this. Anybody, Please help me on how to insert an item, find for a particular item name, and delete an item in hash?
How can I make both the name and date as key and the entire item as value?

my %hash;
Insert an item $V with a key $K?
$hash{$K} = $V
Find for a particular name / key $K?
if (exists $hash{$K}) {
print "it is in there with value '$hash{$K}'\n";
} else {
print "it is NOT in there\n"
}
Delete a particular name / key?
delete $hash{$K}
Make name and date as key and entire item as value?
Easy Way: Just string everything together
set: $hash{ "$name:$date" } = "$name:$date:$field1:$field2"
get: my ($name2,$date2,$field1,$field2) = split ':', $hash{ "$name:$date" }
del: delete $hash{ "$name:$date" }
Harder Way: Store as a hash in the hash (google "perl object")
set:
my %temp;
$temp{"name"} = $name;
$temp{"date"} = $date;
$temp{"field1"} = $field1;
$temp{"field2"} = $field2
$hash{"$name:$date"} = \$temp;
get:
my $find = exists $hash{"$name:$date"} ? $hash{"$name:$date"} : undef;
if (defined find) { # i.e. it was found
printf "field 1 is %s\n", $find->{"field1"}
} else {
print "Not found\n";
}
delete:
delete $hash{"$name:$date"}

It is not easy to understand what you are asking because you do not describe the input and the desired outputs specifically.
My best guess is something along the lines of:
#!/usr/bin/perl
use strict; use warnings;
my #list = (
q(item1{'name'}),
q(item1{'date'}),
);
my %lookup;
for my $entry ( #list ) {
my ($name, $attrib) = $entry =~ /([^{]+){'([^']+)'}/;
$lookup{ $name }{ $attrib } = $entry;
}
for my $entry ( keys %lookup ) {
my %entry = %{ $lookup{$entry} };
print "#entry{keys %entry}\n"
}
use YAML;
print Dump \%lookup;
Output:
item1{'date'} item1{'name'}
---
item1:
date: "item1{'date'}"
name: "item1{'name'}"

If you know what items, you are going to need and what order you'll need them in
for keys, then re parsing the key is of questionable value. I prefer to store
them in levels.
$hash{ $h->{name} }{ $h->{date} } = $h;
# ... OR ...
$hash{ $h->{date} }{ $h->{name} } = $h;
foreach my $name ( sort keys %hash ) {
my $name_hash = $hash{$name};
foreach my $date ( keys %$name_hash ) {
print "\$hash{$name}{$date} => " . Dumper( $name_hash->{$date} ) . "\n";
}
}
For arbitrary levels, you may want a traversal function
sub traverse_hash (&#) {
my ( $block, $hash_ref, $path ) = #_;
$path = [] unless $path;
my ( #res, #results );
my $want = wantarray;
my $want_something = defined $want;
foreach my $key ( %$hash_ref ) {
my $l_path = [ #$path, $key ];
my $value = $hash_ref->{$key};
if ( ref( $value ) eq 'HASH' ) {
#res = traverse_hash( $block, $value, $l_path );
push #results, #res if $want_something && #res;
}
elsif ( $want_something ) {
#res = $block->( $l_path, $value );
push #results, #res if #res;
}
else {
$block->( $path, $value );
}
}
return unless $want_something;
return $want ? #results : { #results };
}
So this does the same thing as above:
traverse_hash {
my ( $key_path, $value ) = #_;
print( '$hash{' . join( '}{', #$key_path ) . '} => ' . ref Dumper( $value ));
();
} \%hash
;

Perl Solution
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
sub main{
my %hash;
my #keys = qw(firstname lastname age); # hash's keys
# fname lname age
# --------|--------|-----
my #arr = ( [ 'foo1', 'bar1', '1' ],
[ 'foo2', 'bar2', '2' ],
[ 'foo3', 'bar3', '3' ]
);
# test if array set up correctly
print "\$arr[1][1] : $arr[1][1] \n"; # bar2
# loads the multidimensional array into the hash
for my $row (0..$#arr){
for my $col ( 0..$#{$arr[$row]} ){
my $itemnum = "item" . ($row+1); # using the item# format you used
$hash{$itemnum}->{$keys[$col]} = $arr[$row][$col];
}
}
# manually add a 4th item
$hash{item4} = {"firstname", "foo", "lastname", "bar", "age", "35"};
# How to Retrieve
# -----------------------
# single item pull
print "item1->firstname : $hash{item1}->{firstname} \n"; # foo1
print "item3->age : $hash{item3}->{age} \n"; # 3
# whole line 1
{ local $, = " ";
print "full line :" , %{$hash{item2}} , "\n"; # firstname foo2 lastname bar2 age 2
}
# whole line 2
foreach my $key (sort keys %{$hash{item2}}){
print "$key : $hash{item2}{$key} \n";
}
# Clearer description
#print "Hash:\n", Dumper %hash;
}
main();
This should be used in addition to the accepted answer. Your question was a little vague on the array to hash requirement, perhaps this is the model you are looking for?

Related

How to print the previous Key Value in Perl?

In this code, I'm checking if a certain key is present or not.
Here I am checking if key "Uri" present. I am getting output as "3".
use strict;
use warnings;
my %Names = (
Martha =>2,
Vivek =>9,
Jason =>6,
Socrates=>7,
Uri =>3,
Nitin =>1,
Plato =>0,
);
if (exists $Names{Uri} ) {
print "$Names{Uri}\n";
}
foreach my $name (sort {$Names{$a} cmp $Names{$b}} keys %Names)
{
print $name, $Names{$name}."\n";
}
Output
3
Plato 0
Nitin 1
Martha 2
Uri 3
Jason 6
Socrates 7
Vivek 9
But, I want the previous key value present before that key. For example:
If I search for key "Uri" Output should be "2"
If I search for key "Vivek" Output should be "7"
If I search for key "Plato" Output should be "0"
Does anyone know how to do it?
Create a sorted array of the hash values, then search through the array to get the value just lower than the value of your search key.
use strict;
use warnings;
my %Names = (
Martha =>2,
Vivek =>9,
Jason =>6,
Socrates=>7,
Uri =>3,
Nitin =>1,
Plato =>0,
);
my #vals = sort {$a <=> $b} values %Names;
get_prev('Uri');
get_prev('Vivek');
get_prev('Plato');
sub get_prev {
my $k = shift;
if (exists $Names{$k}) {
for (#vals) {
if ($Names{$k} == $vals[$_]) {
my $idx = ($_ == 0) ? 0 : $_ - 1;
print $vals[$idx], "\n";
last;
}
}
}
}
Prints:
2
7
0
If you want to print them all:
my $prev;
for my $name (
sort { $Names{$a} <=> $Names{$b} } # Note to use of <=> for numerical comparisons.
keys(%Names)
) {
say "$name $Names{$prev}" if $prev;
$prev = $name;
}
Similarly, to print just one
my $find = 'Uri';
my $prev;
for my $name (
sort { $Names{$a} <=> $Names{$b} }
keys(%Names)
) {
if ($name eq $find) {
say "$name $Names{$prev}" if $prev;
last;
}
$prev = $name;
}
The above hat would be an expensive way to perform multiple lookups.
For that, we'd build a mapping from names to the previous names.
my %prev_name_lkup;
my $prev;
for my $name (
sort { $Names{$a} <=> $Names{$b} }
keys(%Names)
) {
$prev_name_lkup{$name} = $prev if $prev;
$prev = $name;
}
This could also be done as follows:
my #sorted_names =
sort { $Names{$a} <=> $Names{$b} }
keys(%Names);
my %prev_name_lkup =
map { $sorted_names[$_-1] => $sorted_names[$_] }
1..$#sorted_names;
Either way, the lookups would look like this:
say "Uri $Names{$prev_name_lkup{Uri}}";

How do i push a value from a hash onto an array of a hash?

I have a %hashmap and an array #values.
In my code the %hashmap is being created like this $hashmap{$key}="$name";
After the %hashmap is created i need to take it's value and add it to the same %hashmap but with a different key , the new hashmap looks like this :
#hashvalues=($name,$type,$Statement,\#parents,\#children)
$hashmap{$newkey}=\#hashvalues;
I want to push the $name from $hashmap{$key} into the \#children of the $hashmap{$newkey}
This is my code so far :
# first i check if the $hashmap exists so i know i update it
if(exists$hashmap{$name}){
my $auxiliary=\#{$hashmap{$name}};
push(#children,#$auxiliary);
}
my #hashvalues=($name,$type,$Statement,\#parents,\#children);
$hashmap{$name}=\#hashvalues;
The %hash i want to push it is created here , there is no other record of it :
if ($parent ne #$hashvalues2[0]) {
$hashmap{$parent}="$child";
}
The value i am interested to store and push is $child here .
Here is the place were the same %hash will be created again but with the fields name , type etc : (not empty fields ! , they all have a value assigned earlier )
#hashvalues = ($name, $type, $Statement, \#parents, \#children)
$hashmap{$newkey} = \#hashvalues;
I want to see if the %hash was created before this point #hasvalues=($name..
So i check it with this code :
if (exists$hashmap{$name}) { Do Code... }
If there was a recording of it i want to update the %hash , by pushing the value $child in the \#parents of the #hashvalues , so when %hash with type , name .. will be made to have the $child value for the previous version of it.
Here is the order of the code :
if (exists$hashmap{$name}) { Do Code; }
my #hashvalues = ($name, $type, $Statement, \#parents, \#children);
$hashmap{$name} = \#hashvalues;
if ($parent ne #$hashvalues2[0]) {
$hashmap{$parent} = "$child";
}
Here is the whole code :
#FileStatements - An array of Statements
$Statement - a larger string where i collect all my data from
And i fill the #hashvalues with all the data i collect
my $FROMduplicate="";
my $JOINduplicate="";
foreach my $Statement (#FileStatements) {
if ($Statement!~m/create/i) {
next;
}
if ($Statement=~m/create user |^GRANT |^spool /gim) {
next;
}
my $name="";
my $type="";
my $content="";#FileStatements
my #parents=();
my #children=();
my $duplicate="";
# print $Statement."\n";
#NAME--------------------------------------------
my $catch = (split(/ view | trigger | table | synonym | procedure | role /i, $Statement))[1];
$catch =~ s/^\s+//;
$name = (split(/\s+/, $catch))[0];
if ($name=~m/undef/gi){next;}
#DEBUG #print "$name\n";
#TYPE--------------------------------------------
if( $Statement=~m/^create or replace \w+ /i) {
my $tmp = (split(/ replace /i, $Statement))[1];
$tmp =~ s/^\s+//;
$type = (split(/\s+/, $tmp))[0];
}
else{
my $tmp = (split(/^create /i, $Statement))[1];
$tmp =~ s/^\s+//;
$type = (split(/\s+/, $tmp))[0];
}
if ($type=~m/undef| undef |\s+undef\s+|\s+undef,/) {
next;
}
#print "$type\n";
#CONTENT-----------------------------------------
#PARENTS-----------------------------------------
my #froms = split(/ from\s+/i, $Statement);
my #joins = split(/ join /i, $Statement);
foreach my $i (1..#froms-1) {
#print Writer1 "$froms[$i]"."\n\n";
my $from = (split(/ where |select | left | left | right | as /i, $froms[$i])) [0];
$from=~s/^\s+//;
$from=~s/\(+//;
my #Spaces = split(/, | , /,$from);
foreach my $x (0..#Spaces-1) {
my $SpaceFrom = (split(/ /,$Spaces[$x])) [0];
$SpaceFrom=~s/;//;
$SpaceFrom=~s/\)+//;
# print Writer1 $SpaceFrom."\n\n";
if ($SpaceFrom eq $FROMduplicate) {
next;
}
push(#parents,$SpaceFrom);
$FROMduplicate=$SpaceFrom;
}
}
foreach my $x (1..#joins-1){
#print "$joins[$i]"."\n\n";
my $join = (split(/ on /i,$joins[$x])) [0];
$join = (split(/ /i,$joins[$x])) [0];
#print Writer "\n\n".$join."\n\n";
if ($join eq $JOINduplicate) {
next;
}
push(#parents,$join);
$JOINduplicate=$join;
}
#parents = do { my %seen; grep { !$seen{$_}++ } #parents };
#check hash for existence
if(exists$hashmap{$name}){
push(#{$hashmap[3]},#parents);
push(#{$hashmap[0]},$name);
push(#{$hashmap[1]},$type);
push(#{$hashmap[2]},$Statement);
}
my #hashvalues=($name,$type,$Statement,\#parents,\#children);
$hashmap{$name}=\#hashvalues;
# push(#children,$hashmap{$name}) if( exists$hashmap{$name})
}
}
Your question is far from clear, but I think I can answer this question out of context
I want to push the $name from $hashmap{$key} into the \#children of the $hashmap{$newkey}
I assume you have something like this in place already
my %hashmap;
my ( $name, $type, $Statement, #parents, #children );
my #hashvalues = ( $name, $type, $Statement, \#parents, \#children );
$hashmap{$newkey} = \#hashvalues;
Remember that the identifiers name, type, Statement etc. have vanished, and these five values are simply elements of an array
The $name from $hashmap{$key} is the first element of the array, so it is
$hashmap{$key}[0]
The #children of the $hashmap{$newkey} is the fifth element of the array, or
$hashmap{$newkey}[4]
To push the first into the second, you need
push #{ $hashmap{$newkey}[4] }, $hashmap{$key}[0]
You should also use something more meaningful than hashmap for your identifier. The % says that the variable is a hash (there's no such thing as a Perl hash map) and you should use the name to describe the nature of its contents

Perl: multidimensional hash

suppose I have the following data
cluster1:d(A),f(C)s,(A)
cluster2:r(D),h(D),f(A)
I want this out put
output:
cluster1:A->2
cluster1:C->1
cluster2:D->2
cluster2:A->1
here is my try,but it is not correct , the part that I am trying to count characters has a problem that I cant fix
the code is a part of very big code ,and I want exactly multidimensional hash
use strict;
use Data::Dumper;
my %count;
while (<DATA>) {
my %HoH;
my ( $cluster, $ch ) = split (/:/,$_);
$HoH{$cluster}={split /[()]+/,$ch};
for my $clust ( keys %HoH ) {
for my $character ( keys %{ $HoH{$clust} } ) {
$count{$clust}{$HoH{$clust}{$character}}++;
}
}
}
print Dumper(\%count);
foreach my $name (sort keys %count) {
foreach my $subject (keys %{$count{$name}}) {
print "$name:$subject->$count{$name}{$subject}\n";
}
}
DATA
cluster1:d(A),f(C)s,(A)
cluster2:r(D),h(D),f(A)
It would be nice if you try to understand the below code so that you can get an idea for solving the problem:-
use strict;
use Data::Dumper;
my $data = "cluster1:A,B,C,A";
my %cluster = ();
my ($cluster_key, $cluster_val ) = split (':', $data);
my #cluster1_data = split(',', $cluster_val);
foreach my $val ( #cluster1_data ) {
$cluster{$cluster_key}{$val}++;
}
print Dumper(\%cluster);
foreach my $clus ( keys %cluster ) {
my $clus_ref = $cluster{$clus};
foreach my $clu ( keys %{ $clus_ref } ){
my $count = $clus_ref->{$clu};
print"$clus:$clu->$count\n";
}
}
Output:
$VAR1 = {
'cluster1' => {
'A' => 2,
'C' => 1,
'B' => 1
}
};
cluster1:A->2
cluster1:C->1
cluster1:B->1
What do you expect $count{$cluster}{$characters}+=1; to do exactly? You have to loop over your input data to populate %count if you expect to get the desired result:
while (<DATA>) {
next unless /^(cluster\d+):(.+)/;
$count{$1}{$_}++ for split/,/, $2;
}
If you also add sort to the second foreach you'll get the output you want.
EDIT: This solves the question for the updated input and requirements:
my %count;
while (<DATA>) {
next unless /^(cluster\d+):(.+)/;
my $cluster = $1;
$count{$cluster}{$_}++ for $2 =~ /\((\w)\)/g;
}
for my $key (sort keys %count) {
for my $value (sort {
$count{$key}{$b} <=> $count{$key}{$a}
} keys %{$count{$key}}) {
print "$key:$value->$count{$key}{$value}\n";
}
}

Perl: Matching hash keys to a regular expression

I'm wondering if Perl has a built-in way to check for the existence of a hash element with a key matching a particular regex. For example:
my %h = ( 'twelve' => 12, 'thirteen' => 13, 'fourteen' => 14 );
I'm wondering if there is any way to do this:
print "We have 12\n" if exists $h{twelve};
print "We have some teens\n" if exists $h{/.*teen$/};
The smart match operator does this (available since Perl v5.10).
$a $b Type of Match Implied Matching Code
====== ===== ===================== =============
...
Regex Hash hash key grep grep /$a/, keys %$b
...
Sample usage:
# print if any key in %h ends in "teen"
print "We have some teens\n" if /.*teen$/ ~~ %h;
In addition to the other answers here you can also do this with perl's grep:
print "We have some teens\n" if grep {/.*teen/} keys %h;
Yeah, it's called:
use List::Util qw<first>;
# Your regex does not compile perhaps you mean /teen$/
my $value = $hash{ ( first { m/teen/ } keys %hash ) || '' };
(Before smart match, that is. See mob's answer for smart match.)
You could also sort the keys:
my $value = $hash{ ( first { m/teen/ } sort keys %hash ) || '' };
I would freeze this into an "operation":
use Scalar::Util qw<reftype>;
sub values_for_keys_like (\[%$]$) {
my $ref = reftype( $_[0] ) eq 'HASH' ? $_[0] : $$_[0];
return unless my #keys = keys %$ref;
my $regex = shift;
# allow strings
$regex = qr/$regex/ unless my $typ = ref( $regex );
# allow regex or just plain ol' filter functions.
my $test = $typ eq 'CODE' ? $regex : sub { return unless m/$regex/; 1 };
if ( wantarray ) {
return unless my #k = grep { defined $test->( $_ ) } #keys;
return #$ref{ #k };
}
else {
return unless my $key = first { defined $test->( $_ ) } #keys;
return $ref->{ $key };
}
}
And you could use it like so:
my $key = values_for_keys_like( %hash => qr/teen/ );
Or
my $key = values_for_keys_like( $base->{level_two}{level_three} => qr/teen/ );
There's no built-in way, but there's Tie::Hash::Regex on CPAN.

Recursively printing data structures in Perl

I am currently learning Perl. I have Perl hash that contains references to hashes and arrays. The hashes and arrays may in turn contain references to other hashes/arrays.
I wrote a subroutine to parse the hash recursively and print them with proper indentation. Though the routine works as expected, my instructor was not convinced about the readability and elegance of the below code.
I would really appreciate to get the views of Perl experts here on possible optimization of the below code.
Here is my complete code snippet..
# Array of Arrays
$ref_to_AoA = [
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
];
#Array of Hashes
$ref_to_AoH = [
{
husband => "barney",
wife => "betty",
son => "bamm bamm",
},
{
husband => "george",
wife => "jane",
son => "elroy",
},
];
# Hash of Hashes
$ref_to_HoH = {
flintstones => {
husband => "fred",
pal => "barney",
},
jetsons => {
husband => "george",
wife => "jane",
"his boy" => "elroy", # Key quotes needed.
},
simpsons => {
husband => "homer",
wife => "marge",
kid => "bart",
},
};
# Hash which contains references to arrays and hashes
$finalHash = {
'arrayofArrays' => $ref_to_AoA,
'arrayofHash' => $ref_to_AoH,
'hashofHash' => $ref_to_HoH,
};
$string = str($finalHash);
print "$string\n";
#------------------------------------------------------------------
sub str {
my $hash = shift;
my ($space, $newline, $delimiter) = #_;
$space = "" unless (defined $space);
$newline = "\n\n\n" unless (defined $newline);
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
my $str = "";
for (sort keys %{$hash}) {
my $value = $hash->{$_};
$str .= "$newline$space$_ == $value$delimiter";
$str .= recurseErrors($value,$space);
}
$str;
}
#------------------------------------------------------------------
sub recurseErrors {
my $str;
my ($value,$space) = #_;
my $ref = ref $value;
if ($ref eq 'ARRAY') {
my $i = 0;
my $isEmpty = 1;
my #array = #$value;
$space .= "\t";
for my $a (#array) {
if (defined $a) {
$isEmpty = 0;
$str .= "\n$space$_\[$i\] :";
$str .= recurseErrors($a,$space);
}
$i++;
}
$str .= "= { }" if ($isEmpty);
} elsif ($ref eq 'HASH') {
$space .= "\t";
for my $k (sort keys %$value) {
if ( ( ref($value->{$k}) eq 'HASH') || (ref $value->{$k} eq 'ARRAY') ) {
my $val = $value->{$k};
$str .= "\n\n$space$k == ";
$str .= "$val";
}
else {
$str .= "\n$space$k == ";
}
$str .= recurseErrors($value->{$k},$space);
}
# we have reached a scalar (leaf)
} elsif ($ref eq '') {
$str .= "$value";
}
$str
}
#------------------------------------------------------------------
Output:
arrayofArrays == ARRAY(0x9d9baf8)
--------------------------------------------
arrayofArrays[0] :
arrayofArrays[0] :fred
arrayofArrays[1] :barney
arrayofArrays[1] :
arrayofArrays[0] :george
arrayofArrays[1] :jane
arrayofArrays[2] :elroy
arrayofArrays[2] :
arrayofArrays[0] :homer
arrayofArrays[1] :marge
arrayofArrays[2] :bart
arrayofHash == ARRAY(0x9d9bba8)
--------------------------------------------
arrayofHash[0] :
husband == barney
son == bamm bamm
wife == betty
arrayofHash[1] :
husband == george
son == elroy
wife == jane
hashofHash == HASH(0x9da45f8)
--------------------------------------------
flintstones == HASH(0x9d9bb48)
husband == fred
pal == barney
jetsons == HASH(0x9d9bbf8)
his boy == elroy
husband == george
wife == jane
simpsons == HASH(0x9d9bc48)
husband == homer
kid == bart
wife == marge
Always use use strict;
To be a good boy, use use warnings as well.
The names you use for subroutines should make it obvious what the subroutine does. "recurseErrors" kind of violates that principle. Yes, it does recurse. But what errors?
On the first line of each subroutine you should declare and initialize any parameters. recurseErrors first declares $str and then declares its parameters.
Don't mix shift and = #_ like you do in str()
You might consider breaking up what is now called recurseErrors into specialized routines for handling arrays and hashes.
There's no need to quote variables like you do on lines 99 and 109.
Apart from that I think your instructor had a bad day that day.
maybe Data::Dumper is what you want:
use Data::Dumper;
$str = Dumper($foo);
print($str);
If you are new to perl, I'd recommend running your code through perl-critic (there is also a script you can install from CPAN, normally I use it as a test so it gets run from the command line whenever I do "make test"). In addition to its output, you might want to break up your functions a bit more. recurseErrors has three cases that could be split into sub functions (or even put into a hash of ref-type to sub-function ref).
If this were a production job, I'd use Data::Dumper, but it sounds like this is homework, so your teacher might not be too pleased.
Here is one simple example why your code is not easily readable:
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);
You could use the defined or operator:
$delimiter //= "\n" . '-' x 44;
If you are worried about earlier Perls:
defined $delimeter or $delimeter = "\n" . '-' x 44;
Conditionals going off the right margin are enough of a turn-off for me not to read the rest of the code.
My guess is that he doesn't like that you
expect a hash in the str function.
call the same function to print arrays as hashes, despite that there appears to be no common function between them.
allow various ways to call str, but it never figures into the final result.
allow configurable space to be passed in to the root function, but have a tab hardcoded in the recursive function.
omit undefined values that actually hold a place in the arrays
Those are issues that I can see, pretty quickly.
You could have separated out the code blocks that dealt with arrays, and hashes.
sub recurse{
...
recurse_A(#_) if $ref eq 'ARRAY';
recurse_H(#_) if $ref eq 'HASH';
...
}
sub recurse_A{ ... }
sub recurse_H{ ... }
I would recommend starting out your subroutines like this, unless you have a real good reason for doing otherwise.
sub example{
my( $one, $two, $three, $optional_four ) = #_;
( If you do it like this then Komodo, at least, will be able to figure out what the arguments are to your subroutine )
There is rarely any reason to put a variable into a string containing only the variable.
"$var" eq $var;
The only time I can think I would ever do that is when I am using an object that has an overloaded "" function, and I want to get the string, without also getting the object.
package My_Class;
use overload
'""' => 'Stringify',
;
sub new{
my( $class, $name ) = #_;
my $self = bless { name => $name }, $class;
return $self;
}
sub Stringify{
my( $self ) = #_;
return $self->{name};
}
my $object = My_Class->new;
my $string = "$object";
I've struggled with this same problem before, and found my way here. I almost used a solution posted here, but found a more suitable one (for me anyway). Read about Depth First Recursion here.
The sub in the above article works perfectly with a reference containing other Hashes, Arrays, or Scalars. It did not print Hash key names, though, so I slightly modified it:
#!/usr/bin/perl
#
# See:
#
# http://perldesignpatterns.com/?DepthFirstRecursion
#
use strict;
use warnings;
my %hash = (
'a' => {
'one' => 1111,
'two' => 222,
},
'b' => [ 'foo', 'bar' ],
'c' => 'test',
'd' => {
'states' => {
'virginia' => 'richmond',
'texas' => 'austin',
},
'planets' => [ 'venus','earth','mars' ],
'constellations' => ['orion','ursa major' ],
'galaxies' => {
'milky way' => 'barred spiral',
'm87' => 'elliptical',
},
},
);
&expand_references2(\%hash);
sub expand_references2 {
my $indenting = -1;
my $inner; $inner = sub {
my $ref = $_[0];
my $key = $_[1];
$indenting++;
if(ref $ref eq 'ARRAY'){
print ' ' x $indenting,'ARRAY:';
printf("%s\n",($key) ? $key : '');
$inner->($_) for #{$ref};
}elsif(ref $ref eq 'HASH'){
print ' ' x $indenting,'HASH:';
printf("%s\n",($key) ? $key : '');
for my $k(sort keys %{$ref}){
$inner->($ref->{$k},$k);
}
}else{
if($key){
print ' ' x $indenting,$key,' => ',$ref,"\n";
}else{
print ' ' x $indenting,$ref,"\n";
}
}
$indenting--;
};
$inner->($_) for #_;
}
#use strict ;
use warnings ;
# use module
use XML::Simple;
use Data::Dumper;
#debug print "START SCRIPT " ;
my $fileToParse = 'C:/Temp/CDIP/scripts/perl/nps_all_workflows.xml' ;
# create object
my $objXml= new XML::Simple;
# read XML file
my $data = $objXml->XMLin("$fileToParse");
# #debug print "\n FirstLevel is " . $objXml->{'POWERMART'} ;
my $level = 1 ;
#
printHashKeyValues ($data ) ;
sub printHashKeyValues
{
$level ++ ;
my $refHash = shift ;
my $parentKey = shift ;
my $parentValue = shift ;
while( my ($key, $value) = each %$refHash)
{
if ( defined ( $key ) )
{
if ( ref ($refHash->{"$key"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$key"} ;
#debug print " \n The key is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$key"}) eq 'ARRAY' )
{
#debug print " \n the key is an ARRAY " ;
printArrayValues ( $refHash->{"$key"} ) ;
}
} #eof if ( defined ( $key ))
if ( defined ( $value) )
{
if ( ref ($refHash->{"$value"}) eq 'HASH' )
{
my $newRefHash = $refHash->{"$value"} ;
#debug print " \n The value is a hash " ;
printHashKeyValues ($newRefHash , $key , $value) ;
}
if ( ref ($refHash->{"$value"}) eq 'ARRAY' )
{
#debug print " \n the value is an ARRAY " ;
printArrayValues ( $refHash->{"$value"} ) ;
}
} #eof if defined ( $value )
#debug print "\n key: $key, value: $value.\n";
} #eof while
} #eof sub
sub printArrayValues
{
my $arrRef = shift ;
my #array = #$arrRef;
my $parrentArrayElement = shift ;
#debug print "printArrayValues CALLED " ;
foreach my $arrayElement ( #array )
{
if (defined ( $arrayElement ) )
{
if ( ref ($arrayElement) eq 'HASH' )
{
#debug print " \n The \$arrayElement is a hash FROM THE ARRAY " ;
printHashKeyValues ($arrayElement ) ;
} #eof if
if ( ref ($arrayElement) eq 'ARRAY' )
{
#debug print " \n The \$arrayElement is a ARRAY FROM THE ARRAY " ;
printArrayValues ($arrayElement ) ;
} #eof if
#debug print "\n \$arrayElement is $arrayElement " ;
} #eof if ( defined ( $arrayElement ) )
} #eof foreach
} #eof sub
# #debug print output
##debug print Dumper($data);
1 ;