Perl: Find key for a hash of arrays [duplicate] - perl

This question already has answers here:
In Perl, how can I find the index of a given value in an array?
(7 answers)
Closed 9 years ago.
my %hash1 = (
a => ["turkey, mexico"],
b => ["india, china"],
c => ["england, vietnam"],
d => ["usa"],
);
I want to obtain the key related to mexico.
How can I get that?
Tried :
print #($a{$hash1{"mexico"}})

Maybe if you either iterate through the hash keys and return the array containing it, or if you create another hash. For the second, it might look like this:
my %newhash;
for my $key (keys %hash1) {
my #list = split /, / => $hash1{$key}[0];
# or perhaps: my #list = map split(/, /, $_), #{ $hash1{$key} };
for (#list) {
$newhash{$_} = $key;
}
}
$newhash{mexico} eq 'a'; #true
This isn't terribly efficient, but it will work.

print grep { $hash1{$_}[0] =~ /mexico/ } keys %hash1;
and in the case that hash values are arrays with multiple elements,
print grep { grep { $_ eq "mexico" } #{$hash1{$_}} } keys %hash1;

Related

How can I sort an array of hashes by the hashes name?

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

In Perl, how do I print hash keys in order of their (numerical) values?

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;

Inverting a Hash's Key and Values in Perl

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

What's the best practise for Perl hashes with array values?

What is the best practise to solve this?
if (... )
{
push (#{$hash{'key'}}, #array ) ;
}
else
{
$hash{'key'} ="";
}
Is that bad practise for storing one element is array or one is just double quote in hash?
I'm not sure I understand your question, but I'll answer it literally as asked for now...
my #array = (1, 2, 3, 4);
my $arrayRef = \#array; # alternatively: my $arrayRef = [1, 2, 3, 4];
my %hash;
$hash{'key'} = $arrayRef; # or again: $hash{'key'} = [1, 2, 3, 4]; or $hash{'key'} = \#array;
The crux of the problem is that arrays or hashes take scalar values... so you need to take a reference to your array or hash and use that as the value.
See perlref and perlreftut for more information.
EDIT: Yes, you can add empty strings as values for some keys and references (to arrays or hashes, or even scalars, typeglobs/filehandles, or other scalars. Either way) for other keys. They're all still scalars.
You'll want to look at the ref function for figuring out how to disambiguate between the reference types and normal scalars.
It's probably simpler to use explicit array references:
my $arr_ref = \#array;
$hash{'key'} = $arr_ref;
Actually, doing the above and using push result in the same data structure:
my #array = qw/ one two three four five /;
my $arr_ref = \#array;
my %hash;
my %hash2;
$hash{'key'} = $arr_ref;
print Dumper \%hash;
push #{$hash2{'key'}}, #array;
print Dumper \%hash2;
This gives:
$VAR1 = {
'key' => [
'one',
'two',
'three',
'four',
'five'
]
};
$VAR1 = {
'key' => [
'one',
'two',
'three',
'four',
'five'
]
};
Using explicit array references uses fewer characters and is easier to read than the push #{$hash{'key'}}, #array construct, IMO.
Edit: For your else{} block, it's probably less than ideal to assign an empty string. It would be a lot easier to just skip the if-else construct and, later on when you're accessing values in the hash, to do a if( defined( $hash{'key'} ) ) check. That's a lot closer to standard Perl idiom, and you don't waste memory storing empty strings in your hash.
Instead, you'll have to use ref() to find out what kind of data you have in your value, and that is less clear than just doing a defined-ness check.
I'm not sure what your goal is, but there are several things to consider.
First, if you are going to store an array, do you want to store a reference to the original value or a copy of the original values? In either case, I prefer to avoid the dereferencing syntax and take references when I can:
$hash{key} = \#array; # just a reference
use Clone; # or a similar module
$hash{key} = clone( \#array );
Next, do you want to add to the values that exist already, even if it's a single value? If you are going to have array values, I'd make all the values arrays even if you have a single element. Then you don't have to decide what to do and you remove a special case:
$hash{key} = [] unless defined $hash{key};
push #{ $hash{key} }, #values;
That might be your "best practice" answer, which is often the technique that removes as many special cases and extra logic as possible. When I do this sort of thing in a module, I typically have a add_value method that encapsulates this magic where I don't have to see it or type it more than once.
If you already have a non-reference value in the hash key, that's easy to fix too:
if( defined $hash{key} and ! ref $hash{key} ) {
$hash{key} = [ $hash{key} ];
}
If you already have non-array reference values that you want to be in the array, you do something similar. Maybe you want an anonymous hash to be one of the array elements:
if( defined $hash{key} and ref $hash{key} eq ref {} ) {
$hash{key} = [ $hash{key} ];
}
Dealing with the revised notation:
if (... )
{
push (#{$hash{'key'}}, #array);
}
else
{
$hash{'key'} = "";
}
we can immediately tell that you are not following the standard advice that protects novices (and experts!) from their own mistakes. You're using a symbolic reference, which is not a good idea.
use strict;
use warnings;
my %hash = ( key => "value" );
my #array = ( 1, "abc", 2 );
my #value = ( 22, 23, 24 );
push(#{$hash{'key'}}, #array);
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
This does not run:
Can't use string ("value") as an ARRAY ref while "strict refs" in use at xx.pl line 8.
I'm not sure I can work out what you were trying to achieve. Even if you remove the 'use strict;' warning, the code shown does not detect a change from the push operation.
use warnings;
my %hash = ( key => "value" );
my #array = ( 1, "abc", 2 );
my #value = ( 22, 23, 24 );
push #{$hash{'key'}}, #array;
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
foreach my $value (#{$hash{'key'}}) { print "h_key $value\n"; }
push #value, #array;
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
Output:
key = value
array 1
array abc
array 2
value 22
value 23
value 24
h_key 1
h_key abc
h_key 2
key = value
array 1
array abc
array 2
value 22
value 23
value 24
value 1
value abc
value 2
I'm not sure what is going on there.
If your problem is how do you replace a empty string value you had stored before with an array onto which you can push your values, this might be the best way to do it:
if ( ... ) {
my $r = \$hash{ $key }; # $hash{ $key } autoviv-ed
$$r = [] unless ref $$r;
push #$$r, #values;
}
else {
$hash{ $key } = "";
}
I avoid multiple hash look-ups by saving a copy of the auto-vivified slot.
Note the code relies on a scalar or an array being the entire universe of things stored in %hash.

How can I get the second-level keys in a Perl hash-of-hashes?

I need to get all of the values for a certain key in a hash. The hash looks like this:
$bean = {
Key1 => {
Key4 => 4,
Key5 => 9,
Key6 => 10,
},
Key2 => {
Key7 => 5,
Key8 => 9,
},
};
I just need the values to Key4, Key5 and Key6 for example. The rest is not the point of interest. How could I get the values?
Update:
So I don't have a %bean I just add the values to the $bean like this:
$bean->{'Key1'}->{'Key4'} = $value;
hope this helps.
foreach my $key (keys %{$bean{Key1}})
{
print $key . " ==> " . $bean{Key1}{$key} . "\n";
}
should print:
Key4 ==> 4
Key5 ==> 9
Key6 ==> 10
If %bean is a hash of hashes, $bean{Key1} is a hash reference. To operate on a hash reference as you would on a simple hash, you need to dereference it, like this:
%key1_hash = %{$bean{Key1}};
And to access elements within a hash of hashes, you use syntax like this:
$element = $bean{Key1}{Key4};
So, here's a loop that prints the keys and values for $bean{Key1}:
print $_, '=>', $bean{Key1}{$_}, "\n" for keys %{$bean{Key1}};
Or if you just want the values, and don't need the keys:
print $_, "\n" for values %{$bean{Key1}};
See the following Perl documentation for more details on working with complex data structures: perlreftut, perldsc, and perllol.
Yet another solution:
for my $sh ( values %Bean ) {
print "$_ => $sh->{$_}\n" for grep exists $sh->{$_}, qw(Key4 Key5 Key6);
}
See the Perl Data Structure Cookbook for lots of examples of, well, working with Perl data structures.
A good way to do this - assuming what you're posting is an example, rather than a single one off case - would be recursively. So we have a function which searches a hash looking for keys we specify, calling itself if it finds one of the values to be a reference to another hash.
sub recurse_hash {
# Arguments are a hash ref and a list of keys to find
my($hash,#findkeys) = #_;
# Loop over the keys in the hash
foreach (sort keys %{$hash}) {
# Get the value for the current key
my $value = $hash->{$_};
# See if the value is a hash reference
if (ref($value) eq 'HASH') {
# If it is call this function for that hash
recurse_hash($value,#findkeys);
}
# Don't use an else in case a hash ref value matches our search pattern
for my $key (#findkeys) {
if ($key eq $_) {
print "$_ = $value\n";
}
}
}
}
# Search for Key4, Key5 and Key6 in %Bean
recurse_hash(\%Bean,"Key4","Key5","Key6");
Gives this output:
Key4 = 4
Key5 = 9
Key6 = 10