I am trying to merge two hashes. Well, I am able to merge, but the output is not the way I want it to be:
Here is my code:
my %friend_list = (
Raj => "Good friend",
Rohit => "new Friend",
Sumit => "Best Friend",
Rohini => "Fiend",
Allahabad => "UttarPradesh",
);
my %city = (
Bangalore => "Karnataka",
Indore => "MadhyaPradesh",
Pune => "Maharashtra",
Allahabad => "UP",
);
my %friends_place = ();
my ($k, $v);
foreach my $ref (\%friend_list, \%city) {
while (($k,$v) = each (%$ref)) {
if (exists $ref{$k}) {
print"Warning: Key is all ready there\n";
next;
}
$friends_place{$k} = $v;
}
}
while (($k,$v) = each (%friends_place)) {
print "$k = $v \n";
}
From this o/p is
Raj=Good friend
Indore=MadhyaPradesh
Rohit=new Fiend
Bangalore=Karnataka
Allahabad=UttarPradesh
Sumit=Best Friend
Pune=Maharashtra
Rohini =Fiend
But I want to print %friend_list first followed by %city.
Another thing which I was trying to do is, if there is any duplicate key, then it should give me a warning message. But it is not giving me any message. As we can see here, we have Allahabad in both hash.
Thanks
Try with:
my %firend_list = (
Raj => "Good friend",
Rohit => "new Fiend",
Sumit => "Best Friend",
Rohini => "Fiend",
Allahabad => "UttarPradesh",
);
my %city = (
Bangalore => "Karnataka",
Indore => "MadhyaPradesh",
Pune => "Maharashtra",
Allahabad => "UP",
);
#merging
my %friends_place = ( %friend_list, %city );
And, for warnings:
foreach my $friend( keys %friend_list ){
print"Warning: Key is all ready there\n" if $friend ~~ [ keys %city ];
}
The line if (exists $ref{$k}) { is wrong and you can see it if you're putting use strict; use warnings; at the begining of the script.
Moreover this line should be if (exists $friends_place{$k}) { to produce the message about duplicate keys.
As hashes are unordered, you need to use an array to store the ordering:
my %friends_place = (%firend_list, %city);
my #friends_place_keyorder = ((keys %firend_list), (keys %city));
if ((scalar keys %friends_place) != (scalar #friends_place_keyorder)) {
print 'duplicate key found';
}
foreach (#friends_place_keyorder) {
print "$_ = $friends_place{$_}\n";
}
EDIT: my original solution in python, left here for historical purpose:
As hashes are unordered, you need to use an array to store the ordering. I don't know perl, so the following code is python (should be fairly straightforward to translate to perl):
friend_list = ...
city = ...
friends_place = dict(friend_list.items() + city.items())
friends_place_keyorder = friend_list.keys() + city.keys()
# detect duplicate keys by checking their lengths
# if there is duplicate then the hash would be smaller than the list
if len(friends_place) != len(friends_place_keyorder):
print "duplicate key found"
# iterate through the list of keys instead of the hashes directly
for k in friends_place_keyorder:
print k, friends_place[k]
Related
I'm stuck on a problem. I'm trying to make references for 2 hashes, then compare them in a subroutine. However, there is an error:
Can't use an undefined value as a HASH reference at compareHashes.pl line 10.
My code is:
use strict;
use warnings;
use feature qw(say);
my $hash_1; my $hash_2;
compareHashes($hash_1, $hash_2);
sub compareHashes{
say "the first hash:", "\n", %$hash_1;
say "the second hash:", "\n", %$hash_2;
if ((keys(%$hash_1) eq keys(%$hash_2)) and (values(%$hash_1) eq values(%$hash_2))){
say "Two above hashes are equal";}
else {
say "Two above hashes are not equal";
}
};
my %hash1 = (ITALY => "ROME",
FRANCE => "PARIS"
);
my %hash2 = ( ITALY => "MILAN",
FRANCE => "PARIS"
);
my %hash3 = (ITALY => "ROME" );
my %hash4 = (SPAIN => "ROME",
FRANCE => "PARIS"
);
my $hash1_r = \%hash1;
my $hash2_r = \%hash2;
my $hash3_r = \%hash3;
my $hash4_r = \%hash4;
compareHashes ($hash1_r, $hash1_r);
compareHashes ($hash1_r, $hash2_r);
compareHashes ($hash1_r, $hash3_r);
compareHashes ($hash1_r, $hash4_r);
Please tell me what is wrong. I appreciate your help.
You never assigned to $hash_1 and $hash_2!
my ($hash_1, $hash_2) = #_;
You have more problems than that, however, both keys and values in scalar context return the number of elements in a hash, so you are simply checking if both hashes are of the same size!
sub are_hashes_equal {
my ($hash1, $hash2) = #_;
{
my %set;
++$set{$_} for keys(%$hash1);
--$set{$_} for keys(%$hash2);
return 0 if grep { $_ } values(%set);
}
for my $key (keys(%$hash1)) {
return 0 if $hash1->{$key} ne $hash2->{$key};
}
return 1;
}
I have a config.file to do some tests and i would like to get some values from this one also.
Here my config.file:
my $folder = 'E:\FOLDER\Test\WEB';
{
license => [ 'kit-licence.zip',
'kit-work.zip'
],
programs => [
#template society =>\%program_work
'VIKTOR DESCRIPTION PRODUCT' => {
name => 'VIKTOR ',
parameters => [
Count_id => '06 (Viktor)',
Birth_date => '1995-04-30',
Marriage_date => '2014-05-26',
Divorce_date => '2015-03-30',
Activities_folder => $folder.'\VIKTOR\independent worker',
Activities_format => 'Enterprise Format (V35)',
Description_File_from => $folder.'\VIKTOR\FILE\description.xlm',
]
},
'OLIVER NEW OBJECT' => {
name => 'OLIVER ',
parameters => [
Count_id => '06 (oliver)',
Birth_date => '1990-04-30',
Marriage_date => '2011-03-26',
Divorce_date => '2014-01-30',
Activities_folder => $folder.'\OLIVER\independent worker',
Activities_format => 'Enterprise Format (V35)',
Description_File_from => $folder.'\OLIVER\FILE\description.xlm',
]
},
]
};
My file test is following:
#test.pl
use Modern::Perl;
my $config = do 'work.conf';
use Data::Dumper;
say Dumper( $config );
To get parameters in Programs for Viktor for example, I can do this:
%programs = #{ $config->{programs} };
for my $prog (values %programs) {
my %param = #{ $prog->{parameters} };
for my $name (sort keys %param){
print $name, ': ', $param{$name},"\n";
}
}
But in my case, I want to be able to get parameters for every user. Here it's just for Viktor. I would like to get them for Oliver or for another user. For that, and to differentiate all users, I have to use the "template society" which is the name to differentiate every user. For example, for Viktor, it's: "VIKTOR DESCRIPTION PRODUCT". For Oliver: "OLIVER NEW OBJECT".
How can I do that?
Same thing for "License":
license => [ 'kit-licence.zip',
'kit-work.zip'
],
programs => [..
I would like to get the license by name of each one. For example, 'kit-license.zip'.
And not by "hard coding" like that:
use File::Spec::Functions qw/catfile/;
my $filename = catfile($::svn, ${$config->{license}}[0]);
my $filename1 = catfile($::svn, ${$config->{license}}[1]);
Perhaps in a loop, but I didn't find.
PS: Don't ask me why they are all divorced. I really don't know.
You're already doing a good job converting those array refs into hashes. But the values is making your life hard. You need the key and the value at the same time. You can use each to do that.
my %programs = #{ $config->{programs} };
while (my ($template_society, $value) = each %programs ) {
my %param = #{ $value->{parameters} };
print "$template_society\n";
for my $name ( sort keys %param ) {
print "\t", $name, ': ', $param{$name}, "\n";
}
}
This will produce the following output:
VIKTOR DESCRIPTION PRODUCT
Activities_folder: \VIKTOR\independent worker
Activities_format: Enterprise Format (V35)
Birth_date: 1995-04-30
Count_id: 06 (Viktor)
Description_File_from: \VIKTOR\FILE\description.xlm
Divorce_date: 2015-03-30
Marriage_date: 2014-05-26
OLIVER NEW OBJECT
Activities_folder: \OLIVER\independent worker
Activities_format: Enterprise Format (V35)
Birth_date: 1990-04-30
Count_id: 06 (oliver)
Description_File_from: \OLIVER\FILE\description.xlm
Divorce_date: 2014-01-30
Marriage_date: 2011-03-26
The each built-in returns both the key and the value of a hash per iteration, and undef once it's done. That's why you need to put it in a while loop.
If you don't like the each approach, you can also use keys instead of values to get the keys ($template_society) and use that to look up the appropriate value.
my %programs = #{ $config->{programs} };
foreach my $template_society (keys %programs ) {
my %param = #{ $programs{$template_society}->{parameters} };
print "$template_society\n";
for my $name ( sort keys %param ) {
print "\t", $name, ': ', $param{$name}, "\n";
}
}
This will give you the same output.
To get all your licence paths you need to store them in an array and use a loop to process your array ref into that array. The easiest and most concise way to do that is using map.
my #licences = map { catfile($::svn, $_ ) } #{ $config->{license} };
It's like a foreach loop, just shorter. The BLOCK is basically a function that gets the current iteration item in $_. It's essentially the same as the following, just more perlish.
my #licences;
foreach my $licence (#{ $config->{license} } ) {
push #licences, catfile($::svn, $licence );
}
Do not attempt to create variables like $foo1, $foo2 and so on dynamically. That will not work. See this1 for an explanation why.
Finally a word on $::svn: if you are in a package, you should put your code into a function and accept $svn as an argument. Working with globals or package variables from different packages is tricky and messy and you will at some point shoot yourself in the foot with it.
1: The normal document is currently broken, so I used archive.org to get it
I have a data structure which looks like this:
my %hoh = (
'T431567' => {
machin => '01',
bidule => '02',
truc => '03',
},
'T123456' => {
machin => '97',
bidule => '99',
truc => '69',
},
'T444444' => {
machin => '12',
bidule => '64',
truc => '78',
},
);
I want to search the various values of truc for a particular value and find the top-level attribute which corresponds to that entry. For example, looking for a value of 78, I want to find the result 'T444444', because $hoh{T444444}{truc} is 78.
How can I do this, please?
You can do this with grep:
my #keys = grep { $hoh{$_}{truc} == 78 } keys %hoh;
Note that this can return more than one key, if there are duplicate values in the hash. Also note that this is not particularly efficient, since it has to search the entire hash. In most cases it's probably fine, but if the hash can be very large and you may need to run lots of such queries against it, it may be more efficient to build a reverse index as suggested by Sobrique:
my %trucs;
foreach my $part (keys %hoh) {
my $val = $hoh{$part}{truc};
push #{ $trucs{$val} }, $part;
}
my #keys = #{ $trucs{78} };
or, more generally:
my %index;
foreach my $part (keys %hoh) {
my %data = %{ $hoh{$part} };
foreach my $key (keys %data) {
my $val = $data{$key};
push #{ $index{$key}{$val} }, $part;
}
}
my #keys = #{ $index{truc}{78} };
Can't with that data structure as is - There is no 'backwards' relationship from value to key without you creating it.
You've two options - run a search, or create an 'index'. Practically speaking, these are the same, just one saves the results.
my %index;
foreach my $key ( keys %hoh ) {
my $truc = $hoh{$key}{'truc'};
$index{$truc} = $key;
}
Note - won't do anything clever if the 'truc' numbers are duplicated - it'll overwrite. (Handling this is left as an exercise to the reader).
This solution is similar to those already posted, but it uses the each operator to process the original hash in fewer lines of code, and probably more quickly.
I have added the dump output only so that you can see the form of the structure that is built.
use strict;
use warnings;
my %hoh = (
T123456 => { bidule => '99', machin => '97', truc => '69' },
T431567 => { bidule => '02', machin => '01', truc => '03' },
T444444 => { bidule => '64', machin => '12', truc => '78' },
);
my %trucs;
while ( my ($key, $val) = each %hoh ) {
next unless defined( my $truc = $val->{truc} );
push #{ $trucs{$truc} }, $key ;
}
use Data::Dump;
dd \%trucs;
print "\n";
print "$_\n" for #{ $trucs{78} };
output
{ "03" => ["T431567"], "69" => ["T123456"], "78" => ["T444444"] }
T444444
If you can guarantee that the answer is unique, i.e. that there is never more than one element of the original hash that has a given value for the truc entry, or you are interested only in the last one found, then you can write this still more neatly
my %trucs;
while ( my ($key, $val) = each %hoh ) {
next unless defined( my $truc = $val->{truc} );
$trucs{$truc} = $key ;
}
print $trucs{78}, "\n";
output
T444444
Simplest of all, if there is always a truc entry in each second-level hash, and its values is guaranteed to be unique, then this will do the job
my %trucs = map { $hoh{$_}{truc} => $_ } keys %hoh;
print $trucs{78}, "\n";
with the output as above.
I have a data structure like this:
#!/usr/bin/perl -w
my $hash = {
'abTcells' => {
'mesenteric_lymph_node' => {
'Itm2a' => '664.661',
'Gm16452' => '18.1425',
'Sergef' => '142.8205'
},
'spleen' => {
'Itm2a' => '58.07155',
'Dhx9' => '815.2795',
'Ssu72' => '292.889'
}
}
};
What I want to do is to print it out into this format:
mesenteric_lymph_node spleen
Itm2a 664.661 58.07155
Gm16452 18.1425 NA
Sergef 142.8205 NA
Dhx9 NA 815.2795
Ssu72 NA 292.889
What's the way to do it.
I'm currently stuck with the following code https://eval.in/44207
foreach my $ct (keys %{$hash}) {
print "$ct\n\n";
my %hash2 = %{$hash->{$ct}};
foreach my $ts (keys %hash2) {
print "$ts\n";
my %hash3 = %{$hash2{$ts}};
foreach my $gn (keys %hash3) {
print "$gn $hash3{$gn}\n";
}
}
}
Use Text::Table for output. Beautify to taste.
#!/usr/bin/env perl
use strict;
use warnings;
use Text::Table;
my $hash = {
'abTcells' => {
'mesenteric_lymph_node' => {
'Itm2a' => '664.661',
'Gm16452' => '18.1425',
'Sergef' => '142.8205'
},
'spleen' => {
'Itm2a' => '58.07155',
'Dhx9' => '815.2795',
'Ssu72' => '292.889'
}
}
};
my $struct = $hash->{abTcells};
my #cols = sort keys %{ $struct };
my #rows = sort keys %{ { map {
my $x = $_;
map { $_ => undef }
keys %{ $struct->{$x} }
} #cols } };
my $tb = Text::Table->new('', #cols);
for my $r (#rows) {
$tb->add($r, map $struct->{$_}{$r} // 'NA', #cols);
}
print $tb;
Output:
mesenteric_lymph_node spleen
Dhx9 NA 815.2795
Gm16452 18.1425 NA
Itm2a 664.661 58.07155
Sergef 142.8205 NA
Ssu72 NA 292.889
Now, the order of the rows above is different than the one you show because I wanted it to be consistent. If you know the set of all possible rows, then you can specify another order obviously.
First thing would be to separate out the two hashes:
my %lymph_node = %{ $hash->{abTcells}->{mesenteric_lymph_node} };
my %spleen = %{ $hash->{abTcells}->{spleen} };
Now, you have two separate hashes that contains the data you want.
What we need is a list of all the keys. Let's make a third hash that contains your keys.
my %keys;
map { $keys{$_} = 1; } keys %lymph_node, keys %spleen;
Now, we can go through all your keys and print the value for each of the two hashes. If one of the hashes doesn't have the data, we'll set it to NA:
for my $value ( sort keys %keys ) {
my $spleen_value;
my $lymph_nodes_value;
$spleen_value = exists $spleen{$value} ? $spleen{$value} : "NA";
$lymph_node_value = exists $lymph_node{$value} ? $lymph_node{$value} : "NA";
printf "%-20.20s %-9.5f %-9.5f\n", $key, $lymph_node_value, $spleen_value;
}
The printf statement is a nice way to tabularize data. You'll have to create the headings yourself. The ... ? ... : ... statement is an abbreviated if/then/else If the statement before the ? is true, then the value is the value between the ? and the :. Else, the value is the value after the :.
Both of your inner hashes have the same keys, So do a foreach on one of the hashes to get the key, and then print both.
I have following hash of hash :
%HoH = (
flintstones => {
husband => "fred",
pal => "barney",
},
jetsons => {
husband => "george",
wife => "jane",
his boy => "elroy",
},
simpsons => {
husband => "homer",
wife => "marge",
kid => "bart",
},
);
How to iterate over each inner hash (say flintstones) and also extract the key names (husband, pal) and corresponding vales for each such iteration?
for my $k (keys %{ $HoH{flintstones} }) {
my $v = $HoH{flintstones}{$k};
print "key is $k; value is $v\n";
}
another way would be using each
while( my($k, $v) = each %{ $HoH{flintstones} }) { ... }
for my $outer_key ( keys %HoH )
{
print "Values for inner_hash $outer_key:\n";
for my $inner_key ( keys %{$HoH{$outer_key}} )
{
print "\t'$inner_key' points to " . $HoH{$outer_key}{$inner_key} . "\n";
}
}
Because each key in the outer level points to a hash in the inner level we can use all the normal hash functions on that entry.
While it is possible to write this in a more succinct way without the double loop I prefer the nested loops for two reasons.
It is more obvious when someone else has to work on the code (including you in six months as someone else).
It makes it easier to track things such as which outer key leads to this point if needed (as shown in my output).
Just loop over the hash (by keys or values or each, depending on whether you need the keys and on taste) and then loop over the inner hashes (likewise).
So, to get all of the people described by this hash:
for (values %HoH) {
for (values $_) {
push #people, $_
}
}
Or to build a table of all the husbands, all the wives, etc.:
for my $fam (values %HoH) {
push #{$relations{$_}}, $fam->{$_} for keys $fam
}
Or to re-key the table off the husbands:
for my $fam (keys %HoH) {
$patriarchs{$HoH{$fam}{husband}}{family} = $fam;
for (keys $HoH{$fam}) {
next if $_ eq 'husband';
$patriarchs{$HoH{$fam}{husband}}{$_} = $HoH{$fam}{$_};
}
}