If I have a hash:
%hash = ("Dog",1,"Cat",2,"Mouse",3,"Fly",4);
How can I extract the first X elements of this hash. For example if I want the first 3 elements, %newhash would contain ("Dog",1,"Cat",2,"Mouse",3).
I'm working with large hashes (~ 8000 keys).
"first X elements of this hash" doesn't mean anything. First three elements in order by numeric value?
my %hash = ( 'Dog' => 1, 'Cat' => 2, 'Mouse' => 3, 'Fly' => 4 );
my #hashkeys = sort { $hash{$a} <=> $hash{$b} } keys %hash;
splice(#hashkeys, 3);
my %newhash;
#newhash{#hashkeys} = #hash{#hashkeys};
You might want to use something like this:
my %hash = ("Dog",1,"Cat",2,"Mouse",3,"Fly",4);
for ( (sort keys %hash)[0..2] ) {
say $hash{$_};
}
You should have an array 1st:
my %hash = ("Dog" => 1,"Cat"=>2,"Mouse"=>3,"Fly"=>4);
my #array;
foreach $value (sort {$hash{$a} <=> $hash{$b} }
keys %hash)
{
push(#array,{$value=>$hash{$value}});
}
#get range:
my #part=#array[0..2];
print part of result;
print $part[0]{'Cat'}."\n";
Related
I want to sort an array of hashes by the hashes key, how could I do that in Perl?
The structure is created like this :
push (#{$structure[$endpoint][1]}, \%temp_hash);
%temp_hash is a simple hash with key->value.
And now I want to sort that array by the hashes key, there is only one key->value in each hash... been fighting with it for 2 hours already and I gave up..
Try
#sorted = sort { (keys %$a)[0] cmp (keys %$b)[0] } #{$structure[$endpoint][1]};
This sorts the elements of the array (which are hash references) according to the first (only) key of each hash. If the keys are numeric use <=> instead.
Test code:
%a = ( 'a' => 1 );
%b = ( 'zz' => 2 );
%c = ( 'g' => 3);
#arr = (\%a, \%b, \%c);
print "Unsorted\n";
for (#arr)
{
printf "%s\n",((keys %$_)[0]);
}
#sorted = sort { (keys %$a)[0] cmp (keys %$b)[0] } #arr;
print "\nSorted\n";
for (#sorted)
{
printf "%s\n",((keys %$_)[0]);
}
My hash contains binary numbers as keys:
my %h = ("1010" => 1, "1110" => 0, "0001" => 3, "1100" => 2);
In perl I can use custom function for sorting hash. This is my function for sorting binary numbers from lowest to largest:
sub sort_binary_numbers {
my $a_dec = oct("0b".$a);
my $b_dec = oct("0b".$b);
return $a_dec <=> $b_dec;
}
I can sort hash using this function following way:
print Dumper sort sort_binary_numbers keys %h;
And the result will be:
$VAR1 = '0001';
$VAR2 = '1010';
$VAR3 = '1100';
$VAR4 = '1110';
I want to sort hash using values not keys. I can do following:
print Dumper sort { $h{$b} <=> $h{$a} } keys %h;
As you can see I have to use hash name in sorting block. The problem is how to rewrite this sorting block to function (as above examples) and automatically get the appropriate hash name in function. I've tried access hash name using #_ but it was not printed e.g.
sub sort_by_value {
print Dumper #_; # This was not printed
print ref #_; # This was not printed
return $b <=> $a;
}
And call it following way:
print Dumper sort sort_by_value keys %h;
The interesting part is that when I wrap this sorting in to another function and call it in loop from this function I will get the output of data dumper that was previously missing (but I still did not get the output of ref command):
sub calling_from_function {
my %h = %{$_[0]};
foreach my $key (sort sort_by_value keys %h){
}
}
&calling_from_function(\%h);
Then I get this output:
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
Questions:
How can I replace sorting block in this command print Dumper sort { $h{$b} <=> $h{$a} } keys %h; with function and get the appropriate name of hash inside sortign function?
Why wrapping from another function works?
Why ref does not works?
The sorting subroutine doesn't take parameters normally (i.e. unless prototypes are involved) through #_, but through $a and $b. ref #array can never return anything, as an array is never a reference.
Wrapping by another function works, because you populate #_ by parameters to the wrapper.
Use a wrapper to sort any hash:
sub sort_by_value {
my %h = #_;
return sort { $h{$b} <=> $h{$a} } keys %h
}
print Dumper(sort_by_value(%h));
You can also send the hash reference to the subroutine:
sub sort_by_value {
my ($h) = #_;
return sort { $h->{$b} <=> $h->{$a} } keys %$h
}
print Dumper sort_by_value(\%h);
So you want to have a generic sorting function such as
my $sorter = sub { $_[0]{$b} <=> $_[0]{$a} };
When it comes time to sort, just use
my #sorted_keys = sort { $sorter->(\%h) } keys(%h);
You can use hash as a list, convert it to k/v aref pairs, perform sort on values (second element), and pick keys from sorted list (roughly it is Schwartzian transform in disguise).
use strict;
use warnings;
use List::Util 'pairs';
my %h = ("1010" => 1, "1110" => 0, "0001" => 3, "1100" => 2);
my #k = map $_->[0],
sort { $b->[1] <=> $a->[1] }
pairs %h;
without additional modules,
my #k = map $_->[0],
sort { $b->[1] <=> $a->[1] }
map [ $_, $h{$_} ],
keys %h;
I have a hash in which the keys are strings and the values are single-digit numbers; here's a slice of said hash:
'f92a0d43-a230-4bfd-b580-9eac5e0ce6cf' => 7,
'26c4b622-969f-4861-bbab-dd506ea4b00a' => 1,
'afb1f925-4109-4b1d-967f-3958106e0bc3' => 3,
'a099a6dc-0c66-4683-94c3-29d6ef6947fd' => 1,
'e71c4860-224d-4b8d-ae9e-4700e9e65a97' => 2,
I want print the keys in order of descending values. So for the slice listed there, the output would be:
'f92a0d43-a230-4bfd-b580-9eac5e0ce6cf' => 7
'afb1f925-4109-4b1d-967f-3958106e0bc3' => 3
'e71c4860-224d-4b8d-ae9e-4700e9e65a97' => 2
'26c4b622-969f-4861-bbab-dd506ea4b00a' => 1
'a099a6dc-0c66-4683-94c3-29d6ef6947fd' => 1
The order of keys with identical values does not matter. Answers to this question:
In Perl, how can I print the key corresponding to the maximum value in a hash?
suggest using the sort function; which I have:
my #values = sort { $b <=> $a } values %ID_hash;
What I am having trouble with is actually printing the keys in the order.
I tried:
foreach(#values) {
my $cur = $_;
print "$ID_hash{$cur}\t$cur\n";
}
Which fails because I'm supplying values rather than keys.
I know I can always just print the key/value pairs as a tab-separated file and use the Unix version of sort but I'm sure there's a way to do this with Perl. Any help will be much appreciated.
Sort the keys by the values in the hash, then use the sorted keys to print.
for my $key ( sort { $ID_hash{$b} <=> $ID_hash{$a} } keys %ID_hash ) {
print join( "\t", $key, $ID_hash{$key} ), "\n";
}
This equivalent may be a little clearer:
my #sorted_keys = sort { $ID_hash{$b} <=> $ID_hash{$a} } keys %ID_hash ;
print "$_\t$ID_hash{$_}\n" for #sorted_keys;
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
}
}
I would like to make the value the key, and the key the value. What is the best way to go about doing this?
Adapted from http://www.dreamincode.net/forums/topic/46400-swap-hash-values/:
Assuming your hash is stored in $hash:
while (($key, $value) = each %hash) {
$hash2{$value}=$key;
}
%hash=%hash2;
Seems like much more elegant solution can be achieved with reverse (http://www.misc-perl-info.com/perl-hashes.html#reverseph):
%nhash = reverse %hash;
Note that with reverse, duplicate values will be overwritten.
Use reverse:
use Data::Dumper;
my %hash = ('month', 'may', 'year', '2011');
print Dumper \%hash;
%hash = reverse %hash;
print Dumper \%hash;
As mentioned, the simplest is
my %inverse = reverse %original;
It "fails" if multiple elements have the same value. You could create an HoA to handle that situation.
my %inverse;
push #{ $inverse{ $original{$_} } }, $_ for keys %original;
So you want reverse keys & vals in a hash? So use reverse... ;)
%hash2 = reverse %hash;
reverting (k1 => v1, k2 => v2) - yield (v2=>k2, v1=>k1) - and that is what you want. ;)
my %orig_hash = (...);
my %new_hash;
%new_hash = map { $orig_hash{$_} => $_ } keys(%orig_hash);
The map-over-keys solution is more flexible. What if your value is not a simple value?
my %forward;
my %reverse;
#forward is built such that each key maps to a value that is a hash ref:
#{ a => 'something', b=> 'something else'}
%reverse = map { join(',', #{$_}{qw(a b)}) => $_ } keys %forward;
Here is a way to do it using Hash::MultiValue.
use experimental qw(postderef);
sub invert {
use Hash::MultiValue;
my $mvh = Hash::MultiValue->from_mixed(shift);
my $inverted;
$mvh->each( sub { push $inverted->{ $_[1] }->#* , $_[0] } ) ;
return $inverted;
}
To test this we can try the following:
my %test_hash = (
q => [qw/1 2 3 4/],
w => [qw/4 6 5 7/],
e => ["8"],
r => ["9"],
t => ["10"],
y => ["11"],
);
my $wow = invert(\%test_hash);
my $wow2 = invert($wow);
use DDP;
print "\n \%test_hash:\n\n" ;
p %test_hash;
print "\n \%test_hash inverted as:\n\n" ;
p $wow ;
# We need to sort the contents of the multi-value array reference
# for the is_deeply() comparison:
map {
$test_hash{$_} = [ sort { $a cmp $b || $a <=> $b } #{ $test_hash{$_} } ]
} keys %test_hash ;
map {
$wow2->{$_} = [ sort { $a cmp $b || $a <=> $b } #{ $wow2->{$_} } ]
} keys %$wow2 ;
use Test::More ;
is_deeply(\%test_hash, $wow2, "double inverted hash == original");
done_testing;
Addendum
Note that in order to pass the gimmicky test here, the invert() function relies on %test_hash having array references as values. To work around this if your hash values are not array references, you can "coerce" the regular/mixed hash into a multi-value hash thatHash::MultiValue can then bless into an object. However, this approach means even single values will appear as array references:
for ( keys %test_hash ) {
if ( ref $test_hash{$_} ne 'ARRAY' ) {
$test_hash{$_} = [ $test_hash{$_} ]
}
}
which is longhand for:
ref($_) or $_ = [ $_ ] for values %test_hash ;
This would only be needed to get the "round trip" test to pass.
Assuming all your values are simple and unique strings, here is one more easy way to do it.
%hash = ( ... );
#newhash{values %hash} = (keys %hash);
This is called a hash slice. Since you're using %newhash to produce a list of keys, you change the % to a #.
Unlike the reverse() method, this will insert the new keys and values in the same order as they were in the original hash. keys and values always return their values in the same order (as does each).
If you need more control over it, like sorting it so that duplicate values get the desired key, use two hash slices.
%hash = ( ... );
#newhash{ #hash{sort keys %hash} } = (sort keys %hash);