I have a hash which stores the count of key-value pairs, from an array of strings taken from an input document then sorts them and prints them.
%count = ();
foreach $string (#strings) {
$count{$string}++;
}
foreach my $key (sort {$count{$b} <=> $count{$a} } keys %count) {
print $key, ": ", $count{$key} ;
}
so I am wondering is there a way to only print a certain number of key-value pairs in the hash instead of all of them ? i.e print top 5 based the value?
edit: would a for loop solve this?
%count = ();
foreach $string (#strings) {
$count{$string}++;
}
my $n=0; # variable to keep count of processed keys
foreach my $key (sort {$count{$b} <=> $count{$a} } keys %count) {
# count processed keys (++$n)
# and terminate the loop after processing 5 keys
last if ++$n>5;
print $key, ": ", $count{$key} ;
}
Can take a slice of the list returned by sort
use strict;
use warnings;
use feature 'say';
....
my %count;
foreach my $string (#strings) {
++$count{$string}
}
say "$_: $count{$_}"
for ( sort { $count{$b} <=> $count{$a} } keys %count )[0..4];
(This expects that the hash indeed has five keys; if it can happen that that is not the case you'd get hit by warnings so add a test in that case, for instance $_ and say "..." for ...)
The code in the question is clearly not using strict; I recommend to always use it.
The %count = () makes sense if the hash has been populated before and now need be emptied. If you are creating it then just declare (and without = (), which does nothing).
Note, thanks to Grinnz: very recent List::Util 1.50 adds head (and tail) functions
Related
The incoming file is in the form of account:data. In this case, the data are Y-SNPs.
I want to sort by value (SNPs) and return the key (account) with the data so that I can keep the two associated. This prints only the data. And doing a regular array sort on the second field doesn't work either.
#!/usr/bin/perl
#lines = <STDIN>;
chomp #lines;
foreach (#lines)
{
(#f) = split /:/,$_;
$h{$f[0]} = $f[1];
}
#s = map { [ $_, $h{$_} ] } sort values %h;
foreach (#s) {print "#$_\n"}
If you want to sort a hash by values but get the associated keys, you can do it with a custom sort function, i.e. something like this:
my #sorted_keys = sort { $h{$a} <=> $h{$b} } keys %h;
This will return the keys in %h in the order of their values, in this case sorted numerically with <=>. If you want to sort it alphabetically use cmp instead.
For more see the documentation of sort which even has a similar example:
# this sorts the %age hash by value instead of key
# using an in-line function
my #eldest = sort { $age{$b} <=> $age{$a} } keys %age;
I'm having trouble understanding why this code will not output anything:
#!/usr/bin/perl -w
use strict;
my %allwords = (); #Create an empty hash list.
my $running_total = 0;
while (<>) {
print "In the loop 1";
chomp;
print "Got here";
my #words = split(/\W+/,$_);
}
foreach my $val (my #words) {
print "$val\n";
}
And I run it from the terminal using the command:
perl wordfinder.pl < exampletext.txt
I would expect the code above to output each word from the input file, but it does not output anything other than "In the loop 1" and "Got here". I'm trying to separate the input file word by word, using the split parameter I specified.
Update 1: Here, I have declared the variables within their proper scope, which was my main issue. Now I am getting all of the words from the input file to output on the terminal:
my %allwords = (); #Create an empty hash list.
my $running_total = 0;
my #words = ();
my $val;
while (<>) {
print "Inputting words into an array! \n";
chomp;
#words = split(/\W+/,$_);
}
print("Words have been input successfully, performing analysis: \n");
foreach $val (#words) {
print "$val\n";
}
UPDATE 2: Progress has been made. Now, we put all words from any input files into a hash, and then print each unique key (i.e. each unique word found across all input files) from the hash.
#!/usr/bin/perl -w
use strict;
# Description: We want to take ALL text files from the command line input and calculate
# the frequencies of the words contained therein.
# Step 1: Loop over all words in all input files, and put each new unique word in a
# hash (check to see if contained in hash, if not, put the word in; if the word already
# exists in the hash, then increase its "total" by 1). Also, keep a running total of
# all words.
print("Welcome to word frequency finder. \n");
my $running_total = 0;
my %words;
my $val;
while (<>) {
chomp;
foreach my $str (split(/\W+/,$_)) {
$words{$str}++;
$running_total++;
}
}
print("Words have been input successfully, performing analysis: \n");
# Step 2: Loop over all entries in the hash and look for the word (key) with the
# maximum amount, and then remove this from the hash and put in a separate list.
# Do this until the size of the separate list is 10, since we want the top 10 words.
foreach $val (keys %words) {
print "$val\n";
}
Since you've already completed step 1, you're left with getting your top ten most common words. Rather than looping through the hash and finding the most frequent entry, let's let Perl do the work for us by sorting the hash by its values.
To sort the %words hash by its keys, we can use the expression sort keys %words; to sort a hash by its values, but be able to access its keys, we need a more complex expression:
sort { $words{$a} <=> $words{$a} } keys %words
Breaking it down, to sort numerically, we use the expression
sort { $a <=> $b } #array
(see [perl sort][1] for more on the special variables $a and $b used in sorting)
sort { $a <=> $b } keys %words
would sort on the hash keys, so to sort on the values, we do
sort { $words{$a} <=> $words{$b} } keys %words
Note that the output is still the keys of the hash %words.
We actually want to sort from high to low, so swap $a and $b over to reverse the sort direction:
sort { $words{$b} <=> $words{$a} } keys %words
Since we're compiling a top ten list, we only want the first ten from our hash. It's possible to do this by taking a slice of the hash, but the easiest way is just to use an accumulator to keep count of how many entries we have in the top ten:
my %top_ten;
my $i = 0;
for (sort { $words{$b} <=> $words{$a} } keys %words) {
# $_ is the current hash key
$top_ten{$_} = $words{$_};
$i++;
last if $i == 10;
}
And we're done!
I tried to loop through a hash using each on a for each loop. Looks like the $k $v is not updated. Can anyone explain?
%hash = (a=>5,b=>6);
foreach( my ($k,$v) = each %hash){
print "\neach loop : $k $v\n";
}
output :
each loop : a 5
each loop : a 5
foreach takes a list of values, and executes its loop body once per value, assigning some variable ($_ if otherwise unspecified) each time:
foreach ( 1, 2, 3 ) {
print "The value is $_\n";
}
In your case, you gave it a list of two things, being the first key and value taken from the hash. Additionally, you also assigned those two new variables, $key and $value to be the key and value. Thus, your loop executed twice, with those variables remaining constant throughout.
A better way to iterate keys and values from a hash is to iterate on the list of keys, taking a value each time:
foreach my $key ( keys %hash ) {
my $value = $hash{$key};
...
}
Alternatively, you might enjoy the pairs function from List::Util version 1.39:
foreach my $pair ( pairs %hash ) {
my $key = $pair->key;
my $value = $pair->value;
}
Use the while loop.
#!/usr/bin/perl
use strict;
use warnings;
my %hash = (a=>5,b=>6);
while (my ($key, $value) = each %hash) {
print "Key is $key, value is $value\n";
}
Demo
Also see: Is perl's each function worth using?
You need to do while instead of foreach:
my %hash = (a=>5,b=>6);
while( my ($k,$v) = each %hash){
print "\neach loop : $k $v\n";
}
However, each() has gotachas that you need to be aware of, so I prefer just using keys instead, like this:
for my $k (keys %hash) { my $v = $hash{$k}; }
Is there a simple way to validate a hash of hash element comparsion ?
I need to validate a Perl hash of hash element $Table{$key1}{$key2}{K1}{Value} compare to all other elements in hash
third key will be k1 to kn and i want comprare those elements and other keys are same
if ($Table{$key1}{$key2}{K1}{Value} eq $Table{$key1}{$key2}{K2}{Value}
eq $Table{$key1}{$key2}{K3}{Value} )
{
#do whatever
}
Something like this may work:
use List::MoreUtils 'all';
my #keys = map "K$_", 1..10;
print "All keys equal"
if all { $Table{$key1}{$key2}{$keys[1]}{Value} eq $Table{$key1}{$key2}{$_}{Value} } #keys;
I would use Data::Dumper to help with a task like this, especially for a more general problem (where the third key is more arbitrary than 'K1'...'Kn'). Use Data::Dumper to stringify the data structures and then compare the strings.
use Data::Dumper;
# this line is needed to assure that hashes with the same keys output
# those keys in the same order.
$Data::Dumper::Sortkeys = 1;
my $string1= Data::Dumper->Dump($Table{$key1}{$key2}{k1});
for ($n=2; exists($Table{$key1}{$key2}{"k$n"}; $n++) {
my $string_n = Data::Dumper->Dump($Table{$key1}{$key2}{"k$n"});
if ($string1 ne $string_n) {
warn "key 'k$n' is different from 'k1'";
}
}
This can be used for the more general case where $Table{$key1}{$key2}{k7}{value} itself contains a complex data structure. When a difference is detected, though, it doesn't give you much help figuring out where that difference is.
A fairly complex structure. You should be looking into using object oriented programming techniques. That would greatly simplify your programming and the handling of these complex structures.
First of all, let's simplify a bit. When you say:
$Table{$key1}{$key2}{k1}{value}
Do you really mean:
my $value = $Table{$key1}->{$key2}->{k1};
or
my $actual_value = $Table{$key1}->{$key2}->{k1}->{Value};
I'm going to assume the first one. If I'm wrong, let me know, and I'll update my answer.
Let's simplify:
my %hash = %{$Table{$key1}->{$key2}};
Now, we're just dealing with a hash. There are two techniques you can use:
Sort the keys of this hash by value, then if two keys have the same value, they will be next to each other in the sorted list, making it easy to detect duplicates. The advantage is that all the duplicate keys would be printed together. The disadvantage is that this is a sort which takes time and resources.
Reverse the hash, so it's keyed by value and the value of that key is the key. If a key already exists, we know the other key has a duplicate value. This is faster than the first technique because no sorting is involved. However, duplicates will be detected, but not printed together.
Here's the first technique:
my %hash = %{$Table{$key1}->{$key2}};
my $previous_value;
my $previous_key;
foreach my $key (sort {$hash{$a} cmp $hash{$b}} keys %hash) {
if (defined $previous_key and $previous_value eq $hash{$key}) {
print "\$hash{$key} is a duplicate of \$hash{$previous_key}\n";
}
$previous_value = $hash{$key};
$previous_key = $key;
}
And the second:
my %hash = %{$Table{$key1}->{$key2}};
my %reverse_hash;
foreach $key (keys %hash) {
my $value = $hash{$key};
if (exists $reverse_hash{$value}) {
print "\$hash{$reverse_hash{$value}} has the same value as \$hash{$key}\n";
}
else {
$reverse_hash{$value} = $key;
}
}
Alternative approach to the problem is make utility function which will compare all keys if has same value returned from some function for all keys:
sub AllSame (&\%) {
my ($c, $h) = #_;
my #k = keys %$h;
my $ref;
$ref = $c->() for $h->{shift #k};
$ref ne $c->() and return for #$h{#k};
return 1
}
print "OK\n" if AllSame {$_->{Value}} %{$Table{$key1}{$key2}};
But if you start thinking in this way you can found this approach much more generic (recommended way):
sub AllSame (#) {
my $ref = shift;
$ref ne $_ and return for #_;
return 1
}
print "OK\n" if AllSame map {$_->{Value}} values %{$Table{$key1}{$key2}};
If mapping operation is expensive you can make lazy counterpart of same:
sub AllSameMap (&#) {
my $c = shift;
my $ref;
$ref = $c->() for shift;
$ref ne $c->() and return for #_;
return 1
}
print "OK\n" if AllSameMap {$_->{Value}} values %{$Table{$key1}{$key2}};
If you want only some subset of keys you can use hash slice syntax e.g.:
print "OK\n" if AllSame map {$_->{Value}} #{$Table{$key1}{$key2}}{map "K$_", 1..10};
How can I print only the first key and element of my hash?
I have already a sorted hash, but I want to print only the first key and respective value
thanks,
Thanks to all of you at the end I push the keys and the values to two different #array and print element 0 of each array and it works :)
Hashes have unordered keys. So, there is no such key as a first key in a hash.
However, if you need the key that sorts first (for maximum key value):
my %hash = (
'foo' => 'bar',
'qux' => 'baz',
);
my ($key) = sort { $b cmp $a } keys %hash;
print "$key => $hash{$key}"; # Outputs: qux => baz
Remember to use <=> instead of cmp for numerical sorting.
In perl hashes there is no ordering for keys. Use sort function to get the keys in the order that you want or you can push the keys into an array as you create the hash and your first key will be in zero th index in the array
You can use the below code, i am assuming hash name is my_hash and keys and values are numbers. If you have strings, you can use cmp instead of <=>. Refer to the sort documentation for more details
Get the max key
foreach (sort {$b <=> $a} keys %my_hash) {
print "Keys is $_\n";
print "Value is $my_hash{$_}\n";
last;
}
Get the key corresponding to the max value
foreach (sort {$my_hash{$b} <=> $my_hash{$a}} keys %my_hash) {
print "Keys is $_\n";
print "Value is $my_hash{$_}\n";
last;
}
foreach my $key (sort keys(%hash)) {
print "$key" . "$hash{$key}" . "\n";
last;
}
For large hashes, if you do not need the sorted keys for any other reason, it might be better to avoid sorting.
#!/usr/bin/env perl
use strict; use warnings;
my %hash = map { $_ => rand(10_000) } 'aa' .. 'zz';
my ($argmax, $max) = each %hash;
keys %hash; # reset iterator
while (my ($k, $v) = each %hash) {
if ($v >= $max) {
$max = $v;
$argmax = $k;
}
}
print "$argmax => $max\n";
If you are intent on sorting, you only need the key with the maximum value, not the entire arrays of keys and values:
#!/usr/bin/env perl
use strict; use warnings;
my %hash = map { $_ => rand(10_000) } 'aa' .. 'zz';
my ($argmax) = sort { $hash{$b} <=> $hash{$a} } keys %hash;
print "$argmax => $hash{$argmax}\n";
Just as Alan wrote - hashes don't have specific order, but you can sort hash keys:
foreach my $key (sort keys(%hash)) {
print $key . ': ' . $hash{$key} . "\n";
}
or, as you wish, get first element from keys array:
my #keys = keys(%hash);
print $keys[0];