在 Perl 中为每个 ID 添加数组中的所有值
Add all values in array for each ID in Perl
我有这个table:
NAME |12/31/2016|VALUE
AAA |1/31/2017 |10
AAA |2/1/2017 |20
AAA |2/2/2017 |30
AAA |2/3/2017 |40
AAA |2/4/2017 |50
NAME |2/9/2017 |VALUE
BBB |2/10/2017 |20
BBB |2/11/2017 |30
BBB |2/12/2017 |40
BBB |2/13/2017 |50
BBB |2/14/2017 |60
这就是我想要的输出:
NAME |DATE |VALUE
AAA |12/31/2016 |150
AAA |1/31/2017 |140
AAA |2/1/2017 |120
NAME |DATE |VALUE
BBB |2/9/2017 |200
BBB |2/10/2017 |180
BBB |2/11/2017 |150
我想要做的是,对于每个有效符号,(AAA
、BBB
) 我想要三行。
对于每一列的第一行,我想要添加所有值,
例如,AAA 的第 1 行值:
10+20+30+40+50 = 150
然后对于第 2 行,我只想将第二个值添加到最后一个值。
例如第 2 行的值 AAA
20+30+40+50 = 140
等等 BBB
.
我想向下移动日期以便 12/31/2016
匹配 AAA
,然后获取每行的前三个日期。
我目前有这个代码。但这没什么用。它只是给了我一堆数字。
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
use Data::Dumper;
sub uniq {
my %seen;
grep !$seen{$_}++, @_;
}
my %cashflow;
my %fields = (
ID => 0,
DATES => 1,
VALUE => 2,
);
my @total;
my @IDs;
my @uniqueIDs;
my @dates;
my @add;
my $i = 0;
my @values;
my $counter = 3;
open( FILE, "try.CSV" );
while ( my $line = <FILE> ) {
chomp( $line );
my @lineVals = split( /\|/, $line );
if ( $lineVals[ $fields{ID} ] !~ /^SYMBOL$/i ) {
push @IDs, $lineVals[ $fields{ID} ];
}
@uniqueIDs = uniq( @IDs );
#push all CASH FLOW AMOUNTS to @cashflow
if ( looks_like_number( $lineVals[ $fields{VALUE} ] ) ) {
$lineVals[ $fields{VALUE} ] =~ s/\r//;
push @total, $lineVals[ $fields{VALUE} ];
}
if ( $lineVals[ $fields{DATES} ] =~ /(\d{1,2})\/(\d{1,2})\/(\d{4})/ ) {
$lineVals[ $fields{DATES} ] = sprintf( '%04d%02d%02d', , , );
}
$cashflow{ uc $lineVals[ $fields{ID} ] }{DATES} = $lineVals[ $fields{DATES} ];
$cashflow{ uc $lineVals[ $fields{ID} ] }{VALUE} = $lineVals[ $fields{VALUE} ];
foreach my $ID ( @uniqueIDs ) {
foreach my $symb ( keys %cashflow ) {
if ( $ID = $symb ) {
if ( looks_like_number( $lineVals[ $fields{VALUE} ] ) ) {
$lineVals[ $fields{VALUE} ] =~ s/\r//;
push @total, $lineVals[ $fields{VALUE} ];
my $i = 0;
my $grand = 0;
foreach my $val ( @total ) {
while ( $i < $counter ) {
$grand += $val;
print "$grand \n";
$i++;
}
shift @total;
}
}
}
}
}
}
close FILE;
我真的被这个困住了。我不知道该怎么办。
可能的解决方案:
#!perl
use strict;
use warnings;
sub trim {
my ($str) = @_;
s!\A\s+!!, s!\s+\z!! for $str;
$str
}
my $file = 'try.CSV';
open my $fh, '<', $file or die "[=10=]: $file: $!\n";
my ($group_name, @dates, @values);
my $sum = 0;
my $print_group = sub {
return if !defined $group_name;
my $format = " %-6s|%-11s|%s\n";
printf $format, 'NAME', 'DATE', 'VALUE';
for my $date (@dates) {
printf $format, $group_name, $date, $sum;
$sum -= shift @values if @values;
}
};
while (my $line = readline $fh) {
my ($name, $date, $value) = map trim($_), split /\|/, $line;
if ($name eq 'NAME') {
$print_group->();
$group_name = undef;
@dates = $date;
@values = ();
$sum = 0;
next;
}
$group_name ||= $name;
push @dates, $date if @dates < 3;
push @values, $value if @values < 2;
$sum += $value;
}
$print_group->();
让我们回顾一下。
sub trim {
my ($str) = @_;
s!\A\s+!!, s!\s+\z!! for $str;
$str
}
用于从字符串中删除 leading/trailing 空格的辅助函数。我们在这里使用 !
作为 s
分隔符,因为 /
破坏了 SO 的语法突出显示。耸耸肩。
my $file = 'try.CSV';
open my $fh, '<', $file or die "[=12=]: $file: $!\n";
打开我们的输入文件。注意:我们使用词法变量 ($fh
) 而不是裸字文件句柄,并且我们使用 3-argument open。强烈建议这样做。我们还检查 open 的 return 值并在失败时生成一个很好的错误消息,包括无法打开的文件名 ($file
) 和失败的原因 ($!
).
my ($group_name, @dates, @values);
my $sum = 0;
我们设置了一些我们希望在循环迭代中保留的状态变量。 $group_name
是我们当前正在处理的组的名称,@dates
是我们目前看到的保存日期,@values
是我们目前看到的保存值。 $sum
是当前组中所有值的 运行 总和,它从 0
.
开始
my $print_group = sub {
return if !defined $group_name;
my $format = " %-6s|%-11s|%s\n";
printf $format, 'NAME', 'DATE', 'VALUE';
for my $date (@dates) {
printf $format, $group_name, $date, $sum;
$sum -= shift @values if @values;
}
};
用于打印单个组输出的辅助函数。如果 $group_name
没有设置,我们还没有处理当前组的任何输入,所以我们什么也不做,return。否则我们打印 NAME | DATE | VALUE
header,然后是 @dates
中每个元素的一行数据。对于每个 $date
,我们输出当前组名(例如 AAA
)、$date
和值的总和(所有这些都使用 printf
很好地格式化)。最初 $sum
是所有组值的总和,但在第一次迭代后我们开始从 @values
中减去值:如果输入中的值列表是 x1
,x2
, x3
, x4
, ..., 然后 $sum
最初是 x1 + x2 + x3 + x4 + ...
,这就是输出第一行中打印的内容。之后我们减去 x1
,所以下一行得到 x1 + x2 + x3 + x4 + ... - x1
,即 x2 + x3 + x4 + ...
。之后我们减去x2
,所以第三行数据得到x3 + x4 + ...
.
while (my $line = readline $fh) {
my ($name, $date, $value) = map trim($_), split /\|/, $line;
我们的主循环。我们读取一行输入,将其拆分为 |
,每个字段 trim。
if ($name eq 'NAME') {
$print_group->();
$group_name = undef;
@dates = $date;
@values = ();
$sum = 0;
next;
}
如果$name
是'NAME'
,这是一个新组的开始。打印当前组的输出(如果没有当前组,$print_group->()
什么也不做),然后将我们的状态变量重置为初始值,除了 @dates
,它被 $date
来自 header 行的值。然后开始循环的下一次迭代,因为我们已经完成了这一行。
$group_name ||= $name;
push @dates, $date if @dates < 3;
push @values, $value if @values < 2;
$sum += $value;
如果我们到了这里,这行不是新组的开始。如果尚未设置,我们设置 $group_name
。我们将 $date
添加到我们保存的日期列表中(但我们只需要 3 个日期,所以如果我们已经有 3 个则什么都不做)。我们将 $value
添加到保存值列表中(但我们只需要其中的 2 个)。最后,我们将 $value
添加到组中的总数 $sum
。
}
$print_group->();
在循环结束时我们也刚刚处理完一组,所以我们也需要在这里调用$print_group
。
这将按照您的要求进行。它将整个数据文件读入一个数组数组,并在打印之前操作该数组。这些块从末尾向后处理,以便在删除尾随行时其他块保持原位
此程序需要输入文件的路径作为命令行参数,并将结果写入 STDOUT
use strict;
use warnings 'all';
my @data = map [ /[^|\s]+/g ], <>;
# Make a list of the indices of all the header rows
my @headers = grep { $data[$_][0] eq 'NAME' } 0 .. $#data;
# Make a list of the indices of the first
# and last lines of all the data blocks
my @blocks = map {
[
$headers[$_] + 1,
$_ == $#headers ? $#data : $headers[$_+1] - 1
]
} 0 .. $#headers;
# Shift the second column down
# Replace the col2 header with 'DATE'
#
$data[$_][1] = $data[$_-1][1] for reverse 1 .. $#data;
$data[$_][1] = 'DATE' for @headers;
# Edit each block of data
#
for my $block ( reverse @blocks ) {
my ( $beg, $end ) = @$block;
# Calculate the block total
my $total = 0;
for ( $beg ... $end ) {
$total += $data[$_][2];
}
# Calculate the first three data values
for my $i ( $beg .. $beg + 2 ) {
my $next = $total - $data[$i][2];
$data[$i][2] = $total;
$total = $next;
}
# Remove everything except those three lines
splice @data, $beg+3, $end-$beg-2;
}
print join('|', @$_), "\n" for @data;
产出
NAME|DATE|VALUE
AAA|12/31/2016|150
AAA|1/31/2017|140
AAA|2/1/2017|120
NAME|DATE|VALUE
BBB|2/9/2017|200
BBB|2/10/2017|180
BBB|2/11/2017|150
我有这个table:
NAME |12/31/2016|VALUE
AAA |1/31/2017 |10
AAA |2/1/2017 |20
AAA |2/2/2017 |30
AAA |2/3/2017 |40
AAA |2/4/2017 |50
NAME |2/9/2017 |VALUE
BBB |2/10/2017 |20
BBB |2/11/2017 |30
BBB |2/12/2017 |40
BBB |2/13/2017 |50
BBB |2/14/2017 |60
这就是我想要的输出:
NAME |DATE |VALUE
AAA |12/31/2016 |150
AAA |1/31/2017 |140
AAA |2/1/2017 |120
NAME |DATE |VALUE
BBB |2/9/2017 |200
BBB |2/10/2017 |180
BBB |2/11/2017 |150
我想要做的是,对于每个有效符号,(AAA
、BBB
) 我想要三行。
对于每一列的第一行,我想要添加所有值,
例如,AAA 的第 1 行值:
10+20+30+40+50 = 150
然后对于第 2 行,我只想将第二个值添加到最后一个值。
例如第 2 行的值 AAA
20+30+40+50 = 140
等等 BBB
.
我想向下移动日期以便 12/31/2016
匹配 AAA
,然后获取每行的前三个日期。
我目前有这个代码。但这没什么用。它只是给了我一堆数字。
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
use Data::Dumper;
sub uniq {
my %seen;
grep !$seen{$_}++, @_;
}
my %cashflow;
my %fields = (
ID => 0,
DATES => 1,
VALUE => 2,
);
my @total;
my @IDs;
my @uniqueIDs;
my @dates;
my @add;
my $i = 0;
my @values;
my $counter = 3;
open( FILE, "try.CSV" );
while ( my $line = <FILE> ) {
chomp( $line );
my @lineVals = split( /\|/, $line );
if ( $lineVals[ $fields{ID} ] !~ /^SYMBOL$/i ) {
push @IDs, $lineVals[ $fields{ID} ];
}
@uniqueIDs = uniq( @IDs );
#push all CASH FLOW AMOUNTS to @cashflow
if ( looks_like_number( $lineVals[ $fields{VALUE} ] ) ) {
$lineVals[ $fields{VALUE} ] =~ s/\r//;
push @total, $lineVals[ $fields{VALUE} ];
}
if ( $lineVals[ $fields{DATES} ] =~ /(\d{1,2})\/(\d{1,2})\/(\d{4})/ ) {
$lineVals[ $fields{DATES} ] = sprintf( '%04d%02d%02d', , , );
}
$cashflow{ uc $lineVals[ $fields{ID} ] }{DATES} = $lineVals[ $fields{DATES} ];
$cashflow{ uc $lineVals[ $fields{ID} ] }{VALUE} = $lineVals[ $fields{VALUE} ];
foreach my $ID ( @uniqueIDs ) {
foreach my $symb ( keys %cashflow ) {
if ( $ID = $symb ) {
if ( looks_like_number( $lineVals[ $fields{VALUE} ] ) ) {
$lineVals[ $fields{VALUE} ] =~ s/\r//;
push @total, $lineVals[ $fields{VALUE} ];
my $i = 0;
my $grand = 0;
foreach my $val ( @total ) {
while ( $i < $counter ) {
$grand += $val;
print "$grand \n";
$i++;
}
shift @total;
}
}
}
}
}
}
close FILE;
我真的被这个困住了。我不知道该怎么办。
可能的解决方案:
#!perl
use strict;
use warnings;
sub trim {
my ($str) = @_;
s!\A\s+!!, s!\s+\z!! for $str;
$str
}
my $file = 'try.CSV';
open my $fh, '<', $file or die "[=10=]: $file: $!\n";
my ($group_name, @dates, @values);
my $sum = 0;
my $print_group = sub {
return if !defined $group_name;
my $format = " %-6s|%-11s|%s\n";
printf $format, 'NAME', 'DATE', 'VALUE';
for my $date (@dates) {
printf $format, $group_name, $date, $sum;
$sum -= shift @values if @values;
}
};
while (my $line = readline $fh) {
my ($name, $date, $value) = map trim($_), split /\|/, $line;
if ($name eq 'NAME') {
$print_group->();
$group_name = undef;
@dates = $date;
@values = ();
$sum = 0;
next;
}
$group_name ||= $name;
push @dates, $date if @dates < 3;
push @values, $value if @values < 2;
$sum += $value;
}
$print_group->();
让我们回顾一下。
sub trim {
my ($str) = @_;
s!\A\s+!!, s!\s+\z!! for $str;
$str
}
用于从字符串中删除 leading/trailing 空格的辅助函数。我们在这里使用 !
作为 s
分隔符,因为 /
破坏了 SO 的语法突出显示。耸耸肩。
my $file = 'try.CSV';
open my $fh, '<', $file or die "[=12=]: $file: $!\n";
打开我们的输入文件。注意:我们使用词法变量 ($fh
) 而不是裸字文件句柄,并且我们使用 3-argument open。强烈建议这样做。我们还检查 open 的 return 值并在失败时生成一个很好的错误消息,包括无法打开的文件名 ($file
) 和失败的原因 ($!
).
my ($group_name, @dates, @values);
my $sum = 0;
我们设置了一些我们希望在循环迭代中保留的状态变量。 $group_name
是我们当前正在处理的组的名称,@dates
是我们目前看到的保存日期,@values
是我们目前看到的保存值。 $sum
是当前组中所有值的 运行 总和,它从 0
.
my $print_group = sub {
return if !defined $group_name;
my $format = " %-6s|%-11s|%s\n";
printf $format, 'NAME', 'DATE', 'VALUE';
for my $date (@dates) {
printf $format, $group_name, $date, $sum;
$sum -= shift @values if @values;
}
};
用于打印单个组输出的辅助函数。如果 $group_name
没有设置,我们还没有处理当前组的任何输入,所以我们什么也不做,return。否则我们打印 NAME | DATE | VALUE
header,然后是 @dates
中每个元素的一行数据。对于每个 $date
,我们输出当前组名(例如 AAA
)、$date
和值的总和(所有这些都使用 printf
很好地格式化)。最初 $sum
是所有组值的总和,但在第一次迭代后我们开始从 @values
中减去值:如果输入中的值列表是 x1
,x2
, x3
, x4
, ..., 然后 $sum
最初是 x1 + x2 + x3 + x4 + ...
,这就是输出第一行中打印的内容。之后我们减去 x1
,所以下一行得到 x1 + x2 + x3 + x4 + ... - x1
,即 x2 + x3 + x4 + ...
。之后我们减去x2
,所以第三行数据得到x3 + x4 + ...
.
while (my $line = readline $fh) {
my ($name, $date, $value) = map trim($_), split /\|/, $line;
我们的主循环。我们读取一行输入,将其拆分为 |
,每个字段 trim。
if ($name eq 'NAME') {
$print_group->();
$group_name = undef;
@dates = $date;
@values = ();
$sum = 0;
next;
}
如果$name
是'NAME'
,这是一个新组的开始。打印当前组的输出(如果没有当前组,$print_group->()
什么也不做),然后将我们的状态变量重置为初始值,除了 @dates
,它被 $date
来自 header 行的值。然后开始循环的下一次迭代,因为我们已经完成了这一行。
$group_name ||= $name;
push @dates, $date if @dates < 3;
push @values, $value if @values < 2;
$sum += $value;
如果我们到了这里,这行不是新组的开始。如果尚未设置,我们设置 $group_name
。我们将 $date
添加到我们保存的日期列表中(但我们只需要 3 个日期,所以如果我们已经有 3 个则什么都不做)。我们将 $value
添加到保存值列表中(但我们只需要其中的 2 个)。最后,我们将 $value
添加到组中的总数 $sum
。
}
$print_group->();
在循环结束时我们也刚刚处理完一组,所以我们也需要在这里调用$print_group
。
这将按照您的要求进行。它将整个数据文件读入一个数组数组,并在打印之前操作该数组。这些块从末尾向后处理,以便在删除尾随行时其他块保持原位
此程序需要输入文件的路径作为命令行参数,并将结果写入 STDOUT
use strict;
use warnings 'all';
my @data = map [ /[^|\s]+/g ], <>;
# Make a list of the indices of all the header rows
my @headers = grep { $data[$_][0] eq 'NAME' } 0 .. $#data;
# Make a list of the indices of the first
# and last lines of all the data blocks
my @blocks = map {
[
$headers[$_] + 1,
$_ == $#headers ? $#data : $headers[$_+1] - 1
]
} 0 .. $#headers;
# Shift the second column down
# Replace the col2 header with 'DATE'
#
$data[$_][1] = $data[$_-1][1] for reverse 1 .. $#data;
$data[$_][1] = 'DATE' for @headers;
# Edit each block of data
#
for my $block ( reverse @blocks ) {
my ( $beg, $end ) = @$block;
# Calculate the block total
my $total = 0;
for ( $beg ... $end ) {
$total += $data[$_][2];
}
# Calculate the first three data values
for my $i ( $beg .. $beg + 2 ) {
my $next = $total - $data[$i][2];
$data[$i][2] = $total;
$total = $next;
}
# Remove everything except those three lines
splice @data, $beg+3, $end-$beg-2;
}
print join('|', @$_), "\n" for @data;
产出
NAME|DATE|VALUE
AAA|12/31/2016|150
AAA|1/31/2017|140
AAA|2/1/2017|120
NAME|DATE|VALUE
BBB|2/9/2017|200
BBB|2/10/2017|180
BBB|2/11/2017|150