How can I work with multiple values using ->? - perl

I am new to Perl and made some changes to an existing script, but I am not sure if this a right usage in Perl. In C# we do things differently, so is the code sample below correct?
$group->{$type}{class} = 1;
The code I added is
$group->{$type}{class} = 1;
$group->{$name}{port} = 1;
Is this right? Can $group point to both type and name. I tried this with a sample Perl script and it seemed to set and return '1' correctly. But I am not sure if this is how I should do this.

Yes, that looks correct. You are building a complex data structure, specifically a hash of hashes (HoH). The group hash has two keys, $type and $name. The $type subhash has one key, class. The $name subhash has one key, port. It looks like this, roughly, if you dumped it or declared it all at once:
$group = {
$type => {
class => 1
},
$name => {
port => 1
}
}
Of course, $type and $name will evaluate to whatever they're set to. It won't store the reference in the hash.

Um ... this is perl. If it works, it's right. But, to your question. In your code $group is a reference to a hash (maybe called a dictionary in c#). I think you are probably looking for this:
my $group={}; # make the ref
my #types = ('hot','cold','warm'); # make some types
my #names = ('sink','bath','drain'); # and some names
foreach my $type (#types){
$group->{'type'}->{$type}++; # add a new $type to the "type" sub hash
}
foreach my $name (#names){
$group->{'name'}->{$name}++; # add a new $nameto the "name" sub hash
}
Now cycle through the types for example:
foreach my $typeKey (keys %{$group->{'type'}}){
print "type is " . $typeKey; # this is from the #types array
print ", value = " . $group->{'type'}->{$typeKey}; # this would be 1
}

Related

Accessing and displaying key-value pairs in Perl from subroutine

New to Perl. Got syntax errors in accessing key-value pairs from subroutine.
sub displayObj{
my %obj = shift; //the hash. It is a JSON object after decode_json()
my $field = shift; //the key we are searching. It is a string.
my $serialized = "{}"; //Initialization
foreach my $key (keys %obj){
if($key eq $field){
$serialized = $obj[{$field}];
}
}
print "$serialized\n";
}
It is giving me a compilation error in the if block.
I would like to know:
Should I use % or $ in my %obj?
How to access the key-value pair (also a JSON object) and assign it to $serialized using $obj?
I think you're trying to write a subroutine that displays the value of a hash element given its key
But you're missing the basic purpose of hashes: they're content-addressable. That means there's no need to iterate through all the keys of a hash comparing them to the string you're looking for, you can write just $hash{key} and the search will be done for you very quickly using a hashing system (hence the name of the data type). This is just the same as using $array[$i] to access an array element directly instead of looping over all of the indices of the array comparing each one to $i until you find the element you're looking for
If you're really dealing with objects then you shouldn't be accessing their internal data like this anyway. An object will have accessor methods that return the values you're supposed to be using; anything else is part of the internal working of the class and is meant to be private
The syntax error is in this line
$serialized = %obj[{$field}]
where it looks like you're trying to use both a hash key {...} and an array index [...]. That won't work
You don't show how you're calling your subroutine, but I imagine you're passing a reference to a hash, which is a scalar value and must be treated as such inside the subroutine
This program shows a working version of what I think you intended
use strict;
use warnings 'all';
my $obj = {
aa => 1,
cc => 2,
};
displayObj($obj, 'cc');
displayObj($obj, 'bb');
sub displayObj {
my $obj = shift; # The hash. It is a JSON object after decode_json()
my $field = shift; # The key we are searching. It is a string.
my $serialized = '{}'; # Initialization
foreach my $key ( keys %$obj ) {
if ( $key eq $field ) {
$serialized = $obj->{$field};
}
}
print "$serialized\n";
}
output
2
{}
But the loop can be removed altogether as I described, leaving
sub displayObj {
my ($obj, $field) = #_;
my $serialized = $obj->{$field} // '{}';
print "$serialized\n";
}
which produces exactly the same result. In fact there's little point in creating a subroutine to do this; you can write just
print $obj->{bb} // '{}', "\n"
with the same effect
I usually do this way:
sub displayObj{
my $objref = shift;
my %obj = %{$objref};
}

Not able to print value of this complex datastructure in perl

Question explained as comments in the code:
I have a following piece of code in which i am trying to make a hash in which the key itself is a reference to some other array.
my #arr1=(1,2,3,4,5,6,7,8,9,10);
my #arr2=(1001,1002,1003);
$FILES_1=\#arr1;
$num1=2;
$FILES_2=\#arr2;
$num2=4;
#FILES=($FILES_1, $FILES_2);
#NUMS=($num1,$num2);
fun (\#FILES,\#NUMS);
sub fun{
my ($rFILES,$rNUMS) = #_;
print "\n --${$rFILES}[0]->[2] \n "; # This is same as below
print "\n --$rFILES->[0]->[2] \n "; # This is same as above
my %hash=();
$hash{$rFILES->[0]} = $rNUMS->[0];
my $test = $rFILES->[0];
print "\nTEST : $test->[1]";
my #key = keys %hash;
print "\nKey 1 = $key[0]"; # This prints scalar value as below
print "\ntest = $test "; # This prints scalar value as above
print "\nKey 1->[1] = ${$key[0]}->[1]"; #PROBLEM : THIS DOESNT PRINT SAME AS BELOW
print "\ntest->[1] = $test->[1] "; #THIS PRINTS AS EXPECTED.
}
Output:
--3
--3
TEST : 2
Key 1 = ARRAY(0x1bbb540)
test = ARRAY(0x1bbb540)
Key 1->[1] =
test->[1] = 2
Are we not supposed to keep a key of a hash as reference to some array? Why is the value "2" not printed?
A hash key is always a string, you can't store a reference in there. I can't think of a reason you'd want this either.
You should really always use strict and use warnings. If you added those and declared your variables with my properly, you'd have gotten a warning:
Can't use string ("ARRAY(0x85e628)") as a SCALAR ref while "strict refs" in use
The syntax you're using:
${$key[0]}
says $key[0] is a reference to a scalar. It isn't, it's a string with the address of what the reference used to be, as you can't use a reference as a hash key they become strings.
Update:
You probably want something like this instead:
my #filegroups = (
{ number => 1, files => ['file1','file2'] },
{ number => 2, files => ['file3'] },
);
Accessed as:
foreach my $filegroup ( #$filegroups ) {
print "my number is $filegroup->{number}\n";
print " and I have ".scalar( #{ $filegroup->{ files } } )." files\n";
}
If you need extra speed to access the structure by group number (don't bother unless you have hundreds of groups or thousands and thousands of accesses), index them:
my %filegroupindexes = map { $_->{ number } => $_ } values #$filegroups;
Then you can get to the group like this:
print "there are ".scalar( #{ $filegroupindexes{ 1 }->{ files } } )." files in group 1\n";
As a last hint, for printing complex data structures Data::Printer is my favourite:
use Data::Printer colored => 1;
p( #filegroups );
You need Tie::RefHash if you want to use references as hash keys.

Search duplicates in Hash of Hash

i cant resolv this in my mind, its too much to me, perhaps someone can help me:
#Hosts = ("srv1","db1","srv2","db3","srv3","db3","srv3","db4","srv3","db5");
my $count = #Hosts;
$count= $count / 2;
my %hash;
$i = 0;
$ii = 1;
$j = 0;
$jj = 0;
while ($jj < $count) {
$hash{$j}{$Hosts[$i]} = $Hosts[$ii];
$i = $i + 2;
$ii = $ii +2;
$j++;
$jj++
}
print Dumper(\%hash);
Output:
$VAR1 = {
'4' => {
'srv4' => 'db3'
},
'1' => {
'srv2' => 'db3'
},
'3' => {
'srv3' => 'db3'
},
'0' => {
'srv1' => 'db1'
},
'2' => {
'srv3' => 'db3'
}
'5' => {
'srv3' => 'db5'
}
};
I Know this i ugly code, i dont know how to do that better, what i need to do is find douple servers and douple dbs, and put the positions and the string of the duplicates in an array ore something like that, i want to generate a Nagvis Map file out of that.
The Icinga Config file contains am Member string like this:
members srv1, db1, srv2, db3, srv3, db3, srv3, db3, srv4
It has pairs server, db, server, db, here is a sample of the Nagvis Config:
define host {
object_id=5e78fb
host_name=srv1
x=237
y=122
}
define service {
object_id=30646e
host_name=srv1
service_description=db1
x=52
y=122
}
define host {
object_id=021861
host_name=srv2
x=237
y=217
}
define service {
object_id=a5e725
host_name=srv1
service_description=db2
x=52
y=217
}
Thanks in advance
You need to clarify exactly what you want. It's very difficult to tell by your description.
And, your code is in very poor condition. Indenting loops and if statements like this:
while ($jj < $anzahl) {
$hash{$j}{$Hosts[$i]} = $Hosts[$ii];
$i = $i + 2;
$ii = $ii +2;
$j++;
$jj++
}
Makes your code much easier to understand. You also use generic names. What data is stored in #array? Is it a list of systems. Call it #systems. What is $i and $jj suppose to represent? What do you want $hash{$j}{$Hosts[$i]} to represent?
You should always, always, always add the following lines to the top of your program:
use strict;
use warnings;
If you use strict, you must declare all of your variables with my. This makes sure you don't do things like have #array in one place and #Hosts in another. These two lines will catch about 90% of your errors.
I don't know if you want a list of all the DB system that connect to the various servers or if you want a list of the various servers that connect to the DB systems. Therefore, I'll give you both.
I am guessing that your #array is a list of all of your machines and databases in one list:
use strict;
use warnings;
use feature qw(say); # Allows me to use "say" instead of "print"
use Data::Dumper;
my #systems = qw( # The qw(...) is like putting quotes around each word.
svr1 db1 # A nice way to define an array...
srv2 db3
srv3 db3
srv3 db4
srv3 db5
);
my %db_systems; # Database systems with their servers.
my %servers; # Servers with their database systems.
for (;;) { # Loop forever (until I say otherwise)
my $server = shift #systems;
#
# Let's check to make sure that there's a DB machine for this server
#
if ( not #systems ) {
die qq(Cannot get database for server "$server". Odd number of items in array);
}
my $database = shift #systems;
$servers{$server}->{$database} = 1;
$db_systems{$database}->{$server} = 1;
last if not #systems; # break out of loop if there are no more systems
}
say "Servers:" . Dumper \%servers;
say "Databases: " . Dumper \%db_systems;
This produces:
Servers:$VAR1 = {
'srv3' => {
'db4' => 1,
'db3' => 1,
'db5' => 1
},
'svr1' => {
'db1' => 1
},
'srv2' => {
'db3' => 1
}
};
Databases: $VAR1 = {
'db4' => {
'srv3' => 1
},
'db3' => {
'srv3' => 1,
'srv2' => 1
},
'db5' => {
'srv3' => 1
},
'db1' => {
'svr1' => 1
}
};
Is this close to what you want?
Addendum
Hi this is working!! Now i need to understand how to access the Values to print them in my file. This hash of hash thing is kind off ruff to mee. Thanks for that quick Help!
You need to read the Perl tutorial on References and the Perl Reference Page on References.
In Perl, all data is scalar which means that variables talk about single values. In other programming languages, you have structures or records, but not Perl.
Even arrays and hashes are nothing but collections of individual bits of data. What happens when you need something a bit more complex?
A reference is a memory location of another Perl data structure. You could have references to scalar variables like $foo, but that wouldn't do you much good in most circumstances. Where this is helpful is when you have a reference pointing to an array or a hash. This way, you could have much more complex structures that can be used to represent this data.
Imagine an array of ten items ($foo[0] to $foo[9]). Each entry in the array is pointing to another array of ten items. There are now 101 separate arrays being referenced here. We can treat them as a single structure, but it's important to remember that they are separate arrays.
I have a reference to an array at $foo[0]. How do I get access to the array itself? I do what is known as a dereference. To do that, I use curly braces with the right sigil in front. (The sigil is the $, #, or % you see in front of Perl variables:
$foo[0]; # Reference to an array
my #temp = #{ $foo[0] }; # Dereferencing.
my $temp[0]; # Now I can access that inner array
Having to use a temporary array each time I have to dereference it is rather clumsy, so I don't have to:
$foo[0]; # Reference to an array
my $value = ${ $foo[0] }[0]; # Getting the value of an item in my array reference
You can see that last is a bit hard to read. Imagine if I have a hash of a hash of an array of items:
my $phone = ${ ${ ${ $employee{$emp_number} }{phone} }[0] }{NUMBER};
It's a bit unwieldy. Fortunately, Perl allows you a few shortcuts. First, I can nest the references and use the default precedence:
my $phone = $employee{$emp_number}{phone}[0]{NUMBER};
I prefer using the -> notation:
my $phone = $employee{$emp_number}->{phone}->[0]->{NUMBER};
The arrow notion is cleaner because it separates the parts out, and it reminds you these are references!. and, not some complex structure data structure. This helps remind you when you have to do a dereference such as when you use the key, pop, or push commands:
for my $field ( keys %{ $employee } ) { # Dereference the hash
say "Field $field = " . $employee{$emp_number}->{$field}
if ( not ref $employee{$emp_number}->{$field} );
}
Look up the ref to see what it does and why I am only interested in printing out the field if ref returns an empty string.
By now, you should be able to see how to access your hash of hashes using the -> syntax:
my $db_for_server = $servers{$server}->{$database};
And you can use two loops:
for my $server ( keys %servers } {
my %db_systems = %{ $servers{$server} }; # Dereferencing
for my $db_system ( keys %db_systems } {
say "Server $server has a connection to $db_systems{$db_system}";
}
}
Or, without an intermediate hash...
for my $server { keys %servers } {
for my $db_system ( keys %{ $servers{$server} } ) {
say "Server $server has a connection to " . $servers{$server}->{$db_system};
}
}
Now, go out there and get a good book on Modern Perl. You need to learn good programming techniques like using good variable names, indenting, and using strict and warnings in order to help you write better programs that are easier to decipher and support.

Perl hash of hashes of hashes of hashes... is there an 'easy' way to get an element at the end of the list?

I have a Perl hash of hashes of ... around 11 or 12 elements deep. Please forgive me for not repeating the structure below!
Some of the levels have fixed labels, e.g. 'NAMES', 'AGES' or similar so accessing these levels are fine as I can use the labels directly, but I need to loop over the other variables which results in some very long statements. This is an example of half of one set of loops:
foreach my $person (sort keys %$people) {
foreach my $name (sort keys %{$people->{$person}{'NAMES'}}) {
foreach my $age (sort keys %{$people->{$person}{'NAMES'}{$name}{'AGES'}}) {
. . . # and so on until I get to the push #list,$element; part
This is just an example, but it follows the structure of the one I have. It might be shorter not to have the fixed name sections (elements in caps) but they are required for reference purposes else where.
I tried to cast the elements as hashes to shorten it at each stage,
e.g. for the second foreach I tried various forms of:
foreach my $name (sort keys %{$person->{'NAMES'}})
but this didn't work. I'm sure I've seen something similar before, so the semantics may be incorrect.
I've studied pages regarding Hash of Hashes and references to hashes and their elements and so on without luck. I've seen examples of while each loops but they don't seem to be particularly shorter or easier to implement. Maybe there is just a different method of doing this and I'm missing the point. I've written out the full set of foreach loops once and it would be great if I don't have to repeat it another six times or so.
Of course, there may be no 'easy' way, but all help appreciated!
$person is the key, to shorten things for the inner loops you need to assign the value to something:
foreach my $person_key (sort keys %$people) {
my $person = $people->{$person_key};
my $names = $person->{NAMES};
foreach my $name (sort keys %$names) {
Also you can work with each keyword. This definetly should help.
while( my ($person, $val1) = each(%$people) ) {
while( my ($name, $val2) = each(%$val1) ) {
while( my ($age, $val3) = each(%$val2) ) {
print $val3->{Somekey};
You could use Data::Walk, which is kind of File::Find for data structures.
If you want to build a somewhat more flexible solution, you could traverse the data tree recursively. Consider this example data tree (arbitrary depth):
Example data
my %people = (
memowe => {
NAMES => {
memo => {AGE => 666},
we => {AGE => 667},
},
},
bladepanthera => {
NAMES => {
blade => {AGE => 42},
panthera => {AGE => 17},
},
},
);
From your question I concluded you just want to work on the leaves (AGEs in this case). So one could write a recursive traverse subroutine that executes a given subref on all leaves it could possibly find in key-sorted depth-first order. This subref gets the leave itself and a path of hash keys for convenience:
Preparations
sub traverse (&$#) {
my ($do_it, $data, #path) = #_;
# iterate
foreach my $key (sort keys %$data) {
# handle sub-tree
if (ref($data->{$key}) eq 'HASH') {
traverse($do_it, $data->{$key}, #path, $key);
next;
}
# handle leave
$do_it->($data->{$key}, #path, $key);
}
}
I think it's pretty clear how this guy works from the inlined comments. It would be no big change to execute the coderef on all nodes and not the leaves only, if you wanted. Note that I exceptionally added a prototype here for convenience because it's pretty easy to use traverse with the well-known map or grep syntax:
Executing stuff on your data
traverse { say shift . " (#_)" } \%people;
Also note that it works on hash references and we initialized the #path with an implicit empty list.
Output:
42 (bladepanthera NAMES blade AGE)
17 (bladepanthera NAMES panthera AGE)
666 (memowe NAMES memo AGE)
667 (memowe NAMES we AGE)
The given subroutine (written as a { block }) could do anything with the given data. For example this more readable push subroutine:
my #flattened_people = ();
traverse {
my ($thing, #path) = #_;
push #flattened_people, { age => $thing, path => \#path };
} \%people;

Is this the correct way to build a Perl hash that utilizes arrays?

This is the first time I have manipulated hashes and arrays in this way -- and it is working. Basically, for every key there are multiple values that I want to record and then print out in the form "key --> value --> value --> val..."
My code is as follows. I am surprised that it works, so concerned that it works "by mistake". Is this the correct way to accomplish this task, or is there a more efficient or appropriate method?
while ($source =~ m/(regex)/g) { #Get all key names from source
$listkey = $1; #Set current list key to the current regex result.
$list{$listkey} = ++$i unless $list{$listkey}; #Add the key to the hash unless it already exists.
$list{$listkey} = [] unless exists $list{$listkey}; #Add an array for the hash unless the hash already exists.
while ($loopcount==0) {
if ($ifcount==0) {
$listvalue=result_of_some_function_using_list_key; #Get the first list value by using the list key.
$ifcount++; #Increment so we only get the first list value once.
} else {
$listvalue=result_of_some_function_using_list_value; #Update the list value by using the last list value.
}
if ($listvalue) { #If the function returned a value...
push #{$list{$listkey}}, $listvalue; #...then add the value to the hash array for the key.
} else { #There are no more values and we need a new key.
$listkey=0; #Reset variable.
$listvalue=0; #Reset variable.
$loopcount++; #Increment loop counter to exit loop.
}
}
$ifcount=0; #Reset count variable so the next listvalue can be generated from the new key.
$loopcount=0; #Reset count variable so another loop can begin for a new key.
}
foreach $listkey (keys %list) { #For each key in the hash.
print "$listkey --> "; #Print the key.
#values = #{$list{$listkey}}; #Reference the arrays of the hash.
print join ' --> ', #values; #Print the values.
print "\n"; #Print new line.
}
The following code does the same as your code, without the unnecessary steps.
while ($source =~ m/(regex)/g) { # Get all key names from source
$listkey = $1; # Grab current regex result.
$listvalue = result_of_some_function_using_list_key;
while ($listvalue) {
push #{$list{$listkey}}, $listvalue;
$listvalue = result_of_some_function_using_list_value;
}
$listkey = 0; # Reset variable.
$domain = 0; # Reset variable.
}
However, as others have commented, global variables should be avoided in most cases. Instead, the list key and list value should be lexically scoped with my(), and the functions for generating list values should take one or more parameters (domain, list key and/or list value) as input.
The lines
$list{$listkey} = ++$i unless $list{$listkey};
$list{$listkey} = [] unless exists $list{$listkey};
in your original code aren't needed, it is sufficient with push #{ $list{$key} }, $value to initialize an entry.
The code above has many unnecessary steps. Perl is a very expressive language, and allows logic like this to be expressed very simply:
# uncomment for some sample data
# sub function {"#_" !~ /^\[{3}/ and "[#_]"}
# my $source = 'one two three';
my %list;
while ($source =~ m/(\S+)/g) {
my $key = $1;
my $value = function($key);
while ($value) {
push #{ $list{$key} }, $value;
$value = function($value)
}
}
for my $key (keys %list) {
print join(' --> ' => $key, #{$list{$key}}), "\n";
}
Nope! If this works, it's definitely "by mistake". But it's also obvious that this isn't your real code and that you added several more mistakes in "translating" it to an example, so it's hard to judge exactly what the intent was, but going from the skeleton of your program, it looks like it should be something like:
my %result;
while ($source =~ m/(regex)/g) {
my $key = $1;
my $value = mangle($key);
while ($value) {
push #{ $results{$key} }, $value;
$value = frob($value);
}
}
and no more. Your attempts to initialize the hash aren't doing what you think they are (and aren't necessary), your while loop as written isn't a good idea at all, and neither are all the global variables.