I have a Perl script that returns this hash structure :
$VAR1 = {
'Week' => [
'1238',
{
'OUT3FA_5' => 65,
'OUT3A_5' => 20,
'OUT3-Fix' => 45,
'IN1' => 85
},
'1226',
{
'OUT3FA_5' => 30,
'OUT3A_5' => 5,
'OUT3-Fix' => 25,
'IN1' => 40
}
]
};
What I'd like to do is, count the total of IN1
for each week, per example in this case it'll return:
$VAR1 = {
'Week' => [
'1238',
{
'OUT3FA_5' => 65,
'Total_IN1' => 85,
'OUT3A_5' => 20,
'OUT3-Fix' => 45,
'IN1' => 85
},
'1226',
{
'OUT3FA_5' => 30,
'Total_IN1' => 125,
'OUT3A_5' => 5,
'OUT3-Fix' => 25,
'IN1' => 40
}
]
};
And so on for each week.
How can I do this please? Any help would be appreciated.
Here is what I tried to do but it's not working :
my @sum_IN1 = qw(IN1); #kinda useless to use an array just for one value...
for my $num (keys %hash) {
my $found;
my $sum = 0;
for my $key (@sum_IN1) {
next unless exists $hash{$num}{$key};
$sum += $hash{$num}{$key};
$found = 1;
}
$hash{$num}{Total_IN1} = $sum if $found;
}
First, your data structure is confusing. 'Week'
is a reference to an array, some of whose elements are strings (such as '1238'
) and the rest of whose elements are hash references.
While Perl lets you get away with this, it would be a better design for each level of your data structure to hold only one kind of thing. This is something to consider. However, I will leave it as is for now.
Here is a quick way to do it:
my $ttl = 0;
$_->{'In1Total'} = $ttl+=$_->{'IN1'} || 0 for(grep {ref $_} @{$VAR1->{'Week'}});
use Data::Dumper;
print Dumper $VAR1;
Update: changed //
to ||
as Mikko L suggested.
Explanation:
grep {ref $_}
gets only the elements that are hash references out of the array.
$_->{'IN1'} || 0
$_->{'IN1'} || 0
- If one of the hashes did not have 'IN1'
, this would use zero instead. This is basically a check for the hash key being defined. ||
is acceptable to do this for hash keys. In other situations, however, you need the defined-or operator ( //
, available from version 5.10 I believe).
$_->{'In1Total'} = $total+=$_->{'IN1'} || 0
$_->{'In1Total'} = $total+=$_->{'IN1'} || 0
this adds the current value of IN1
to the count, then puts the result into In1Total
. Admittedly this could be made a bit clearer by separating it into a couple of lines.
Update 2: fixed mistake Borodin pointed out.
You need to keep a state variable holding the running total for each item in the array. I also suspect that you Week
array is supposed to be a hash?
use strict;
use warnings;
use Data::Dump;
my $data = {
Week => [
1238,
{ "IN1" => 85, "OUT3-Fix" => 45, "OUT3A_5" => 20, "OUT3FA_5" => 65 },
1226,
{ "IN1" => 40, "OUT3-Fix" => 25, "OUT3A_5" => 5, "OUT3FA_5" => 30 },
],
};
my $week = $data->{Week};
# Sort the array entry pairs by week number
#
my @pairs;
push @pairs, [ splice @$week, 0, 2 ] while @$week;
@$week = ();
for my $pair (sort { $a->[0] <=> $b->[0] } @pairs) {
push @$week, @$pair;
}
# Calculate the running totals of IN1
#
my $total = 0;
for my $item (@$week) {
next unless ref $item eq 'HASH' and exists $item->{IN1};
$total += $item->{IN1};
$item->{Total_IN1} = $total;
}
dd $data;
output
{
Week => [
1226,
{
"IN1" => 40,
"OUT3-Fix" => 25,
"OUT3A_5" => 5,
"OUT3FA_5" => 30,
"Total_IN1" => 40,
},
1238,
{
"IN1" => 85,
"OUT3-Fix" => 45,
"OUT3A_5" => 20,
"OUT3FA_5" => 65,
"Total_IN1" => 125,
},
],
}
Without seeing your code, it is hard to see why it is not working, but it may be because you are using a hash in your code, but the data structure returned is a hash reference.
Here is a version which should work (using hash references):
#!/usr/bin/env perl
use warnings;
use strict;
use Data::Dumper;
use List::MoreUtils qw( natatime );
my $data = {
'Week' => [
'1238',
{ 'IN1' => 85,
'OUT3FA_5' => 65,
'OUT3A_5' => 20,
'OUT3-Fix' => 45
},
'1226',
{ 'IN1' => 40,
'OUT3FA_5' => 30,
'OUT3A_5' => 5,
'OUT3-Fix' => 25
}
]
};
for my $key ( keys %$data ) {
my $weekly_data = $data->{$key};
my $total = 0;
my $iter = natatime 2, @$weekly_data;
while ( my ( $id, $daily_data ) = $iter->() ) {
next unless $daily_data->{IN1};
$total += $daily_data->{IN1};
$daily_data->{Total_IN1} = $total;
}
}
print Dumper($data);
1;
Here's the output:
$VAR1 = {
'Week' => [
'1238',
{
'OUT3FA_5' => 65,
'Total_IN1' => 85,
'OUT3A_5' => 20,
'OUT3-Fix' => 45,
'IN1' => 85
},
'1226',
{
'OUT3FA_5' => 30,
'Total_IN1' => 125,
'OUT3A_5' => 5,
'OUT3-Fix' => 25,
'IN1' => 40
}
]
};
Maybe you could learn something from a modification of your code, which, btw, works perfectly. The problem is that you treat the data structure wrong. This:
# initial data structure
my $data = {
'Week' => [
'1238',
{
'IN1' => 85,
'OUT3FA_5' => 65,
'OUT3A_5' => 20,
'OUT3-Fix' => 45
},
'1226',
{
'IN1' => 40,
'OUT3FA_5' => 30,
'OUT3A_5' => 5,
'OUT3-Fix' => 25
},
],
};
Is a hash reference. In the referenced hash, there's a key Week
that points to an array reference of key-value pairs. So this should be a hash instead:
# create a Week hash from the even-sized list in $data->{Week}
my %week = @{$data->{Week}};
The I just needed to replace some variable names in your code:
my @sum_IN1 = qw(IN1);
for my $num (keys %week) {
my $found;
my $sum = 0;
for my $key (@sum_IN1) {
next unless exists $week{$num}{$key};
$sum += $week{$num}{$key};
$found = 1;
}
$week{$num}{Total_IN1} = $sum if $found;
}
print Dumper \%week;
That works fine! Output (in wrong order, but you can sort
it easily):
$VAR1 = {
'1238' => {
'OUT3FA_5' => 65,
'Total_IN1' => 85,
'OUT3A_5' => 20,
'OUT3-Fix' => 45,
'IN1' => 85
},
'1226' => {
'OUT3FA_5' => 30,
'Total_IN1' => 40,
'OUT3A_5' => 5,
'OUT3-Fix' => 25,
'IN1' => 40
}
};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.