Is there a hash equivalent for map?
my %new_hash = hash_map { new_key($a) => new_val($b) } %hash;
I know that I could loop through the keys.
List::Pairwise claims to implement exactly that syntax -- see mapp, grepp. I haven't used it though.
Also, you can do it as
%new_hash = map { new_key($_) => new_value($hash{$_}) } keys %hash;
which I admit looks clumsier if %hash is really a $deeply->{buried}->{hash}. I prefer using $temp = ...; map {...} keys %$temp in such cases.
I really can’t see what you are trying to do here. What does “a hash equivalent for map” even mean? You can use map on a hash just fine. If you want the keys, just use keys; for example"
#msglist = map { "value of $_ is $hash{$_}" } keys %hash
although usually
say "value of $_ is $hash{$_}" keys %hash;
is just fine.
If you want both, then use the whole hash.
For assignment, what’s wrong with %new_hash = %old_hash?
Do you have deep-copy issues? Then use Storable::dclone.
Do you want both key and value available in the closure at the same time? Then make a bunch of pairs with the first map:
#pairlist = map { [ $_ => $hash{$_} ] } keys %hash
I need to see an example of what you would want to do with this, but so far I can see zero cause for using some big old module instead of basic Perl.
You can use map like this:
my $i = 0;
my %new_hash = map { $i ^= 1 ? new_key($_) : new_val($_) } %hash;
You can use mapn from my module List::Gen to do this:
use List::Gen 'mapn';
my %new_hash = mapn {new_key($_[0]) => new_value($_[1])} 2 => %old_hash;
mapn is like map, except it it takes an additional argument, the number of elements to walk the list by. Inside the block, the #_ array is set to the current slice.
$ perl -d /dev/null
DB<2> %p = ( a=>'b', c=> 'd');
DB<5> p Dumper \%p
$VAR1 = {
'c' => 'd',
'a' => 'b'
};
To e.g. reverse the key and the value:
DB<6> %q = map { ($p{$_}, $_ ) } keys %p
DB<7> p Dumper \%q
$VAR1 = {
'b' => 'a',
'd' => 'c'
};
As of perl 5.20, core utility List::Util::pairmap does exactly that:
use List::Util qw(pairmap);
my %new_hash = pairmap { new_key($a) => new_val($b) } %hash;
It's not necessarily optimal (as it involves unrolling the hash to a list and back) but I believe this is the shortest way in vanilla perl.
Related
I have a partially nested hash like the following:
$href = {one=>1, word_counts=>{"the"=>34, "train"=>4} };
and I would like to get the value of $href->{'word_counts'}{'train'}.
Is it possible to put the {'word_counts'}{'train'} into a variable, so I can access it by simply calling $href->$variable?
No, but you can use Data::Diver to get a value given a list of keys:
my #keys = ('word_counts', 'train');
my $value = Data::Diver::Dive($href, \(#keys));
There are various ways to do this. I don't think you need to involved $href once you have a shortcut to the value that you want.
You can take a reference to the value, but then you have to dereference it:
my $value_ref = \ $href->{'word_counts'}{'train'};
say $$value_ref;
There's an experimental refaliasing feature where both sides are a reference. Now you don't need to dereference:
use v5.22;
\ my $value_ref = \ $href->{'word_counts'}{'train'};
say $value_ref; # 4
$value_ref = 17;
say $href->{'word_counts'}{'train'}; # 17
It's not hard to walk the hash yourself. The trick is to get one level of the hash, store it in a variable, then use that variable to get the next level. Keep going until you are where you want to be:
my $href = {
one => 1,
word_counts => {
"the" => {
"dog" => 45,
"cat" => 24,
},
"train" => {
"car" => 7,
"wreck" => 37,
}
}
};
my #keys = qw( word_counts train car );
my $temp = $href;
foreach my $key ( #keys ) {
die "Not a hash ref at <$key>" unless ref $temp eq ref {};
die "<$key> not in the hash" unless exists $temp->{$key};
$temp = $temp->{$key};
}
print "Value is <$temp>"; # 7
In addition to the more general, excellent answers from ysth and brian d foy, consider also a very simple (perhaps too simple) solution:
my #keys = qw( word_counts train);
print $href->{ $keys[0] }{ $keys[1] }; # 4
Note that this solution is repetitive, not elegant (the order of keys is hardcoded), and does not try to walk the hash. But depending on the context and the specific task of the OP, this may be all that is needed.
I have a large multi-dimensional hash which is an import of a JSON structure.
my %bighash;
There is an element in %bighash called:
$bighash{'core'}{'dates'}{'year'} = 2019.
I have a separate string variable called core.dates.year which I would like to use to extract 2019 from %bighash.
I've written this code:
my #keys = split(/\./, 'core.dates.year');
my %hash = ();
my $hash_ref = \%hash;
for my $key ( #keys ){
$hash_ref->{$key} = {};
$hash_ref = $hash_ref->{$key};
}
which when I execute:
say Dumper \%hash;
outputs:
$VAR1 = {
'core' => {
'dates' => {
'year' => {}
}
}
};
All good so far. But what I now want to do is say:
print $bighash{\%hash};
Which I want to return 2019. But nothing is being returned or I'm seeing an error about "Use of uninitialized value within %bighash in concatenation (.) or string at script.pl line 1371, line 17 (#1)...
Can someone point me into what is going on?
My project involves embedding strings in an external file which is then replaced with actual values from %bighash so it's just string interpolation.
Thanks!
Can someone point me into what is going on [when I use $bighash{\%hash}]?
Hash keys are strings, and the stringification of \%hash is something like HASH(0x655178). The only element in %bighash has core —not HASH(0x655178)— for key, so the hash lookup returns undef.
Useful tools:
sub dive_val :lvalue { my $p = \shift; $p //= \( $$p->{$_} ) for #_; $$p } # For setting
sub dive { my $r = shift; $r //= $r->{$_} for #_; $r } # For getting
dive_val(\%hash, split /\./, 'core.dates.year') = 2019;
say dive(\%hash, split /\./, 'core.dates.year');
Hash::Fold would seem to be helpful here. You can "flatten" your hash and then access everything with a single key.
use Hash::Fold 'flatten';
my $flathash = flatten(\%bighash, delimiter => '.');
print $flathash->{"core.dates.year"};
There are no multi-dimensional hashes in Perl. Hashes are key/value pairs. Your understanding of Perl data structures is incomplete.
Re-imagine your data structure as follows
my %bighash = (
core => {
dates => {
year => 2019,
},
},
);
There is a difference between the round parentheses () and the curly braces {}. The % sigil on the variable name indicates that it's a hash, that is a set of unordered key/value pairs. The round () are a list. Inside that list are two scalar values, i.e. a key and a value. The value is a reference to another, anonymous, hash. That's why it has curly {}.
Each of those levels is a separate, distinct data structure.
This rewrite of your code is similar to what ikegami wrote in his answer, but less efficient and more verbose.
my #keys = split( /\./, 'core.dates.year' );
my $value = \%bighash;
for my $key (#keys) {
$value //= $value->{$key};
}
print $value;
It drills down step by step into the structure and eventually gives you the final value.
I've created a hash of hashes in perl, where this is an example of what the hash ends up looking like:
my %grades;
$grades{"Foo Bar"}{Mathematics} = 97;
$grades{"Foo Bar"}{Literature} = 67;
$grades{"Peti Bar"}{Literature} = 88;
$grades{"Peti Bar"}{Mathematics} = 82;
$grades{"Peti Bar"}{Art} = 99;
and to print the entire hash, I'm using:
foreach my $name (sort keys %grades) {
foreach my $subject (keys %{ $grades{$name} }) {
print "$name, $subject: $grades{$name}{$subject}\n";
}
}
I need to print just the inner hash referring to "Peti Bar" and find the highest value, so theoretically, I should just parse through Peti Bar, Literature; Peti Bar, Mathematics; and Peti Bar, Art and end up returning Art, since it has the highest value.
Is there a way to do this or do I need to parse through the entire 2d hash?
You don't need to parse through the first level if you know the key that you're interested. Just leave out the first loop and access it directly. To get the highest value, you have to look at each subject once.
Keep track of the highest value and the key that goes with it, and then print.
my $max_value = 0;
my $max_key;
foreach my $subject (keys %{ $grades{'Peti Bar'} }) {
if ($grades{'Peti Bar'}{$subject} > $max_value){
$max_value = $grades{'Peti Bar'}{$subject};
$max_key = $subject;
}
}
print $max_key;
This will output
Art
An alternative implementation with sort would look like this:
print +(
sort { $grades{'Peti Bar'}{$b} <=> $grades{'Peti Bar'}{$a} }
keys %{ $grades{'Peti Bar'} }
)[0];
The + in +( ... ) tells Perl that the parenthesis () are not meant for the function call to print, but to construct a list. The sort sorts on the keys, descending, because it has $b first. It returns a list, and we take the first value (index 0).
Note that this is more expensive than the first implementation, and not necessarily more concise. Unless you're building a one-liner or your ; is broken I wouldn't recommend the second solution.
This is trivial using the List::UtilsBy module
The code is made clearer by extracting a reference to the inner hash that we're interested in. The max_by is called to return the keys of that hash that has the maximum value
use strict;
use warnings 'all';
use feature 'say';
use List::UtilsBy 'max_by';
my %grades = (
'Foo Bar' => { Literature => 67, Mathematics => 97 },
'Peti Bar' => { Literature => 88, Mathematics => 82, Art => 99 },
);
my $pb_grades = $grades{'Peti Bar'};
say max_by { $pb_grades->{$_} } keys %$pb_grades;
output
Art
As a Perl beginner I would use the List::Util core module:
use 5.014;
use List::Util 'reduce';
my $k='Peti Bar';
say reduce { $grades{$k}{$a} > $grades{$k}{$b} ? $a : $b } keys %{$grades{$k}};
Please what is the difference between these two hash initialization methods?
The First Method:
$items{"food"} = "4.4";
$items{"water"} = "5.0";
$items{"shelter"} = "1.1";
foreach $item (keys $items) {
print "$item\n";
}
The output is:
food
water
shelter
The Second Method:
%items = {
'food' => '4.4',
'water' => '5.0',
'shelter' => '1.1'
};
foreach $item (keys %items) {
print "$item\n";
}
The output is a hash reference:
HASH(0x8cc41bc)
Why does the second method return a reference instead of the actual values?
Because you have misunderstood what {} does.
It creates an anonymous hash, returning a reference.
What you've just done is functionally similar to:
my %stuff = (
'food' => '4.4',
'water' => '5.0',
'shelter' => '1.1'
);
my %items = \%stuff;
Which doesn't make a lot of sense.
Use () to init the hash, and it'll work just fine.
This is a good example of why you should always turn on warnings in your Perl code.
$ perl -Mwarnings -E'%h = {}; say keys %h'
Reference found where even-sized list expected at -e line 1.
HASH(0xbb6d48)
For more detailed explanation, use "diagnostics" too.
$ perl -Mwarnings -Mdiagnostics -E'%h = {}; say keys %h'
Reference found where even-sized list expected at -e line 1 (#1)
(W misc) You gave a single reference where Perl was expecting a list
with an even number of elements (for assignment to a hash). This usually
means that you used the anon hash constructor when you meant to use
parens. In any case, a hash requires key/value pairs.
%hash = { one => 1, two => 2, }; # WRONG
%hash = [ qw/ an anon array / ]; # WRONG
%hash = ( one => 1, two => 2, ); # right
%hash = qw( one 1 two 2 ); # also fine
HASH(0x253ad48)
Is there a way to get a sub-hash? Do I need to use a hash slice?
For example:
%hash = ( a => 1, b => 2, c => 3 );
I want only
%hash = ( a => 1, b => 2 );
Hash slices return the values associated with a list of keys. To get a hash slice you change the sigil to # and provide a list of keys (in this case "a" and "b"):
my #items = #hash{"a", "b"};
Often you can use a quote word operator to produce the list:
my #items = #hash{qw/a b/};
You can also assign to a hash slice, so if you want a new hash that contains a subset of another hash you can say
my %new_hash;
#new_hash{qw/a b/} = #hash{qw/a b/};
Many people will use a map instead of hash slices:
my %new_hash = map { $_ => $hash{$_} } qw/a b/;
Starting with Perl 5.20.0, you can get the keys and the values in one step if you use the % sigil instead of the # sigil:
my %new_hash = %hash{qw/a b/};
You'd probably want to assemble a list of keys you want:
my #keys = qw(a b);
And then use a loop to make the hash:
my %hash_slice;
for(#keys) {
$hash_slice{$_} = %hash{$_};
}
Or:
my %hash_slice = map { $_ => $hash{$_} } #keys;
(My preference is the second one, but whichever one you like is best.)
Yet another way:
my #keys = qw(a b);
my %hash = (a => 1, b => 2, c => 3);
my %hash_copy;
#hash_copy{#keys} = #hash{#keys};
Too much functional programming leads me to think of zip first.
With List::MoreUtils installed,
use List::MoreUtils qw(zip);
%hash = qw(a 1 b 2 c 3);
#keys = qw(a b);
#values = #hash{#keys};
%hash = zip #keys, #values;
Unfortunately, the prototype of List::MoreUtils's zip inhibits
zip #keys, #hash{#keys};
If you really want to avoid the intermediate variable, you could
zip #keys, #{[#hash{#keys}]};
Or just write your own zip without the problematic prototype. (This doesn't need List::MoreUtils at all.)
sub zip {
my $max = -1;
$max < $#$_and $max = $#$_ for #_;
map { my $ix = $_; map $_->[$ix], #_; } 0..$max;
}
%hash = zip \#keys, [#hash{#keys}];
If you're going to be mutating in-place,
%hash = qw(a 1 b 2 c 3);
%keep = map +($_ => 1), qw(a b);
$keep{$a} or delete $hash{$a} while ($a, $b) = each %hash;
avoids the extra copying that the map and zip solutions incur. (Yes, mutating the hash while you're iterating over it is safe... as long as the mutation is only deleting the most recently iterated pair.)
FWIW, I use Moose::Autobox here:
my $hash = { a => 1, b => 2, c => 3, d => 4 };
$hash->hslice([qw/a b/]) # { a => 1, b => 2 };
In real life, I use this to extract "username" and "password" from a form submission, and pass that to Catalyst's $c->authenticate (which expects, in my case, a hashref containing the username and password, but nothing else).
New in perl 5.20 is hash slices returning keys as well as values by using % like on the last line here:
my %population = ('Norway',5000000,'Sweden',9600000,'Denmark',5500000);
my #slice_values = #population{'Norway','Sweden'}; # all perls can do this
my %slice_hash = %population{'Norway','Sweden'}; # perl >= 5.20 can do this!
A hash is an unordered container, but the term slice only really makes sense in terms of an ordered container. Maybe look into using an array. Otherwise, you may just have to remove all of the elements that you don't want to produce your 'sub-hash'.