Using Data::Dumper and List::Util I'm able to sum the total of each row within my array with a subroutine. This part is correct.
With an easier approach I attempted to print the grand total of all numbers with a separate subroutine called get_grandtotal. This returns incorrect numbers.
My question is how do I print the correct grand total?
And what modifications would I use to print the column total (instead of the row total) using a similar structure in get_row(#values).
#!/usr/bin/perl
use 5.10.1;
use warnings;
use strict;
use List::Util qw(sum);
use Data::Dumper;
my #values = (
[ 6, 5, 13 ],
[ 35, 9, 6 ],
[ 65, 255, 54 ]
);
get_row(#values);
sub get_row {
my #total;
foreach my $row (#_) {
say join ' ', #$row;
push #total, sum #$row;
}
say Data::Dumper->Dump( [ \#total ], [ qw(*Row_Total) ] );
}
my $sum = 0;
sub get_grandtotal() {
foreach (#values) {
$sum += $_;
}
print "Grand Total = $sum\n";
}
get_grandtotal();
Output
6 5 13
35 9 6
65 255 54
#Row_Total = (
'24',
'50',
'374'
);
Grand Total = 61899232
You are trying to add together array references in
$sum += $_;
change this to
$sum += sum #$_;
and your code will work.
This subroutine uses map to extract the columns from the array, and prints the totals
sub get_column {
my #total;
foreach my $i (0 .. $#{$values[0]}) {
my #column = map $_->[$i], #values;
say join ' ', #column;
push #total, sum #column;
}
say Data::Dumper->Dump( [ \#total], [ qw(*Column_Total) ] );
}
output
6 35 65
5 9 255
13 6 54
#Column_Total = (
'106',
'269',
'73'
);
Related
I've been really confused about this, I'm trying to create a big matrix of numbers and I want to use sprintf with perl to have a nicer output. I'm trying to use sprintf like so
my $x = 0;
my $y = 0;
for ($x=1; $x<=$steps; $y++) { # loop through lines
for ($y=0; $y<=$distances; $y++) {
my $format = sprintf ("%s",$matrix[$x][$y]);
but this is really doing my head in, as I am looping through all the values of $x and $y and getting their combinations. So I am not sure if I'm meant to use more formatting arguments like so
my $format = sprintf ("%s%s%s",$matrix[$x][$y]);
(of course this is giving me compilation errors as it's not right)
But when I only use one argument, I can't put spaces in between my columns :/ Can somebody explain what's happening? I really don't understand what I'm meant to do to get the formatting nice. I'm looking to just align the columns and have a couple of whitespaces between them. Thank you all so much.
I would be thinking in terms of using map, as a way to display every element:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [1,2,3,4],
[5,6,7,8],
[9,10,11,12], );
print join ("\n", map { join ( "\t", #$_ ) } #matrix );
This is formatting on tab-stops, rather than fixed width columns, and outputs:
1 2 3 4
5 6 7 8
9 10 11 12
If you particularly wanted sprintf though:
foreach my $row ( #matrix ) {
print map { sprintf("%5s", $_) } #$row,"\n";
}
(5 columns wide).
In each of these, I'm working on whole rows - that only really applies though, if I'm right about the assumptions I've made about which elements you're displaying.
At a very basic level - your code could work as:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [ 1, 2, 3, 4 ],
[ 5, 6, 7, 8 ],
[ 9, 10, 11, 12 ], );
my $steps = 2;
my $distances = 3;
for ( my $x = 1; $x <= $steps; $x++ ) { # loop through lines
for ( my $y = 0; $y <= $distances; $y++ ) {
printf( "%5s", $matrix[$x][$y] );
}
print "\n";
}
Although note - that will only work with equal numbers of columns. You could, however, do something like:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [ 1, 2, ],
[ 3, 4, 5, ],
[ 6, 7, 8, 9, 10, 11, 12 ], );
my $steps = 2;
my $distances = 3;
for ( my $x = 1; $x <= $steps; $x++ ) { # loop through lines
for ( my $y = 0; $y <= $distances; $y++ ) {
printf( "%5s", $matrix[$x][$y] // '' );
}
print "\n";
}
Which omits the first row (because you set $x to 1), and iterates up to 4 columns:
3 4 5
6 7 8 9
This omits the extra values on the last line, and uses // to test if the cell is empty or not.
for my $row (#matrix) {
my $format = join(' ', ('%5.2f') x #$row)."\n";
printf($format, #$row);
}
If all rows have the same number of columns, you could calculate the format once.
if (#matrix) {
my $format = join(' ', ('%5.2f') x #{$matrix[0]})."\n";
for my $row (#matrix) {
printf($format, #$row);
}
}
If the size of the columns isn't unknown in advance, you'll need to need to perform the following in order:
Format the cells (if needed),
Find the length of the largest cell of each column, then
Print out the matrix with padding.
The following assumes every row of the matrix is the same length.
use List::Util qw( max );
if (#matrix) {
for my $row (#matrix) {
$_ = sprinf('%.2f', $_) for #$row;
}
my $num_cols = #{$matrix[0]};
my #col_sizes = (0) x $num_cols;
for my $row (#matrix) {
$col_sizes[$x] = max(0, $col_sizes[$x], $row->[$x]);
}
my $format = join(' ', map { "%$_s" } #col_sizes)."\n";
for my $row (#matrix) {
printf($format, #$row);
}
}
I have a hash with 5 keys, each of these keys have 5 values
foreach $a(#mass){
if($a=~some regex){
#value=($1,$2,$3,$4,$5);
$hash{"keysname$c"}="#value";
c++;
}
}
Each scalar is a value of different parameters , I have to determinate the highest value of the first array for the all keys in hash
Edit:
Code must compare first value of key1 with first value of key2, key3...key5 and print the highest one
This will print max value for structure like
my %hash = ( k1 => [6,4,1], k2 => [16,14,11] );
use List::Util qw(max);
# longest array
my $n = max map $#$_, values %hash;
for my $i (0 .. $n) {
my $max = max map $_->[$i], values %hash;
print "max value on position $i is $max\n";
}
and for strings,
my %hash = ( k1 => "6 4 1", k2 => "16 14 11" );
use List::Util qw(max);
# longest array
my $n = max map $#{[ split ]}, values %hash;
for my $i (0 .. $n) {
my $max = max map [split]->[$i], values %hash;
print "max value on position $i is $max\n";
}
If I understand your question correctly (and it's a little unclear) then I think you want something like this:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use List::Util 'max';
my (#data, #max);
while (<DATA>) {
chomp;
push #data, [split];
}
for my $i (0 .. $#{$data[0]}) {
push #max, max map { $_->[$i] } #data;
}
say "#max";
__DATA__
93 3 26 87 7
66 96 46 77 42
26 3 71 64 91
31 27 14 40 86
82 72 71 34 7
try this
map {push #temp, #{$_}} values %hash;
#desc_sorted= sort {$b <=> $a} #temp;
print $desc_sorted[0],"\n";
map will consolidate all lists to a single list and sort will sort that consolidated array in descending order.
I am reading an ordered file for which I must count by-hour, by-minute or by-second occurrences. If requested, I must print times with 0 occurrences (normalized output) or skip them (non-normalized output). The output must obviously be ordered.
I first thought using an array. When the output is non normalized, I am doing roughly the equivalent of:
#array[10] = 100;
#array[10000] = 10000;
And to print the result:
foreach (#array) {
print if defined;
}
Is there a way to reduce iterations to only elements defined in the array? In the previous example, that would mean doing only two iterations, instead of 10000 as using $#array implies. Then I would also need a way to know the current array index in a loop. Does such a thing exist?
I am thinking more and more to use a hash instead. Using a hash solves my problem and also eliminates the need to convert hh:mm:ss times to index and vice-versa.
Or do you have a better solution to suggest for this simple problem?
Yes, use a hash. You can iterate over the ordered array of the keys of the hash if your keys sort correctly.
You can also remember just the pairs of numbers in an array:
#!/usr/bin/perl
use warnings;
use strict;
my #ar = ( [ 10, 100 ],
[ 100, 99 ],
[ 12, 1 ],
[ 13, 2 ],
[ 15, 1 ],
);
sub normalized {
my #ar = sort { $a->[0] <=> $b->[0] } #_;
map "#$_", #ar;
}
sub non_normalized {
my #ar = sort { $a->[0] <=> $b->[0] } #_;
unshift #ar, [0, 0] unless $ar[0][0] == 0;
my #return;
for my $i (0 .. $#ar) {
push #return, "#{ $ar[$i] }";
push #return, $_ . $" . 0 for 1 + $ar[$i][0] .. $ar[$i + 1][0] - 1;
}
return #return;
}
print join "\n", normalized(#ar), q();
print "\n";
print join "\n", non_normalized(#ar), q();
I am new to perl. I want to store the values corresponding to the keys in the following fashion. Please see below for a sample input data. Could someone help me to do this in perl.
output:
key value
1 (11, 20)
2 (17, 15)
3 (10, 11)
Input data:
key value
2 17
3 10
1 11
1 20
2 15
3 11
You can store the data in a hash-of-arrays structure (perldoc perldsc):
use warnings;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
my %data;
while (<DATA>) {
my ($k, $v) = split;
push #{ $data{$k} } , $v;
}
print Dumper(\%data);
=for output
$VAR1 = {
'1' => [
'11',
'20'
],
'2' => [
'17',
'15'
],
'3' => [
'10',
'11'
]
};
=cut
__DATA__
2 17
3 10
1 11
1 20
2 15
3 11
Assuming that each of the lines in the input data is in
a string, use a hash which has unique keys by definition.
If the key exists in the hash push the value onto the arrayref.
If it doesn't exist assign an array reference to the key with the
value. Next time that key appears you'll push the value onto the arrayref.
my $hash = {};
foreach my $line ( #lines ) {
my ($key, $val) = split(/\s/, $line);
if( $hash->{$key} ) {
push( #{ $hash->{ $key } }, $val );
}
else {
$hash->{$key} = [ $val ];
}
}
Using oneliner:
$ echo "key value
2 17
3 10
1 11
1 20
2 15
3 11" | perl -anE'next if 1..1;push#{$h{$F[0]}},$F[1]}{say"key value";$"=", ";say"$_ (#{$h{$_}})"for sort{$a<=>$b}keys%h'
key value
1 (11, 20)
2 (17, 15)
3 (10, 11)
Updated:
After my initial post and responses, I managed to have another crack and have written out my aims and results a bit clearer:
Aim:
I'm attempting to count the number of hits in a search string of a log file to figure out how many occurrences of a message are generated in the following ways:
Total per day.
Total per hour.
Highest per min, per hour.
Highest per sec, per hour.
My working code:
#!/usr/bin/perl
#use strict;
use warnings;
use Data::Dumper;
my #a = (
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:44:47/ ],
[ qw /2012-02-21_09:44:47/ ],
[ qw /2012-02-22_09:44:49/ ],
[ qw /2012-02-21_10:44:49/ ]
);
my ( %count, $count ) = ();
foreach (#a) {
my $line = #$_[0] ;
$line =~ /(\S+)_(\d+):(\d+):(\d+)/ ;
my $day = $1;
my $hour= $2;
my $min = $3;
my $sec = $4;
$count {$day}->{$hour}->{$min}->{$sec}{'sec'} += 1 ;
$count {$day}->{$hour}->{$min}{'min'} += 1 ;
$count {$day}->{$hour}{'hour'} += 1 ;
$count {$day}{'day'} += 1 ;
}
#print Dumper (%count) . "\n";
foreach my $k1 ( sort keys %count ) {
print "$k1\t$count{$k1}{'day'}\n" ;
foreach my $k2 ( sort keys %{$count{$k1}} ) {
if ($k2 =~ /day/) {
next;
}
print " $k2:00\t\t$count{$k1}{$k2}->{'hour'}\n";
foreach my $k3 ( sort keys %{$count{$k1}{$k2}} ) {
if ($k3 =~ /hour/) {
next;
}
print " $k2:$k3\t\t$count{$k1}{$k2}{$k3}->{'min'}\n";
foreach my $k4 ( sort keys %{$count{$k1}{$k2}{$k3}} ) {
if ($k4 =~ /min/) {
next;
}
print " $k2:$k3:$k4\t$count{$k1}{$k2}{$k3}{$k4}->{'sec'}\n";
}
print "\n";
}
print "\n";
}
}
exit;
Results
I've had to turn off strict (of which I am ashamed), due to my poor hash dereference methods.
2012-02-21 12
09:00 11
09:43 9
09:43:43 2
09:43:44 3
09:43:45 4
09:44 2
09:44:47 2
10:00 1
10:44 1
10:44:49 1
Attempting to output:
2012-02-21 12
09:00 11
09:43 9
09:43:45 4
10:00 1
10:44 1
10:44:49 1
Questions:
Is there a better way of writing the code, and turning on strict?
How would I go about listing the highest occurrence of a hash value within a hash, in an attempt to list the highest number count only?
Thanks for all the previous posts, I couldn't have gotten this far without them.
Cheers,
Andy
It can be simplified somewhat (I also made some stylistic changes to improve readability):
my #data = (
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:43/ ]
);
my %counts;
foreach my $words (#data) {
my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ );
$counts{$day}->{$hour} += 1;
}
foreach my $day (keys %counts) {
foreach my $hour (keys %{ $counts{$day} }) {
print "Hour count for $day:$hour is: $counts{$day}->{$hour}\n";
}
}
The working part of the loop that is central to your query is this:
my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ );
# You don't need minutes/seconds, so don't match them
# On the other hand, it's better to match YYYY/MM/DD explicitly!
# A regexp match in a list context will return a list of captures!
# e.g. ($1, $2, ...)
$counts{$day}->{$hour} += 1;
# You need to merely add 1 to a value. No need to push ones on a list.
# Please note that if the data is not guaranteed to be perfectly formatted,
# you need to defend against non-matches:
$counts{$day}->{$hour} += 1 if (defined $day && defined $hour);
Here's the same code with comments added clarifying why I made the stylistic changes:
my #data = ( # Don't use #a - variable name should have meanings
[ qw /2012-02-21_09:43:43/ ], # Not sure why you are using an array ref with
[ qw /2012-02-21_09:43:43/ ], # just 1 element, but let's pretend that is OK
);
my %counts;
foreach my $words (#data) { # Almost never rely on $_ - less readable
my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ ;
$counts{$day}->{$hour} += 1; # You can omit "->" but that's less readable
}
foreach my $day (keys %counts) { # Always localize your variable to the block they need
foreach my $hour (keys %{ $counts{$day} }) {
print "Hour count for $day:$hour is: $counts{$day}->{$hour}\n";
}
}
You should consider using a module to parse your time stamps, such as DateTime::Format::Strptime.
use DateTime::Format::Strptime;
my $strp = new DateTime::Format::Strptime(
pattern => "%Y-%m-%d_%H:%M:%S"
);
my $t = $strp->parse_datetime("2012-02-21_09:43:43");
my $year = $t->year;
my $month = $t->month;
my $day = $t->day;
# ...etc
If you were to do something like:
for my $aref (#a) {
for my $line (#$aref) { # Note: better than $line = #$_[0]
my $t = $strp->parse_datetime($line);
my $key = sprintf "%s-%s", $t->year, $t->month;
push #{$count{$key}}, $t; # save the whole object in the array
}
}
for my $key (sort keys %count) {
my $count = #{$count{$key}}; # get size of array
for my $obj (#{$count{$key}}) { # list all the DateTime objects
my $hour = $obj->hour;
# etc ...
}
}
You could store all the data from the timestamps into DateTime objects, and use it later as required.
There is a problem with your regex to get the date.
As the date contains the character - you cant get the whole date with \d+
Instead you should use \S+ so that you get the whole date.
I am trying your code now...will update with further info
Update 1
I assume that you want to get the count per day and per hour.So tweaked the logic little bit
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my ( #a, $line, %count, $day, $hour, $min, $sec ) = ();
#a = (
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:47/ ],
[ qw /2012-02-21_09:43:47/ ],
[ qw /2012-02-21_09:43:49/ ],
[ qw /2012-02-21_10:43:49/ ],
);
foreach (#a) {
$line = #$_[0] ;
$line =~ /(\S+)_(\d+):(\d+):(\d+)/ ;
$day = $1;
$hour = $2;
$min = $3;
$sec = $4;
#$count{$day} += 1;
$count{$day}{$hour} += 1;
}
#print "Val is:".$count{$day}{$hour}."\n";
print Dumper (%count) . "\n";
foreach $day(keys%count)
{
#print "Day count $day is:".$count{$day}."\n";
foreach $hour(keys %{ $count{$day} })
{
print "Hour count $hour is:".$count{$day}{$hour}."\n";
}
}