'Do something' if key exists in hash1 and not in hash2 - perl

I've written some code that finds overlapping keys in 3 different HoAs that contain some information on which I sort them later:
#!/usr/bin/perl
use warnings;
use strict;
my #intersect;
for my $key (sort keys %hash1) {
if (exists $hash2{$key} && $hash3{$key} ) {
my ($hit1, $percent_id1) = #{ $hash1{$key}[-1] };
my ($hit2, $percent_id2) = #{ $hash2{$key}[-1] };
my ($hit3, $percent_id3) = #{ $hash3{$key}[-1] };
push #intersect, "$key\tC1:[$condition1]$hit1 [$percent_id1]\tC2:[$condition2]$hit2 [$percent_id2]\tC3:[$condition3]$hit3 [$percent_id3]\n\n";\n";
}
}
I'm trying to adapt the script to also find keys that exist in:
hash1 and hash2, but not hash3
hash2 and hash3, but not hash1
hash1 and hash3, but not hash2
For which I'm using (e.g. for the first instance):
elsif (exists $hash2{$key} && !exists $hash3{$key} ) { # Is this the right way to specify a 'not exists'?
my ($hit1, $percent_id1) = #{ $blast1{$key}[-1] };
my ($hit2, $percent_id2) = #{ $blast2{$key}[-1] };
push #intersect, "$key\tC1:[$condition1]$hit1 [$percent_id1]\tC2:[$condition2]$hit2 [$percent_id2]\n";
}
Later in the code I loop through each #intersect in order to rank them (the details of what's going on below are largely irrelevant):
foreach (#intersect) {
chomp;
my (#condition1_match) = ($_ =~ /C1:.*?Change:(-?\d+\.\d+|-?inf)/);
#q_value1 = ($_ =~ /C1:.*?q:(\d+\.\d+)/);
my (#percent_id) = ($_ =~ /C\d+:.*\[(\d+\.\d+)\]/);
push #percentages, "#percent_id%";
my (#condition2_match) = ($_ =~ /C2:.*?Change:(-?\d+\.\d+|-?inf)/);
#q_value2 = ($_ =~ /C2:.*?q:(\d+\.\d+)/);
my (#condition3_match) = ($_ =~ /C3:.*?Change:(-?\d+\.\d+|-?inf)/);
#q_value3 = ($_ =~ /C3:.*?q:(\d+\.\d+)/);
my $condition1_match = $condition1_match[0] // $condition1_match[1];
my $condition2_match = $condition2_match[0] // $condition2_match[1];
my $condition3_match = $condition3_match[0] // $condition3_match[1];
if (abs $condition1_match > abs $condition2_match && abs $condition1_match > abs $condition3_match) {
push #largest_change, $condition1_match;
}
elsif (abs $condition2_match > abs $condition1_match && abs $condition2_match > abs $condition3_match) {
push #largest_change, $condition2_match;
}
else { push #largest_change, $condition3_match}
Obviously in the case where a key exists in two, but not three hashes, there will be a lot of instances where variables are undef, and as such I get a lot of Use of uninitialized value in...
Should I be prefixing each variable with if (defined ($variable )) ??

my %seen;
++$seen{$_} for keys(%hash1), keys(%hash2), keys(%hash3);
for (keys(%seen)) {
next if $seen{$_} != 2;
print("$_ is found in exactly two hashes\n");
}
This version tracks where the keys came from:
my %seen;
push #{ $seen{$_} }, 'hash1' for keys(%hash1);
push #{ $seen{$_} }, 'hash2' for keys(%hash2);
push #{ $seen{$_} }, 'hash3' for keys(%hash3);
for (keys(%seen)) {
next if #{ $seen{$_} } != 2;
print("$_ found in #{ $seen{$_} }\n");
}

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";
}
}

compare multiple hashes for common keys merge values

I have a working bit of code here where I am comparing the keys of six hashes together to find the ones that are common amongst all of them. I then combine the values from each hash into one value in a new hash. What I would like to do is make this scaleable. I would like to be able to easily go from comparing 3 hashes to 100 without having to go back into my code and altering it. Any thoughts on how I would achieve this? The rest of the code already works well for different input amounts, but this is the one part that has me stuck.
my $comparison = List::Compare->new([keys %{$posHashes[0]}], [keys %{$posHashes[1]}], [keys %{$posHashes[2]}], [keys %{$posHashes[3]}], [keys %{$posHashes[4]}], [keys %{$posHashes[5]}]);
my %comboHash;
for ($comparison->get_intersection) {
$comboHash{$_} = ($posHashes[0]{$_} . $posHashes[1]{$_} . $posHashes[2]{$_} . $posHashes[3]{$_} . $posHashes[4]{$_} . $posHashes[5]{$_});
}
my %all;
for my $posHash (#posHashes) {
for my $key (keys(%$posHash)) {
push #{ $all{$key} }, $posHash->{$key};
}
}
my %comboHash;
for my $key (keys(%all)) {
next if #{ $all{$key} } != #posHashes;
$comboHash{$key} = join('', #{ $all{$key} });
}
Just make a subroutine and pass it hash references
my $combination = combine(#posHashes);
sub combine {
my #hashes = #_;
my #keys;
for my $href (#hashes) {
push #keys, keys %$href;
}
# Insert intersection code here..
# .....
my %combo;
for my $href (#hashes) {
for my $key (#intersection) {
$combo{$key} .= $href->{$key};
}
}
return \%combo;
}
Create a subroutine:
sub combine_hashes {
my %result = ();
my #hashes = #_;
my $first = shift #hashes;
for my $element (keys %$first) {
my $count = 0;
for my $href (#hashes) {
$count += (grep {$_ eq $element} (keys %$href));
}
if ($count > $#hashes) {
$result{$element} = $first->{$element};
$result{$element} .= $_->{$element} for #hashes;
}
}
\%result;
}
and call it by:
my %h = %{combine_hashes(\%h1, \%h2, \%h3)};
...or as:
my %h = %{combine_hashes(#posHashes)};
There is pretty straightforward solution:
sub merge {
my $first = shift;
my #hashes = #_;
my %result;
KEY:
for my $key (keys %$first) {
my $accu = $first->{$key};
for my $hash (#hashes) {
next KEY unless exists $hash->{$key};
$accu .= $hash->{$key};
}
$result{$key} = $accu;
}
return \%result;
}
You have to call it with references to hashes and it will return also hash reference e.g.:
my $comboHashRef = merge(#posHashes);

array to hash in 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?