How can I create a hash of hashes in Perl? - perl

I am new to Perl. I need to define a data structure in Perl that looks like this:
city 1 -> street 1 - [ name , no of house , senior people ]
street 2 - [ name , no of house , senior people ]
city 2 -> street 1 - [ name , no of house , senior people ]
street 2 - [ name , no of house , senior people ]
How can I acheive this?

Here is an another example using a hash reference:
my $data = {
city1 => {
street1 => ['name', 'house no', 'senior people'],
street2 => ['name','house no','senior people'],
},
city2 => {
street1 => etc...
...
}
};
You then can access the data the following way:
$data->{'city1'}{'street1'}[0];
Or:
my #street_data = #{$data->{'city1'}{'street1'}};
print #street_data;

I found the answer like
my %city ;
$city{$c_name}{$street} = [ $name , $no_house , $senior];
i can generate in this way

The Perl Data Structures Cookbook, perldsc, may be able to help. It has examples showing you how to create common data structures.

my %city ;
If you want to push
push( #{ city{ $c_name } { $street } }, [ $name , $no_house , $senior] );
(0r)
push #{ city{ $c_name } { $street } }, [ $name , $no_house , $senior];

You may read my short tutorial at this answer. In short you may put reference to hash into the value.
%hash = ( name => 'value' );
%hash_of_hash = ( name => \%hash );
#OR
$hash_of_hash{ name } = \%hash;
# NOTICE: {} for hash, and [] for array
%hash2 = ( of_hash => { of_array => [1,2,3] } );
# ---^ ---^
$hash2{ of_hash }{ of_array }[ 2 ]; # value is '3'
# ^-- lack of -> because declared by % and ()
# same but with hash reference
# NOTICE: { } when declare
# NOTICE: -> when access
$hash_ref = { of_hash => { of_array => [1,2,3] } };
# ---^
$hash_ref->{ of_hash }{ of_array }[ 2 ]; # value is '3'
# ---^

Related

Perl Issue trying to add static text by IP address

I have a script that has an exact count of IP address' being compared to an expected count of IP address going though a specific port.
Code:
my %minimum = (
'10.10.10.10' => 2,
'10.10.10.11' => 3,
'10.10.10.12' => 6,
'10.10.10.13' => 7,
);
my %count;
open my $fh, '-|', 'netstat -an |grep 1111 ' or die "could not run netstat: $!";
while(<$fh>) {
next unless /^\s*............(regex) /;
$count{$1}++;
}
close $fh;
while(my ($ip, $expected) = each %minimum) {
$count{$ip} ||= 0;
next if $count{$ip} == $expected && print color("green"), "$ip: OK! Expected = $expected count = $count{$ip}\n", color("reset");
print color("red"), "$ip: BAD! Expected = $expected count = $count{$ip}\n", color("reset");
}
I'm trying to add a static hostname. Currently an example output looks like:
10.10.10.10: OK! Expected = 2 Actual = 2
10.10.10.11: OK! Expected = 3 Actual = 3
10.10.10.12: OK! Expected = 6 Actual = 6
10.10.10.13: BAD! Expected = 7 Actual = 5
But I want to include a static hostname to look like below:
10.10.10.10: aaaa#aa.com OK! Expected = 2 Actual = 2
10.10.10.11: bbb#aa.com OK! Expected = 3 Actual = 3
10.10.10.12: ccc#aa.com OK! Expected = 6 Actual = 6
10.10.10.13: ddd#aa.com BAD! Expected = 7 Actual = 5
Thank you all for any recommendations/tips.
You could create a second hash to keep the info, keyed on the ip addresses, or you could create a nested data structure like so:
my %minimum = (
'10.10.10.10' => { label => 'hotdog', count => 2 },
'10.10.10.11' => { label => 'burger', count => 3 },
'10.10.10.12' => { label => 'steak', count => 6 },
'10.10.10.13' => { label => 'pizza', count => 7 },
);
and then later, when you need the info, you can retrieve it:
while(my ($ip, $data) = each %minimum) {
$count{$ip} ||= 0;
my $label = $data->{label};
my $expected = $data->{count};
# ... rest of code here ...
}
my %notes = (
'10.10.10.10' => 'cheeseburger',
'10.10.10.11' => 'hotdog',
'10.10.10.12' => '...',
'10.10.10.13' => '...',
);
s{^([^:]*):\K}{ defined($notes{$1}) ? " $notes{$1}" : "" }e;

incorporating local variables in a global hash in perl

I need to incorporate a generic hash (macro) in multiple user hashes. In actuality these are all specifications written as HoH/HoL in perl.
I would like the 'user' specs to adopt the macro specs with their own modifications. In example below, the variable '$v_Y' needs to have different values in user1 and user2.
What I have below is not exactly code, but an attempt to illustrate the problem. I am unable to have multiple values of $v_Y since macro_spec is already created.
## this is in a package
my $MACRO_SPEC = {
mkeyX => "value_X",
mkeyY => $v_Y,
};
#this is USER1 package,
$v_Y = "U1_VALUE_X";
# use MACRO_SPEC
my $USER1 = (
u1key1 => "u1value1", u1macrokey => $MACRO_SPEC, # need macro to interpolate 'local' $v_Y
);
#this is USER2 package,
$v_Y = "U2_VALUE_X";
# use MACRO_SPEC
my $USER2 = (
u2key1 => "u2value1",
u2macrokey => $MACRO_SPEC, # need macro to interpolate 'local' $v_Y
);
#this is how USER1 should look after the interpolation
my $USER1 = (
u1key1 => "u1value1",
u1macrokey => {
mkeyX => "value_X",
mkeyY => "U1_VALUE_X"
},
);
#this is how USER2 should look after the interpolation
my $USER2 = (
u2key1 => "u2value1",
u1macrokey => {
mkeyX => "value_X",
mkeyY => "U2_VALUE_X"
},
);
Like melpomene suggested, you want $MACRO_SPEC to be a function that can generate something different each time it is called.
package One;
our $v_Y;
my $MACRO_SPEC = sub { +{ mkeyX => "value_X", mkeyY => $v_Y } };
...
package USER1;
$One::v_Y = "U1_VALUE_X";
my $user1 = { # { }, not ( ), to define a hash reference
u1key1 => "u1value1",
u1macrokey => $MACRO_SPEC->(), # $f->() to exec code defined in $f
...
};

How do I loop over a hash of hashes in perl and access the data contained?

I have a list
my %SERVICES = (
name=>
{
description => 'Item 1',
service_codes => [ 'item1' ],
},
name2=>
{
description => 'Item 2',
service_codes => [ 'item2' ],
},
);
what I need to do is reference the description and print it to a variable called $service_name so when referenced the text will read Item 1 or Item 2.
I am VERY new at perl and am trying to learn this on my own the best as I can. Any help would be appreciated, please use small words. :)
Easy enough, try looping over the data structure
while ( my ($k, $data) = each %SERVICES ) {
my $service_name = $data->{description};
say $service_name;
}
Program:
foreach my $id (keys %SERVICES)
{
my $des = $SERVICES{$id}{'description'};
print "$des\n";
}

how to query eXist using XPath?

I decided to use eXist as a database for an application that I am writing in Perl and
I am experimenting with it. The problem is that I have stored a .xml document with the following structure
<foo-bar00>
<perfdata datum="GigabitEthernet3_0_18">
<cli cmd="whatsup" detail="GigabitEthernet3/0/18" find="" given="">
<input_rate>3</input_rate>
<output_rate>3</output_rate>
</cli>
</perfdata>
<timeline>2011-5-23T11:15:33</timeline>
</foo-bar00>
and it is located in the "/db/LAB/foo-bar00/2011/5/23/11_15_33.xml" collection.
I can successfully query it, like
my $xquery = 'doc("/db/LAB/foo-bar00/2011/5/23/11_15_33.xml")' ;
or $xquery can be equal to
= doc("/db/LAB/foo-bar00/2011/5/23/11_15_33.xml")/foo-bar00/perfdata/cli/data(output_rate)
or
= doc("/db/LAB/foo-bar00/2011/5/23/11_15_33.xml")/foo-bar00/data(timeline)
my ($rc1, $set) = $eXist->executeQuery($xquery) ;
my ($rc2, $count) = $eXist->numberOfResults($set) ;
my ($rc3, #data) = $eXist->retrieveResults($set) ;
$eXist->releaseResultSet($set) ;
print Dumper(#data) ;
And the result is :
$VAR1 = {
'hitCount' => 1,
'foo-bar00' => {
'perfdata' => {
'cli' => {
'given' => '',
'detail' => 'GigabitEthernet3/0/18',
'input_rate' => '3',
'cmd' => 'whatsup',
'output_rate' => '3',
'find' => ''
},
'datum' => 'GigabitEthernet3_0_18'
},
'timeline' => '2011-5-23T11:15:33'
}
};
---> Given that I know the xml document that I want to retrieve info from.
---> Given that I want to retrieve the timeline information.
When I am writing :
my $db_xml_doc = "/db/LAB/foo-bar00/2011/5/23/11_15_33.xml" ;
my ($db_rc, $db_datum) = $eXist->queryXPath("/foo-bar00/timeline", $db_xml_doc, "") ;
print Dumper($db_datum) ;
The result is :
$VAR1 = {
'hash' => 1717362942,
'id' => 3,
'results' => [
{
'node_id' => '1.2',
'document' => '/db/LAB/foo-bar00/2011/5/23/11_15_33.xml'
}
]
};
The question is : How can I retrieve the "timeline" info ? Seems that the "node_id" variable (=1.2) can points to the "timeline" info, but how can I use it ?
Thank you.
use XML::LibXML qw( );
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file('a.xml');
my $root = $doc->documentElement();
my ($timeline) = $root->findnodes('timeline');
if ($timeline) {
print("Exists: ", $timeline->textContent(), "\n");
}
or
my ($timeline) = $root->findnodes('timeline/text()');
if ($timeline) {
print("Exists: ", $timeline->getValue(), "\n");
}
I could have used /foo-bar00/timeline instead of timeline, but I didn't see the need.
Don't know if you're still interested, but you could either retrieve the doc as DOM and apply an xquery to the DOM, or, probably better, only pull out the info you want in the query that you submit to the server.
Something like this:
for $p in doc("/db/LAB/foo-bar00/2011/5/23/11_15_33.xml")//output_rate
return
<vlaue>$p</value>

How can I create a hash of hashes from an array of hashes in Perl?

I have an array of hashes, all with the same set of keys, e.g.:
my $aoa= [
{NAME=>'Dave', AGE=>12, SEX=>'M', ID=>123456, NATIONALITY=>'Swedish'},
{NAME=>'Susan', AGE=>36, SEX=>'F', ID=>543210, NATIONALITY=>'Swedish'},
{NAME=>'Bart', AGE=>120, SEX=>'M', ID=>987654, NATIONALITY=>'British'},
]
I would like to write a subroutine that will convert this into a hash of hashes using a given key hierarchy:
my $key_hierarchy_a = ['SEX', 'NATIONALITY'];
aoh_to_hoh ($aoa, $key_hierarchy_a) = #_;
...
}
will return
{M=>
{Swedish=>{{NAME=>'Dave', AGE=>12, ID=>123456}},
British=>{{NAME=>'Bart', AGE=>120, ID=>987654}}},
F=>
{Swedish=>{{NAME=>'Susan', AGE=>36, ID=>543210}}
}
Note this not only creates the correct key hierarchy but also remove the now redundant keys.
I'm getting stuck at the point where I need to create the new, most inner hash in its correct hierarchical location.
The problem is I don't know the "depth" (i.e. the number of keys). If I has a constant number, I could do something like:
%h{$inner_hash{$PRIMARY_KEY}}{$inner_hash{$SECONDARY_KEY}}{...} = filter_copy($inner_hash,[$PRIMARY_KEY,$SECONDARY_KEY])
so perhaps I can write a loop that will add one level at a time, remove that key from the hash, than add the remaining hash to the "current" location, but it's a bit cumbersome and also I'm not sure how to keep a 'location' in a hash of hashes...
use Data::Dumper;
my $aoa= [
{NAME=>'Dave', AGE=>12, SEX=>'M', ID=>123456, NATIONALITY=>'Swedish'},
{NAME=>'Susan', AGE=>36, SEX=>'F', ID=>543210, NATIONALITY=>'Swedish'},
{NAME=>'Bart', AGE=>120, SEX=>'M', ID=>987654, NATIONALITY=>'British'},
];
sub aoh_to_hoh {
my ($aoa, $key_hierarchy_a) = #_;
my $result = {};
my $last_key = $key_hierarchy_a->[-1];
foreach my $orig_element (#$aoa) {
my $cur = $result;
# song and dance to clone an element
my %element = %$orig_element;
foreach my $key (#$key_hierarchy_a) {
my $value = delete $element{$key};
if ($key eq $last_key) {
$cur->{$value} ||= [];
push #{$cur->{$value}}, \%element;
} else {
$cur->{$value} ||= {};
$cur = $cur->{$value};
}
}
}
return $result;
}
my $key_hierarchy_a = ['SEX', 'NATIONALITY'];
print Dumper(aoh_to_hoh($aoa, $key_hierarchy_a));
As per #FM's comment, you really want an extra array level in there.
The output:
$VAR1 = {
'F' => {
'Swedish' => [
{
'ID' => 543210,
'NAME' => 'Susan',
'AGE' => 36
}
]
},
'M' => {
'British' => [
{
'ID' => 987654,
'NAME' => 'Bart',
'AGE' => 120
}
],
'Swedish' => [
{
'ID' => 123456,
'NAME' => 'Dave',
'AGE' => 12
}
]
}
};
EDIT: Oh, BTW - if anyone knows how to elegantly clone contents of a reference, please teach. Thanks!
EDIT EDIT: #FM helped. All better now :D
As you've experienced, writing code to create hash structures of arbitrary depth is a bit tricky. And the code to access such structures is equally tricky. Which makes one wonder: Do you really want to do this?
A simpler approach might be to put the original information in a database. As long as the keys you care about are indexed, the DB engine will be able to retrieve rows of interest very quickly: Give me all persons where SEX = female and NATIONALITY = Swedish. Now that sounds promising!
You might also find this loosely related question of interest.