How to print the complete content of an array in Perl - perl

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

Related

Use of uninitialized value $_ in hash element

This is regarding a warning message I received when running a Perl script.
I understand why I'm receiving this warning: probably because $element is undefined when being called but I don't see it.
for ( my $element->{$_}; #previous_company_names; ) {
map { $element => $previous_company_names->{$_} }
0 .. $previous_company_names;
The result is this message
Use of uninitialized value $_ in hash element
First and foremost - for a new programmer, absolutely the most important thing you must do, is use strict; and use warnings;. You've got my in there, which suggests you might be, but it pays to re-iterate it.
$_ is a special variable, called the implicit variable. It doesn't really make sense to use it in the way you're doing like that, in a for loop. Take a look at perlvar for some more detail.
Indeed, I'd suggest steering clear of map entirely until you really grok it, because it's a good way to confuse yourself.
With a for (or foreach) loop you can either:
for my $thing ( #list_of_things ) {
print $thing;
}
Or you can do:
for ( #list_of_things ) {
print $_;
}
$_ is set implicitly by each iteration of the second loop, which can be quite useful because lots of things default to using it.
E.g.
for ( #list_of_things ) {
chomp;
s/ /_/g;
print;
}
When it comes to map - map is a clever little function, that lets you evaluate a code block for each element in a list. Personally - I still get confused by it, and tend to stick with for or foreach loops instead, most of the time.
But what you're doing with it, isn't really going to work - map makes a hash.
So something like:
use Data::Dumper;
my %things = map { $_ => 1 } 1..5;
print Dumper \%things;
This creates the hash 'things':
$VAR1 = {
'1' => 1,
'3' => 1,
'5' => 1,
'4' => 1,
'2' => 1
};
Again, $_ is used inside, because it's the magic variable - it's set to 'whatever was in the second bit' (e.g 1,2,3,4,5) each loop, and then the block is evaluated.
So your map expression doesn't really make a lot of sense, because you don't have $element defined... and even if you did, you'd repeatedly overwrite it.
I would also note - $previous_company_names would need to be numeric, and is in NO way related to #previous_company_names. You might be meaning to use $#previous_company_names which is the last element index.

Perl using 'map' to rewrite these codes

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!

Perl: the 'semi' infinite loop?

I have here a working code, It works fine with 8 or 10 emails, but if you just put 20 emails it never finishes computing. That is, it is not an infinite loop because otherwise it would never compute anything. Also, if you use just 10 emails but ask it to make lists of more than 2, same thing happens. Yes, as pointed out, there is a while(#address) and somewhere in there, a push into address, that is the reason. I tried to replace that array into which it was pushed by another name, but i get weird errors like it picks one email from the list and it will complain that while strict references are on, i cant use that ...
I understand 100% the code up until the 'map' line. After that, not so much...
If we look at this part:
push #addresses, $address;
$moved{$address}++;
# say "pushing $address to moved"; # debug
one would say that the variable $address would have to be pushed, not into the #addresses, as that is the source of data (hence the loop as pointed out) but onto ..'moved' but, sorry, 'moved' is a hash. You can't push a variable into a hash, can you ? should then 'moved' be actually an array and not a hash? this is where i get lost
I was thinking about this instead, but ...it is just intuition, not real knowledge
push #{ $moved[$i] }, $address
I think I have solved it, taking as departure point the remark from 'Konerak'. Indeed the issue was a never decreasing list. Because I am not knowledgeable in reference arrays I was kind of lost, but somehow reading the code I tried to find similarity in a expected behaviour.
Therefore I created another array called #reserva and I wrote this:
push # {$reserva [$i]}, $address
instead of
push #addresses, $address;
Now, I get lists the size I want regardless of how many emails I enter. I tried with 1000 and had no problem in less than one second.
So, here is the full code
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my $only_index = 3; # Read from command line with $ARGV[0] or use Getopt::Long
my %blacklist = ( # Each key in this hash represents one index/day
'2' => [ 'a', 'b' ], # and has an arrayref of domains that have replied on
'3' => [ 'c' ], # that day. We look at all keys smaller than the current
); # index in each iteration and ignore all these domains
my #domains; # holds the domains we have already seen for each list
my #lists = ([]); # Holds all the lists
my %moved; # the addresses we moved to the back
my $i = 0;
my #addresses = <DATA>;
while (#addresses) {
my $address = shift #addresses;
chomp $address;
$address =~ m/#([a-zA-Z0-9\-.]*)\b/;
my $domain = $1;
# If the domain has answered, do not do it again
next if
grep { /$domain/ }
map { exists $blacklist{$_} ? #{ $blacklist{$_} } : () } (0..$i);
$i++ if (#{ $lists[$i] } == 2
|| (exists $moved{$address} && #addresses < 1));
if (exists $domains[$i]->{$domain}) {
push #addresses, $address;
$moved{$address}++;
# say "pushing $address to moved"; # debug
} else {
$domains[$i]->{$domain}++;
# send the email
# say "added $address to $i"; # debug
push #{ $lists[$i] }, $address;
}
}
# print Dumper \#lists; # Show all lists
print Dumper $lists[$only_index]; # Only show the selected list
1;
__DATA__
1#a
2#a
3#a
1#b
2#b
1#c
2#c
3#c
1#d
2#d
3#d
4#d
1#e
1#f
1#g
1#h
4#a
5#a
4#c
That's some twisty code, it's no wonder you're having trouble following it. I'm not actually sure what the body of the code is supposed to accomplish, but you can at least avoid the infinite loop by not using while (#array) - use foreach my $item (#array) instead and you'll iterate over it and avoid and bizarre behavior that will arise from modifying the array inside the loop.
chomp(#addresses); # chomp called on an array chomps each element
foreach my $address (#addresses) {
# Do work here
}

Converting code to perl sub, but not sure I'm doing it right

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.

Perl throws an error message about syntax

So, building off a question about string matching (this thread), I am working on implementing that info in solution 3 into a working solution to the problem I am working on.
However, I am getting errors, specifically about this line of the below function:
next if #$args->{search_in} !~ /#$cur[1]/;
syntax error at ./db_index.pl line 16, near "next "
My question as a perl newbie is what am I doing wrong here?
sub search_for_key
{
my ($args) = #_;
foreach $row(#{$args->{search_ary}}){
print "#$row[0] : #$row[1]\n";
}
my $thiskey = NULL;
foreach $cur (#{$args->{search_ary}}){
print "\n" . #$cur[1] . "\n"
next if #$args->{search_in} !~ /#$cur[1]/;
$thiskey = #$cur[0];
last;
}
return $thiskey;
}
You left off the semicolon at the end of the previous line. That's what caused the syntax error, anyway. I think you're also misusing $args, but it's hard to be sure about that without knowing how you're calling this function.
There are several issues here.
Are you adding use strict; and use warnings; at the top of your script before you do anything else? You only posted the sub, but it is clear that you are not using these.
What is NULL? (strict will not let you use bare-words...) Be sure to read What is Truth in Perl? The more Perly way is to deal with "truth" or "false" is defined / undef or exists or specifically test for a value chosen as a convention.
Missing ; after print "\n" . #$cur[1] . "\n"
Your data structures seem way too complicated. From what I can tell, you are passing a reference to a hash of arrays, true? Why your data structures get really obscure, back up and look at what you are trying to do...
Perl gives you plenty of way to shoot yourself in the foot. It is not strictly typed and you will do yourself (and your readers) a favor by naming references as a derivative of what they refer to. So instead of $args use $ref2HoArefs for example.
Side note, are you sure you can't just use a hash for what you're doing? It seems awfully complicated do do something so simple:
my %hash = (
key1 => 'value1',
key2 => 'value2',
);
exists $hash{$search_in}; # true/false.
my $result = $hash{$search_in}; # returns 'value1' when $search_in is 'key1'
Or if you need to search by value:
my %flip = reverse %hash;
$result = $flip{$search_in};
And if you really need a regex key ( or value ) lookup:
sub string_match {
my ($lookup_hash, $key ) = #_;
for my $hash_key ( %{ $lookup_hash } ){
return $hash_key if $key =~ $lookup_hash->{$hash_key};
}
return; # not found.
}
my $k = string_match({
'whitespace at end' => qr/\s+$/,
'whitespace at start' => qr/^\s+/,
}, "Some Garbage string "); # k == whitespace at end