Can I use 'map' or some similar function to make the codes simpler?
# $animal and #loads are pre-defined somewhere else.
my #bucket;
foreach my $item (#loads) {
push #bucket, $item->{'carrot'} if $animal eq 'rabbit' && $item->{'carrot'};
push #bucket, $item->{'meat'} if $animal eq 'lion' && $item->{'meat'};
}
Are you looking for something like this?
%foods = ( 'lion' => 'meat', 'rabbit' => 'carrot');
# ...
foreach my $item (#loads) {
push #bucket, $item->{$food{$animal}} if $item->{$food{$animal}};
}
This question would be easier to answer authoritatively with a bit more sample data. As it is I need to make a lot of assumptions.
Assuming:
#loads = (
{ carrot => 47, meat => 32, zebras => 25 },
{ carrot => 7, zebras => 81 },
);
and #buckets should look like:
#buckets = ( 47, 32, 7 );
when #animals looks like:
#animals = qw/ rabbit lion /;
Here's a maptastic approach. To understand it you will need to think in terms of lists of values as the operands rather than scalar operands:
my #animals = qw/ rabbit lion /;
my %eaten_by = (
lion => 'meat',
rabbit => 'carrot',
mouse => 'cheese',
);
# Use a "hash slice" to get a list of foods consumed by desired animals.
# hash slices let you access a list of hash values from a hash all at once.
my #foods_eaten = #eaten_by{ #animals };
# Hint: read map/grep chains back to front.
# here, start with map #loads and then work back to the assignment
my #bucket =
grep $_, # Include only non-zero food amounts
map #{$_}{#foods_eaten}, # Hash slice from the load, extract amounts of eaten foods.
map #loads; # Process a list of loads defined in the loads array
Rewritten in a verbose nested loop you get:
my #buckets;
for my $item ( #loads ) {
for my $animal ( #animals ) {
my $amount = $item{ $eaten_by{$animal} };
next unless $amount;
push #buckets, $amount;
}
}
Which one to use? It all depends on your audience--who will be maintaining the code? Are you working with a team of Perl hackers featuring 4 of the perl5porters? use the first one. Is your team composed of one or two interns that come and go with the seasons who will spend 1% of their time working on code of any kind? Use the second example. Likely, your situation is somewhere in the middle. Use your discretion.
Happy hacking!
Related
I have a code snippet as below
# Get top 10 CPU hogging process
my $prumObj = OvPrum->new($OVOSystem, $Session);
my $numprocs = 10;
my $ProcString= "";
my $ProcessList = $prumObj->getTopCPUHoggingProcess($numprocs);
if ( $ProcessList and scalar(#$ProcessList) )
{
$ProcString = "Top ".$numprocs." CPU hogging processes are listed below :\n";
$ProcString .= "\nCPU PID Process\n\n";
foreach ( #$ProcessList)
{
$ProcString .= $$_{pctcpu}." ".$$_{pid}." ".$$_{args}."\n";
}
}
The output of the above snippet is
Top 10 CPU hogging processes are listed below :
% CPU PID Process
100 6092 CPUSTRES
6 4252 opcmona
However I would want to get all the fields (instead of %CPU, PID and Process). How can I get all the fields assigned to the variable ProcString?
Could you please help me in this.
Firstly, the code you have shown was written by someone who doesn't know as much Perl as they think they know. Everywhere that they have a construct like $$_{pctcpu}, a real Perl programmer would have written $_->{pctcpu}. They also didn't know that variables are interpolated in a double-quoted Perl string. So the last line of code would actually have been written as:
$ProcString .= "$_->{pctcpu} $_->{pid} $_->{args}\n";
Secondly, you need to realise that no-one here knows what the OvPrum module is or how it works. So all of this is guesswork.
Having said that. It seems pretty clear that your getTopCPUHoggingProcess() method is returning an array reference and that every element of the referenced array is a hash reference.
So I think your question is actually, "given an array of hash references, how can I print all of the values from the array, not just the ones the current code explicitly asks for". And I think the code below answers that. Notice that I've faked the definition of the $ProcessList variable. My version certainly won't be accurate, but it should be close enough to enable you to work out what is going on.
#!/usr/bin/perl
use strict;
use warnings;
my $ProcessList = [{
pctcpu => 3,
pid => 1000,
args => 'something',
extra => 'Important data',
}, {
pctcpu => 2,
pid => 2000,
args => 'something else',
extra => 'Vital data',
}];
my $ProcString = '';
# Get all of the keys of the first hash in the array
my #keys = keys %{ $ProcessList->[0] };
print "#keys\n";
foreach (#$ProcessList) {
$ProcString .= join(' ', #{$_}{ #keys }) . "\n";
}
print "$ProcString\n";
I am struggling with accessing/ modifying hashes of unknown (i.e. dynamic) depth.
Suppose I am reading in a table of measurements (Length, Width, Height) from a file, then calculating Area and Volume to create a hash like the following:
# #Length Width Height Results
my %results = (
'2' => {
'3' => {
'7' => {
'Area' => 6,
'Volume' => 42,
},
},
},
'6' => {
'4' => {
'2' => {
'Area' => 24,
'Volume' => 48,
},
},
},
);
I understand how to access a single item in the hash, e.g. $results{2}{3}{7}{'Area'} would give me 6, or I could check if that combination of measurements has been found in the input file with exists $results{2}{3}{7}{'Area'}. However that notation with the series of {} braces assumes I know when writing the code that there will be 4 layers of keys.
What if there are more or less and I only discover that at runtime? E.g. if there were only Length and Width in the file, how would you make code that would then access the hash like $results{2}{3}{'Area'}?
I.e. given a hash and dynamic-length list of nested keys that may or may not have a resultant entry in that hash, how do you access the hash for basic things like checking if that key combo has a value or modifying the value?
I almost want a notation like:
my #hashkeys = (2,3,7);
if exists ( $hash{join("->",#hashkeys)} ){
print "Found it!\n";
}
I know you can access sub-hashes of a hash and get their references so in this last example I could iterate through #hashkeys, checking for each one if the current hash has a sub-hash at that key and if so, saving a reference to that sub-hash for the next iteration. However, that feels complex and I suspect there is already a way to do this much easier.
Hopefully this is enough to understand my question but I can try to work up a MWE if not.
Thanks.
So here's a recursive function which does more or less what you want:
sub fetch {
my $ref = shift;
my $key = shift;
my #remaining_path = #_;
return undef unless ref $ref;
return undef unless defined $ref->{$key};
return $ref->{$key} unless scalar #remaining_path;
return fetch($ref->{$key}, #remaining_path);
}
fetch(\%results, 2, 3, 7, 'Volume'); # 42
fetch(\%results, 2, 3); # hashref
fetch(\%results, 2, 3, 7, 'Area', 8); # undef
fetch(\%results, 2, 3, 8, 'Area'); # undef
But please check the comment about bad data structure which is already given by someone else, because it's very true. And if you still think that this is what you need, at least rewrite it using a for-loop, as perl does not optimize tail recursion.
Take a look at $; in "man perlvar".
http://perldoc.perl.org/perlvar.html#%24%3b
You may use the idea to convert variable length array into single key.
my %foo;
my (#KEYS)=(2,3,7);
$foo{ join( $; , #KEYS ) }{Area}=6;
$foo{ join( $; , #KEYS ) }{Volume}=42;
I need to create something like this:
my $valueref = {
1 => 1,
2 => 2,
3 => 3,
4 => 4
};
Based on certain conditions, it might be up to 40, or 50 or 60. In each case it would be consecutive integers, as show in the example. Once created, it would never be changed, simply passed to a preexisting subroutine. Since both the keys and the values will be consecutive, I could also create the hash using a for loop. I was curious what would be the fastest and/or most efficient way to create the hash? Or if there was yet another way it could be done?
Using map would suffice:
my $valueref = { map { $_ => $_ } 1 .. 40 };
Though one might note here that this is actually an array...
my #array = 0 .. 40;
So $valueref->{$n} is actually $array[$n]. I don't know if there is any benefit to using a hash in this case.
I'd probably use map for this:
my $highest_value = 50;
my %foo = map { $_ => $_ } 1 .. $highest_value ;
Remember that order is not guaranteed in a hash.
"Once created, it would never be changed, simply passed to a
preexisting subroutine"
Sounds like a good candidate for the state keyword (or a closure):
use feature 'state';
sub foo {
my ( $param1, $param2, $limit ) = #_;
state $valueref = { map { $_ => $_ } 0 .. $limit };
...
}
This enables one to initialize the data structure and then not have to worry about passing it as an argument later on.
Hash slice? Something like this:
my %hash;
my $count = 10;
#hash{1..$count} = (1..$count);
I'm working from a question I posted earlier (here), and trying to convert the answer to a sub so I can use it multiple times. Not sure that it's done right though. Can anyone provide a better or cleaner sub?
I have a good deal of experience programming, but my primary language is PHP. It's frustrating to know how to execute in one language, but not be able to do it in another.
sub search_for_key
{
my ($args) = #_;
foreach $row(#{$args->{search_ary}}){
print "#$row[0] : #$row[1]\n";
}
my $thiskey = NULL;
my #result = map { $args->{search_ary}[$_][0] } # Get the 0th column...
grep { #$args->{search_in} =~ /$args->{search_ary}[$_][1]/ } # ... of rows where the
0 .. $#array; # first row matches
$thiskey = #result;
print "\nReturning: " . $thiskey . "\n";
return $thiskey;
}
search_for_key({
'search_ary' => $ref_cam_make,
'search_in' => 'Canon EOS Rebel XSi'
});
---Edit---
From the answers so far, I've cobbled together the function below. I'm new to Perl, so I don't really understand much of the syntax. All I know is that it throws an error (Not an ARRAY reference at line 26.) about that grep line.
Since I seem to not have given enough info, I will also mention that:
I am calling this function like this (which may or may not be correct):
search_for_key({
'search_ary' => $ref_cam_make,
'search_in' => 'Canon EOS Rebel XSi'
});
And $ref_cam_make is an array I collect from a database table like this:
$ref_cam_make = $sth->fetchall_arrayref;
And it is in the structure like this (if I understood how to make the associative fetch work properly, I would like to use it like that instead of by numeric keys):
Reference Array
Associative
row[1][cam_make_id]: 13, row[1][name]: Sony
Numeric
row[1][0]: 13, row[1][1]: Sony
row[0][0]: 19, row[0][1]: Canon
row[2][0]: 25, row[2][1]: HP
sub search_for_key
{
my ($args) = #_;
foreach my $row(#{$args->{search_ary}}){
print "#$row[0] : #$row[1]\n";
}
print grep { $args->{search_in} =~ #$args->{search_ary}[$_][1] } #$args->{search_ary};
}
You are moving in the direction of a 2D array, where the [0] element is some sort of ID number and the [1] element is the camera make. Although reasonable in a quick-and-dirty way, such approaches quickly lead to unreadable code. Your project will be easier to maintain and evolve if you work with richer, more declarative data structures.
The example below uses hash references to represent the camera brands. An even nicer approach is to use objects. When you're ready to take that step, look into Moose.
use strict;
use warnings;
demo_search_feature();
sub demo_search_feature {
my #camera_brands = (
{ make => 'Canon', id => 19 },
{ make => 'Sony', id => 13 },
{ make => 'HP', id => 25 },
);
my #test_searches = (
"Sony's Cyber-shot DSC-S600",
"Canon cameras",
"Sony HPX-32",
);
for my $ts (#test_searches){
print $ts, "\n";
my #hits = find_hits($ts, \#camera_brands);
print ' => ', cb_stringify($_), "\n" for #hits;
}
}
sub cb_stringify {
my $cb = shift;
return sprintf 'id=%d make=%s', $cb->{id}, $cb->{make};
}
sub find_hits {
my ($search, $camera_brands) = #_;
return grep { $search =~ $_->{make} } #$camera_brands;
}
This whole sub is really confusing, and I'm a fairly regular perl user. Here are some blanket suggestions.
Do not create your own undef ever -- use undef then return at the bottom return $var // 'NULL'.
Do not ever do this: foreach $row, because foreach my $row is less prone to create problems. Localizing variables is good.
Do not needlessly concatenate, for it offends the style god: not this, print "\nReturning: " . $thiskey . "\n";, but print "\nReturning: $thiskey\n";, or if you don't need the first \n: say "Returning: $thiskey;" (5.10 only)
greping over 0 .. $#array; is categorically lame, just grep over the array: grep {} #{$foo[0]}, and with that code being so complex you almost certainly don't want grep (though I don't understand what you're doing to be honest.). Check out perldoc -q first -- in short grep doesn't stop until the end.
Lastly, do not assign an array to a scalar: $thiskey = #result; is an implicit $thiskey = scalar #result; (see perldoc -q scalar) for more info. What you probably want is to return the array reference. Something like this (which eliminates $thiskey)
printf "\nReturning: %s\n", join ', ', #result;
#result ? \#result : 'NULL';
If you're intending to return whether a match is found, this code should work (inefficiently). If you're intending to return the key, though, it won't -- the scalar value of #result (which is what you're getting when you say $thiskey = #result;) is the number of items in the list, not the first entry.
$thiskey = #result; should probably be changed to $thiskey = $result[0];, if you want mostly-equivalent functionality to the code you based this off of. Note that it won't account for multiple matches anymore, though, unless you return #result in its entirety, which kinda makes more sense anyway.
I'm sending a subroutine a hash, and fetching it with my($arg_ref) = #_;
But what exactly is %$arg_ref? Is %$ dereferencing the hash?
$arg_ref is a scalar since it uses the $ sigil. Presumably, it holds a hash reference. So yes, %$arg_ref deferences that hash reference. Another way to write it is %{$arg_ref}. This makes the intent of the code a bit more clear, though more verbose.
To quote from perldata(1):
Scalar values are always named with '$', even when referring
to a scalar that is part of an array or a hash. The '$'
symbol works semantically like the English word "the" in
that it indicates a single value is expected.
$days # the simple scalar value "days"
$days[28] # the 29th element of array #days
$days{'Feb'} # the 'Feb' value from hash %days
$#days # the last index of array #days
So your example would be:
%$arg_ref # hash dereferenced from the value "arg_ref"
my($arg_ref) = #_; grabs the first item in the function's argument stack and places it in a local variable called $arg_ref. The caller is responsible for passing a hash reference. A more canonical way to write that is:
my $arg_ref = shift;
To create a hash reference you could start with a hash:
some_sub(\%hash);
Or you can create it with an anonymous hash reference:
some_sub({pi => 3.14, C => 4}); # Pi is a gross approximation.
Instead of dereferencing the entire hash like that, you can grab individual items with
$arg_ref->{key}
A good brief introduction to references (creating them and using them) in Perl is perldoc perfeftut. You can also read it online (or get it as a pdf). (It talks more about references in complex data structures than in terms of passing in and out of subroutines, but the syntax is the same.)
my %hash = ( fred => 'wilma',
barney => 'betty');
my $hashref = \%hash;
my $freds_wife = $hashref->{fred};
my %hash_copy = %$hash # or %{$hash} as noted above.
Soo, what's the point of the syntax flexibility? Let's try this:
my %flintstones = ( fred => { wife => 'wilma',
kids => ['pebbles'],
pets => ['dino'],
}
barney => { # etc ... }
);
Actually for deep data structures like this it's often more convenient to start with a ref:
my $flintstones = { fred => { wife => 'Wilma',
kids => ['Pebbles'],
pets => ['Dino'],
},
};
OK, so fred gets a new pet, 'Velociraptor'
push #{$flintstones->{fred}->{pets}}, 'Velociraptor';
How many pets does Fred have?
scalar # {flintstones->{fred}->{pets} }
Let's feed them ...
for my $pet ( # {flintstones->{fred}->{pets} } ) {
feed($pet)
}
and so on. The curly-bracket soup can look a bit daunting at first, but it becomes quite easy to deal with them in the end, so long as you're consistent in the way that you deal with them.
Since it's somewhat clear this construct is being used to provide a hash reference as a list of named arguments to a sub it should also be noted that this
sub foo {
my ($arg_ref) = #_;
# do something with $arg_ref->{keys}
}
may be overkill as opposed to just unpacking #_
sub bar {
my ($a, $b, $c) = #_;
return $c / ( $a * $b );
}
Depending on how complex the argument list is.