perl using subroutine as argument of a foreach - perl

my %number_words = hash_a_file($_);
foreach my $key ( keys %number_words ) {
++$word_list{$key};
}
This is working but I would like to avoid using the intermediate variable
like this
foreach my $key ( keys hash_a_file($_) ) {
++$word_list{$key};
}
I tried to use refs but still failed. Any way to do this ? Thanks !

The thing is, a subroutine doesn't return a hash. It returns a list. In your original code, it only becomes a hash when you store it in a hash variable.
But there are other ways to create a hash from a list. You can create an anonymous hash and then de-reference it. It's ugly, but it works.
# Inner braces create an anonymous hash.
# Outer braces de-reference that into a "real" hash
foreach my $key ( keys %{ { hash_a_file($_) } } ) {
++$word_list{$key};
}
Update: To back up Borodin's comment, I should add that if this code was presented to me in a code review, I'd suggest rewriting it to use an explicit hash variable as your original code does.

Return a hashref, so that you can form a valid argument for keys (instead of a list)
sub hash_a_file { return { a => 1, b => 2 } }
foreach my $key ( keys %{ hash_a_file() } ) {
say $key
}

Related

Find key in subhash without iterate through the whole hash

I have a hash that looks like this:
my $hash = {
level1_f1 => {
level2_f1 => 'something',
level2_f2 => 'another thing'
},
level1_f2 => {
level2_f3 => 'yet another thing',
level2_f4 => 'bla bla'
level2_f5 => ''
}
...
}
I also got a list of values that correspond to the "level2" keys, which I want to know if thy exist in the hash.
#list = ("level2_f2", "level2_f4", "level2_f99")
I don't know which "level1" key each element of #list belongs to. The only way of finding if they existed I could think was using a foreach loop to go through #list, another foreach loop to go through the keys of %hash and checking
foreach my $i (#array) {
foreach my $k (keys %hash) {
if (exists $hash{$k}{$list[$i]})
}
}
but I wanted to know if there is a more eficient or maybe a more elegant way to do it. All the answers I found ask you to know the "level1" key, which I don't.
Thanks!!
Use values:
for my $inner_hash (values %$hash) {
say grep exists $inner_hash->{$_}, #list;
}
You have to loop all the level1 keys. But if you don't need to know which keys match and merely care for the existence of any, then you don't have to ask for each member of your list explicitly. You could say
foreach my $k (keys %hash) {
if ( #{ $hash{$k} }{ #list } )
{
}
}
The hash slice will return all values in the subhash which have matching keys in the list. Keys in the list that are not in the subhash get ignored.
Note however, that this does potentially more work than you may really need.
You don't need to iterate over "the entire hash".
You will necessarily over the elements of the outer hash since you want to check the value of each one, but you don't need to iterate over the elements of the inner hashes. Your solution already demonstrates that.
So your solution is as efficient as it can be, at least in terms of how well it scales. You can only perform small optimizations such as stopping as soon as a match is found.
for my $i (#list) {
while ( my (undef, $inner) = each(%hash) ) {
if (exists($inner->{$i}) {
...
last;
}
}
keys(%hash); # Reset iterator since it might not be exhausted.
}
As a micro optimization, it might be beneficial to invert the nesting of the loops.
my %list = map { $_ => 1 } #list;
while ( my (undef, $inner) = each(%hash) ) {
while (defined( my $k = each(%$inner) )) {
if ($list{$k}) {
delete($list{$k});
...
last if !keys(%list);
}
}
keys(%$inner); # Reset iterator since it might not be exhausted.
last if !keys(%list);
}
keys(%hash); # Reset iterator since it might not be exhausted.
If the hashes are small, these changes might actually slow things down.
Honestly, if there's truly a speed issue, the problem is that you used the wrong data structure for the type of query you want to run on it!

Which key from which hash is this expression accessing?

I am tracing a Perl script and I cannot understand what the following expression is supposed to do:
keys %{ $data->{net_assets_detail}->{$port}->{$manager} }
I am trying to understand which key from which hash we are trying to access — from data, port or manager?
Below is the complete code:
foreach my $port ( keys %{$data->{net_assets_detail} } ) {
foreach my $manager (keys %{ $data->{net_assets_detail}->{$port} } ) {
my $fund_value = MOA::CLSUtils::get_manager_fund_value( $port, $manager, $args->{end_date} );
$fund_value ||=0;
my $net_asset_value = $data->{net_assets_manager}->{$port}->{$manager};
$net_asset_value ||=0;
foreach my $tran_type (keys %{ $data->{net_assets_detail}->{$port}->{$manager} } ) {
my $value = $data->{net_assets_detail}->{$port}->{$manager}->{$tran_type};
print OUT"$port\t";
print OUT"$manager\t";
print OUT"$tran_type\t";
print OUT"$value\n";
}
}
}
}
Let's look at the line part by part.
%{ $data->{net_assets_detail}->{$port}->{$manager} }
There is a hashref called $data.
Inside, there are some keys. One of them is net_assets_detail
Inside is a hashref
Inside, there are some keys. One of them is the value of $port
Inside is a hashref
Inside, there are some keys. One of them is the value of $manager
The %{ ... } is dereferencing the hashref, so built-ins that expect a hash can work on it.
The keys takes all the keys of the hash (which was dereferenced from the long thingy) and returns them as a list. The foreach iterates over that list, and puts each key into the lexical variable $tram_type that is available in the body of the loop.

access hash with hashes

I have this hash with hashses.
I would like to iterate only the value of '0'.
$VAR1 = {
'1' => {
'192.168.1.1' => '192.168.1.38'
},
'0' => {
'192.168.32.6' => '192.168.32.43'
}
};
The only way I can access it is by creating two foreach my $key (keys(%myhash)) loops:
Can I use:
foreach my $key (keys(%myhash{0})) ## does not work
or directly access the values somehow?
Thanks
First of all, if you are using consecutive integers as keys to a hash then it is likely that you should be using an array instead.
The value of the hash corresponding to key 0 is $dhcpoffers{0} because it is a scalar value. %dhcpoffers{0} is just a syntax error.
You need
for my $key (keys %{ $dhcpoffers{0} }) { ... }
or, if you prefer
my $offer_0 = $dhcpoffers{0};
for my $key (keys %$offer_0) { ... }
Since version 14 of Perl 5, keys will accept a hash reference, so you may be able to write the much cleaner
for my $key (keys $dhcpoffers{0}) { ... }

perl how to reference hash itself

This might seem to be an odd thing to do, but how do I reference a hash while 'inside' the hash itself? Here's what I'm trying to do:
I have a hash of hashes with a sub at the end, like:
my $h = { A => [...], B => [...], ..., EXPAND => sub { ... } };
. I'm looking to implement EXPAND to see if the key C is present in this hash, and if so, insert another key value pair D.
So my question is, how do I pass the reference to this hash to the sub, without using the variable name of the hash? I expect to need to do this to a few hashes and I don't want to keep having to change the sub to reference the name of the hash it's currently in.
What you've got there is some nested array references, not hashes. Let's assume you actually meant that you have something like this:
my $h = { A => {...}, B => {...}, ..., EXPAND() };
In that case, you can't reference $h from within its own definition, because $h does not exist until the expression is completely evaluated.
If you're content to make it two lines, then you can do this:
my $h = { A=> {...}, B => {...} };
$h = { %$h, EXPAND( $h ) };
The general solution is to write a function that, given a hash and a function to expand that hash, returns that hash with the expansion function added to it. We can close over the hash in the expansion function so that the hash's name doesn't need to be mentioned in it. That looks like this:
use strict;
use warnings;
use 5.010;
sub add_expander {
my ($expanding_hash, $expander_sub) = #_;
my $result = { %$expanding_hash };
$result->{EXPAND} = sub { $expander_sub->($result) };
return $result;
}
my $h = add_expander(
{
A => 5,
B => 6,
},
sub {
my ($hash) = #_;
my ($maxkey) = sort { $b cmp $a } grep { $_ ne 'EXPAND' } keys %$hash;
my $newkey = chr(ord($maxkey) + 1);
$hash->{$newkey} = 'BOO!';
}
);
use Data::Dumper;
say Dumper $h;
$h->{EXPAND}->();
say Dumper $h;
Notice that we are creating $h but that the add_expander call contains no mention of $h. Instead, the sub passed into the call expects the hash it is meant to expand as its first argument. Running add_expander on the hash on the sub creates a closure that will remember which hash the expander is associated with and incorporates it into the hash.
This solution assumes that what should happen when a hash is expanded can vary by subject hash, so add_expander takes an arbitrary sub. If you don't need that degree of freedom, you can incorporate the expansion sub into add_expander.
The hash being built (potentially) happens after EXPAND() runs. I would probably use something like this:
$h = EXPAND( { A=>... } )
Where EXPAND(...) returns the modified hashref or a clone if the original needs to remain intact.

How can I cleanly turn a nested Perl hash into a non-nested one?

Assume a nested hash structure %old_hash ..
my %old_hash;
$old_hash{"foo"}{"bar"}{"zonk"} = "hello";
.. which we want to "flatten" (sorry if that's the wrong terminology!) to a non-nested hash using the sub &flatten(...) so that ..
my %h = &flatten(\%old_hash);
die unless($h{"zonk"} eq "hello");
The following definition of &flatten(...) does the trick:
sub flatten {
my $hashref = shift;
my %hash;
my %i = %{$hashref};
foreach my $ii (keys(%i)) {
my %j = %{$i{$ii}};
foreach my $jj (keys(%j)) {
my %k = %{$j{$jj}};
foreach my $kk (keys(%k)) {
my $value = $k{$kk};
$hash{$kk} = $value;
}
}
}
return %hash;
}
While the code given works it is not very readable or clean.
My question is two-fold:
In what ways does the given code not correspond to modern Perl best practices? Be harsh! :-)
How would you clean it up?
Your method is not best practices because it doesn't scale. What if the nested hash is six, ten levels deep? The repetition should tell you that a recursive routine is probably what you need.
sub flatten {
my ($in, $out) = #_;
for my $key (keys %$in) {
my $value = $in->{$key};
if ( defined $value && ref $value eq 'HASH' ) {
flatten($value, $out);
}
else {
$out->{$key} = $value;
}
}
}
Alternatively, good modern Perl style is to use CPAN wherever possible. Data::Traverse would do what you need:
use Data::Traverse;
sub flatten {
my %hash = #_;
my %flattened;
traverse { $flattened{$a} = $b } \%hash;
return %flattened;
}
As a final note, it is usually more efficient to pass hashes by reference to avoid them being expanded out into lists and then turned into hashes again.
First, I would use perl -c to make sure it compiles cleanly, which it does not. So, I'd add a trailing } to make it compile.
Then, I'd run it through perltidy to improve the code layout (indentation, etc.).
Then, I'd run perlcritic (in "harsh" mode) to automatically tell me what it thinks are bad practices. It complains that:
Subroutine does not end with "return"
Update: the OP essentially changed every line of code after I posted my Answer above, but I believe it still applies. It's not easy shooting at a moving target :)
There are a few problems with your approach that you need to figure out. First off, what happens in the event that there are two leaf nodes with the same key? Does the second clobber the first, is the second ignored, should the output contain a list of them? Here is one approach. First we construct a flat list of key value pairs using a recursive function to deal with other hash depths:
my %data = (
foo => {bar => {baz => 'hello'}},
fizz => {buzz => {bing => 'world'}},
fad => {bad => {baz => 'clobber'}},
);
sub flatten {
my $hash = shift;
map {
my $value = $$hash{$_};
ref $value eq 'HASH'
? flatten($value)
: ($_ => $value)
} keys %$hash
}
print join( ", " => flatten \%data), "\n";
# baz, clobber, bing, world, baz, hello
my %flat = flatten \%data;
print join( ", " => %flat ), "\n";
# baz, hello, bing, world # lost (baz => clobber)
A fix could be something like this, which will create a hash of array refs containing all the values:
sub merge {
my %out;
while (#_) {
my ($key, $value) = splice #_, 0, 2;
push #{ $out{$key} }, $value
}
%out
}
my %better_flat = merge flatten \%data;
In production code, it would be faster to pass references between the functions, but I have omitted that here for clarity.
Is it your intent to end up with a copy of the original hash or just a reordered result?
Your code starts with one hash (the original hash that is used by reference) and makes two copies %i and %hash.
The statement my %i=%{hashref} is not necessary. You are copying the entire hash to a new hash. In either case (whether you want a copy of not) you can use references to the original hash.
You are also losing data if your hash in the hash has the same value as the parent hash. Is this intended?