At first sorry for my english - i hope you will understand me.
There is a hash:
$hash{a} = 1;
$hash{b} = 3;
$hash{c} = 3;
$hash{d} = 2;
$hash{e} = 1;
$hash{f} = 1;
I want to sort it by values (not keys) so I have:
for my $key ( sort { $hash{ $a } <=> $hash{ $b } } keys %hash ) { ... }
And at first I get all the keys with value 1, then with value 2, etc... Great.
But if hash is not changing, the order of keys (in this sort-by-value) is always the same.
Question: How can I shuffle sort-results, so every time I run 'for' loop, I get different order of keys with value 1, value 2, etc. ?
Not quite sure I well understand your needs, but is this ok:
use List::Util qw(shuffle);
my %hash;
$hash{a} = 1;
$hash{b} = 3;
$hash{c} = 3;
$hash{d} = 2;
$hash{e} = 1;
$hash{f} = 1;
for my $key (sort { $hash{ $a } <=> $hash{ $b } } shuffle( keys %hash )) {
say "hash{$key} = $hash{$key}"
}
You can simply add another level of sorting, which will be used when the regular sorting method cannot distinguish between two values. E.g.:
sort { METHOD_1 || METHOD_2 || ... METHOD_N } LIST
For example:
sub regular_sort {
my $hash = shift;
for (sort { $hash->{$a} <=> $hash->{$b} } keys %$hash) {
print "$_ ";
};
}
sub random_sort {
my $hash = shift;
my %rand = map { $_ => rand } keys %hash;
for (sort { $hash->{$a} <=> $hash->{$b} ||
$rand{$a} <=> $rand{$b} } keys %$hash ) {
print "$_ ";
};
}
To sort the keys by value, with random ordering of keys with identical values, I see two solutions:
use List::Util qw( shuffle );
use sort 'stable';
my #keys =
sort { $hash{$a} <=> $hash{$b} }
shuffle keys %hash;
or
my #keys =
map $_->[0],
sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] }
map [ $_, $hash{$_}, rand ],
keys %hash;
The use sort 'stable'; is required to prevent sort from corrupting the randomness of the list returned by shuffle.
The above's use of the Schwartzian Transform is not an attempt at optimisation. I've seen people use rand in the compare function itself to try to achieve the above result, but doing so is buggy for two reasons.
When using "misbehaving" comparisons such as that, the results are documented as being undefined, so sort is allowed to return garbage, repeated elements, missing elements, etc.
Even if sort doesn't return garbage, it won't be a fair sort. The result will be weighed.
You can have two functions for ascending and decending order and use them accordingly like
sub hasAscending {
$hash{$a} <=> $hash{$b};
}
sub hashDescending {
$hash{$b} <=> $hash{$a};
}
foreach $key (sort hashAscending (keys(%hash))) {
print "\t$hash{$key} \t\t $key\n";
}
foreach $key (sort hashDescending (keys(%hash))) {
print "\t$hash{$key} \t\t $key\n";
}
It seems like you want to randomize looping through the keys.
Perl, does not store in sequential or sorted order, but this doesn't seem to be random enough for you, so you may want to create an array of keys and loop through that instead.
First, populate an array with keys, then use a random number algorithm (1..$#length_of_array) to push the key at that position in the array, to the array_of_keys.
If you're trying to randomize the keys of the sorted-by-value hash, that's a little different.
See Codepad
my %hash = (a=>1, b=>3, c=>3, d=>2, e=>1, f=>1);
my %hash_by_val;
for my $key ( sort { $hash{$a} <=> $hash{$b} } keys %hash ) {
push #{ $hash_by_val{$hash{$key}} }, $key;
}
for my $key (sort keys %hash_by_val){
my #arr = #{$hash_by_val{$key}};
my $arr_ubound = $#arr;
for (0..$arr_ubound){
my $randnum = int(rand($arr_ubound));
my $val = splice(#arr,$randnum,1);
$arr_ubound--;
print "$key : $val\n"; # notice: output varies b/t runs
}
}
Related
I need help with creating the mode function in Perl. I have written a code using help from different sources but every time the value differs.
Mode - The maximum frequency of occurrence of an element in an array
Current Code -
#array = <STDIN>;
#sorted = sort { $a <=> $b } #array ;
for $i(#sorted)
{
$cnt =0;
for $j(#sorted)
{
if($i eq $j)
{
$cnt = $cnt + 1;
$data{$i}= $cnt;
}
}
}
#modes = sort { $data{$a} <=> $data{$b} } keys %data;
$mode = $modes[-1];
Having cleaned up your indentation and added use strict and use warnings, I get the following code.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my #array = <STDIN>;
my #sorted = sort { $a <=> $b } #array ;
my %data;
for my $i (#sorted) {
my $cnt = 0;
for my $j (#sorted) {
if ($i eq $j) {
$cnt = $cnt + 1;
$data{$i}= $cnt;
}
}
}
my #modes = sort { $data{$a} <=> $data{$b} } keys %data;
say "The mode is: $modes[-1]";
And that seems to work correctly. So, perhaps, you need to explain the problem in a little more detail.
Update: Ok, so I think I now understand your problem - even though you really haven't said what it is very clearly.
If there are two potential values for the mode (i.e. two numbers that appear the same number of times) then you want the smallest one. And your current solution picks one of the potential values at random.
The problem is this line:
#modes = sort { $data{$a} <=> $data{$b} } keys %data;
This sorts the hash by the number of times the values appear. But if multiple values appear the same number of time, they can appear in any order - so you'll get a seemingly random number out of the end.
The solution is to add more intelligence to the sort so that in case of a tie, the smaller key sorts last. That would look like this:
my #modes = sort {
$data{$a} <=> $data{$b}
or
$b <=> $a
} keys %data;
This question already has answers here:
Sorting hash keys by Alphanumeric sort
(4 answers)
Closed 8 years ago.
I have a hash which looks like this
my %hash = (
'124:8' => '',
'4:2' => '',
'17:11' => '',
'17:0' => '',
#and so on
);
I tried to sort and use hash keys by small number to bigger
for my $keys ( sort { $a > $b } keys %hash ) {
#do stuff
}
This gives me some result that looks like correct but it fails sometimes. I don't know how to compare both numbers, 124:8 with 4:2 since it has : in a middle, any suggestions ?
You might want to sort on first and second number delimited by :
my #sorted = sort {
my ($aa, $bb) = map [ split /:/ ], $a, $b;
$aa->[0] <=> $bb->[0] || $aa->[1] <=> $bb->[1]
} keys %hash;
for my $key (#sorted) { .. }
Using Schwartzian,
my #sorted = map $_->[0],
sort {
$a->[1] <=> $b->[1] || $a->[2] <=> $b->[2]
}
map [ $_, split /:/ ],
keys %hash;
When you sort numbers, you use the <=> operator:
for my $key (sort { $a <=> $b } keys %hash) {
This operator returns 1, 0 or -1 depending on the comparison. > only returns true or false, which explains it working with some results, but not all.
Because your keys are not numbers, they will only partially convert to numbers, and you will get warnings
Argument "17:11" isn't numeric in sort
Then you will need to use something like Sort::Key::Natural, or swing your own, such as:
sort {
my #a = $a =~ /\d+/g;
my #b = $b =~ /\d+/g;
$a[0] <=> $b[0] ||
$a[1] <=> $b[1] # continue as long as needed
} keys %hash
You may also use a Schwartzian transform to cache the numbers and possibly speed up the sort.
Or just sort by string comparison, though this will cause 17:11 to end up after 17:2.
Not as elegant as above solutions, but what to convert the : to . and compare them as floating point numbers? Because no math operations occurs, no rounding errors and the next could work:
my %tmp = map { (my $x = $_) =~ s/:/./; $_,$x} keys %hash;
my #sortedkeys = sort { $tmp{$a} <=> $tmp{$b} } keys %tmp;
#4:2 17:0 17:11 124:8
Or this approach is wrong?
I'm reading a file. I want a hash that gives me the first number of a line as a key to a hash of all the numbers of the rest of the line to 1.
I believe I'm adding the hash correctly, because Dumper prints correctly.
However, print "$first $secondID\n" is not giving me any output.
while (<FILE>) {
chomp $_;
if (/(\d+)\t(.+)/) {
$firstNum = $1;
#seconds = split(/\,/,$2);
foreach $following (#seconds) {
$Pairs->{$firstNum}{$following} = 1;
}
foreach $first (sort {$a <=> $b} keys %Pairs) {
print "$first\n";
%second = {$Pairs{$first}};
foreach $secondID (sort {$a <=> $b} keys %second) {
print "$first $secondID\n";
}
}
print Dumper($Pairs);
}
else {
print "ERROR\n";
}
}
Later on, given a pair of numbers I would like to look up to see whether $Pairs{$num1}{$num2} is defined. would I write
if(defined $Pairs{$num1}{$num2})
Or should I check the first key first. Then check the second key
if (defined $Pairs{$num1}) {
$temp = $Pairs{$num1};
if (defined $temp{$num2}) {
print "true\n;
}
}
You have a couple of errors. Firstly you seem to be unsure whether you are using %Pairs or $Pairs to store your hash, and secondly you have %second = {$Pairs{$first}}, which tries to assign a hash reference to the hash %second. Presumably you want my %second = %{ $Pairs{$first} }.
You should always use strict and use warnings at the start of all your Perl programs, and declare all variables at the point of first use using my. This will alert you to simple mistakes you could otherwise easily overlook, and would have shown up your use of both %Pairs and $Pairs in this program, as well as your attempt to assign a single value (a hash reference) to a hash.
Rather than copying the entire hash, you should save a reference to it in $seconds. Then you can dereference it in the following for loop.
Experienced Perl programmers would also thank you for using lower-case plus underscore for local (my) variables, and reserving capitals for package and class names.
This program works as you intended, and expects the file name as a command-line parameter:
use strict;
use warnings;
my %pairs;
while (<>) {
unless ( /(\d+)\s+(.+)/ ) {
print "ERROR\n";
next;
}
my $first_num = $1;
my #seconds = split /,/, $2;
foreach my $following (#seconds) {
$pairs{$first_num}{$following} = 1;
}
foreach my $first (sort { $a <=> $b } keys %pairs) {
print "$first\n";
my $second = $pairs{$first};
foreach my $second_id (sort { $a <=> $b } keys %$second) {
print "$first $second_id\n";
}
}
}
my %hash;
while ( <> ) {
my #numbers = split /\D+/;
my $key = shift #numbers;
#{$hash{$key}}{ #numbers } = ( 1 ) x #numbers;
}
# test it this way...
if ( $hash{ $num1 }{ $num2 } ) {
}
Use:
%second = %{$Pairs->{$first}};
In Perl, I want to sort the keys of a hash by value, numerically:
{
five => 5
ten => 10
one => 1
four => 4
}
producing two arrays:
(1,4,5,10) and (one, four, five, ten)
And then I want to normalize the values array such that the numbers are sequential:
(1,2,3,4)
How do I do this?
First sort the keys by the associated value. Then get the values (e.g. by using a hash slice).
my #keys = sort { $h{$a} <=> $h{$b} } keys(%h);
my #vals = #h{#keys};
Or if you have a hash reference.
my #keys = sort { $h->{$a} <=> $h->{$b} } keys(%$h);
my #vals = #{$h}{#keys};
How do I sort a hash (optionally by value instead of key)?
To sort a hash, start with the keys. In this example, we give the list of keys to the sort function which then compares them ASCIIbetically (which might be affected by your locale settings). The output list has the keys in ASCIIbetical order. Once we have the keys, we can go through them to create a report which lists the keys in ASCIIbetical order.
my #keys = sort { $a cmp $b } keys %hash;
foreach my $key ( #keys ) {
printf "%-20s %6d\n", $key, $hash{$key};
}
We could get more fancy in the sort() block though. Instead of comparing the keys, we can compute a value with them and use that value as the comparison.
For instance, to make our report order case-insensitive, we use lc to lowercase the keys before comparing them:
my #keys = sort { lc $a cmp lc $b } keys %hash;
Note: if the computation is expensive or the hash has many elements, you may want to look at the Schwartzian Transform to cache the computation results.
If we want to sort by the hash value instead, we use the hash key to look it up. We still get out a list of keys, but this time they are ordered by their value.
my #keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;
From there we can get more complex. If the hash values are the same, we can provide a secondary sort on the hash key.
my #keys = sort {
$hash{$a} <=> $hash{$b}
or
"\L$a" cmp "\L$b"
} keys %hash;
Please see the Perl FAQ entry titled "How do I sort a hash (optionally by value instead of key)".
You can also use perldoc -q to search the FAQ locally on your machine, as in perldoc -q sort, which is how I found your answer.
my ( #nums, #words );
do { push #nums, shift #$_;
push #words, shift #$_;
}
foreach sort { $a->[0] <=> $b->[0] }
map { [ $h->{ $_ }, $_ ] } keys %$h
;
Sometimes it's best to show rather than tell...
%results = (Paul=>87, Ringo=>93, John=>91, George=>97);
#display the results in ascending key (alphabetical) order
print "key ascending...\n";
foreach $key ( sort { $a cmp $b } keys %results ){
print "$key=>$results{$key}\n";
}
print "\n";
# display the results in descending key (alphabetical) order
print "key descending...\n";
foreach $key ( sort { $b cmp $a } keys %results ){
print "$key=>$results{$key}\n";
}
print "\n";
# display the results in descending value (numerical) order
print "value ascending...\n";
foreach $key ( sort { $results{$a} <=> $results{$b} } keys %results ){
print "$key=>$results{$key}\n";
}
print "\n";
# display the results in ascending value (numerical) order
print "value descending...\n";
foreach $key ( sort { $results{$b} <=> $results{$a} } keys %results ){
print "$key=>$results{$key}\n";
}
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];