【问题标题】:Add all values in array for each ID in Perl在 Perl 中为每个 ID 添加数组中的所有值
【发布时间】:2017-09-24 08:54:21
【问题描述】:

我有这张桌子:

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

我想要做的是,对于每个有效符号,(AAABBB)我想要三行。

对于每一列的第一行,我想要添加所有值,

例如,AAA 的第 1 行值:

10+20+30+40+50 = 150

然后对于第 2 行,我只想从第二个值添加到最后一个值。

例如 AAA 的第 2 行值

20+30+40+50 = 140

BBB 也是如此。

我想将日期向下移动,以便 12/31/2016AAA 匹配,然后获取每行的前三个日期。

我目前有这个代码。但这并没有多大作用。它只是给了我一堆数字。

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;

我真的很纠结这个。我不知道如何处理这个问题。

【问题讨论】:

标签: arrays perl hash sum shift


【解决方案1】:

一个可能的解决方案:

#!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 中减去这些值:如果输入中的值列表是x1x2x3、@987654349 @, ...,然后 $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-&gt;() 如果没有当前组,则不执行任何操作),然后将我们的状态变量重置为初始值,@dates 除外,它填充了来自标题行。然后开始循环的下一次迭代,因为我们已经完成了这一行。

    $group_name ||= $name;
    push @dates, $date if @dates < 3;
    push @values, $value if @values < 2;
    $sum += $value;

如果我们到了这里,这条线并不是一个新组的开始。如果尚未设置,我们设置$group_name。我们将$date 添加到我们保存的日期列表中(但我们只需要 3 个日期,所以如果我们已经有 3 个则什么也不做)。我们将$value 添加到我们保存的值列表中(但我们只需要其中的两个)。最后,我们将$value 添加到组内的$sum 总数中。

}
$print_group->();

在循环结束时,我们也刚刚完成了一个组的处理,所以我们也需要在这里调用$print_group

【讨论】:

  • 感谢您的快速回答。然而。这给了我这个结果:参数“CASH”不是数字加法(+)名称|日期|值符号|12/31/2016 |900 参数“现金”第 19 行。符号|2017 年 1 月 31 日|900 符号|2/1/2017 |890
  • @shia1717 我不确定你指的是什么。您的示例数据中没有CASH,我的代码第19行不包含+
  • 哦,对不起,那是 VALUE。
  • 你失去了我。您使用什么输入数据?
  • 我正在使用另一个。 (掌心)。对不起。我又试了一次,效果很好!非常感谢!
【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多