我有这张桌子:
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行我想从第二个值添加到最后一个。
例如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', $3, $2, $1 );
}
$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;
我真的坚持这个。我不知道如何处理这个问题。
答案 0 :(得分:4)
可能的解决方案:
#!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 "$0: $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
}
用于从字符串中删除前导/尾随空格的辅助函数。我们在这里使用!
作为s
分隔符,因为/
打破了SO的语法突出显示。耸肩。
my $file = 'try.CSV';
open my $fh, '<', $file or die "$0: $file: $!\n";
打开我们的输入文件。注意:我们使用词法变量($fh
)而不是裸字文件句柄,我们使用3参数打开。强烈建议这样做。我们还检查open的返回值并在出现故障时生成一条很好的错误消息,包括无法打开的文件的名称($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
,我们尚未处理当前组的任何输入,因此我们不执行任何操作并返回。否则,我们打印NAME | DATE | VALUE
标题,然后是@dates
中每个元素的一行数据。对于每个$date
,我们输出当前的组名称(例如AAA
),$date
和值的总和(所有这些都使用printf
进行了很好的格式化)。最初$sum
是所有组值的总和,但在第一次迭代后,我们开始从@values
中减去值:如果输入中的值列表为x1
,{{1} },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;
,然后修剪每个字段。
|
如果 if ($name eq 'NAME') {
$print_group->();
$group_name = undef;
@dates = $date;
@values = ();
$sum = 0;
next;
}
为$name
,则这是新群组的开头。如果有的话打印当前组的输出(如果没有当前组,则'NAME'
什么都不做),然后将我们的状态变量重置回初始值,$print_group->()
除外,@dates
填充$date
标题行中的1}}值。然后开始循环的下一次迭代,因为我们完成了这一行。
$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
。
答案 1 :(得分:2)
这会按照你的要求行事。它将整个数据文件读入一个数组数组,并在打印之前操作该数组。从末尾向后处理块,以便在删除尾随行时其他块保持原位
此程序需要输入文件的路径作为命令行上的参数,并将结果写入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