How to print the previous Key Value in Perl? - 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}}";

Related

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

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

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

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.

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?