【问题标题】:How can add values in each row and column and print at the end in Perl?如何在 Perl 的每一行和每一列中添加值并在末尾打印?
【发布时间】:2009-11-16 13:37:43
【问题描述】:

以下是示例 csv 文件

date,type1,type2,.....
2009-07-01,n1,n2,.....
2009-07-02,n21,n22,....
and so on...

我想在每行和每列中添加值并在每行的末尾和底部打印。即

date,type1,type2
2009-07-01,n1,n2,.....row_total1
2009-07-02,n21,n22,....row_total2
Total,col_total1,col_total1,......total

请提出建议。

【问题讨论】:

    标签: perl csv


    【解决方案1】:

    不那么优雅,更短:

    $ perl -plaF, -e '$r=0;$r+=$F[$_],$c[$_]+=$F[$_]for 1..$#F;$_.=",$r";END{$c[0]="Total";print join",",@c}'
    

    快速而肮脏,但在基本情况下应该可以解决问题。对于更复杂的内容,请使用 Text::CSV 和实际脚本。

    一个扩展版本,因为它有点毛茸茸的:

    #! perl -plaF,
    $r=0;
    $r+=$F[$_], $c[$_]+=$F[$_] for 1..$#F;
    $_.=",$r";
    END { $c[0]="Total"; print join ",", @c }'
    

    【讨论】:

    • 我认为你的意思是 'perl -plaF,'(在某些 shell 中可能需要逗号前的反斜杠)
    • 没错。那和其他一些修复;)谢谢
    • 谢谢,这给了我每列的附加值,如何为每一行使用它。
    【解决方案2】:

    这是一种简单的方法,您可以根据自己的要求轻松构建:

    use strict;
    use warnings;
    use 5.010;
    use List::Util qw(sum);
    use List::MoreUtils qw(pairwise);
    use Text::ParseWords;
    
    our ($a, $b);
    my @header = parse_csv( scalar <DATA> );
    my @total  = (0) x @header;
    output_csv( @header, 'row_total' );
    
    for my $line (<DATA>) {
        my @cols  = parse_csv( $line );
        my $label = shift @cols;
        push @cols, sum @cols;
        output_csv( $label, @cols );
        @total = pairwise { $a + $b } @total, @cols;
    }
    
    output_csv( 'Total', @total );
    
    sub parse_csv { 
        chomp( my $data = shift );
        quotewords ',', 0, $data; 
    }
    
    sub output_csv { say join ',' => @_ }
    
    __DATA__
    date,type1,type2
    2009-07-01,1,2
    2009-07-02,21,22
    

    输出预期:

    date,type1,type2,row_total
    2009-07-01,1,2,3
    2009-07-02,21,22,43
    Total,22,24,46
    

    上面有些东西是List::UtilList::MoreUtils的使用:

    # using List::Util::sum
    my $sum_of_all_values_in_list = sum @list;
    
    # using List::MoreUtils::pairwise
    my @two_arrays_added_together = pairwise { $a + $b } @array1, @array2;
    

    此外,虽然我在示例中使用了 Text::ParseWords,但您确实应该考虑使用 Text::CSV。该模块涵盖了更奇怪的 CSV 边缘情况,还提供了正确的 CSV 组合(我的 output_csv() 子非常天真!)。

    /I3az/

    【讨论】:

      【解决方案3】:

      类似于 JB 的 perlgolf 候选,除了打印结束行总数和标签。

      #!/usr/bin/perl -alnF,
      use List::Util qw(sum);
      chomp;
      push @F, $. == 1 ? "total" : sum(@F[1..$#F]);
      print "$_,$F[-1]";
      for (my $i=1;$i<@F;$i++) {
          $totals[$i] += $F[$i];
      }
      END {
          $totals[0] = "Total";
          print join(",",@totals);
      };
      

      【讨论】:

        【解决方案4】:

        这是需要在 Perl 脚本中确定的事情吗?在 Perl 中没有“快速而肮脏”的方法可以做到这一点。您将需要读入文件,累积总数,然后将文件写回(逐行处理输入和输出将是最干净的)。

        如果这是一份一次性报告,或者您正在与有能力的用户群合作,则可以使用 Excel 等电子表格程序最轻松地生成您想要的数据。

        【讨论】:

          【解决方案5】:

          每当我使用 CSV 时,我都会使用 AnyData 模块。它可能会增加一些开销,但它可以防止我犯错误(“哦,废话,那个日期列被引用并且里面有逗号!?”)。
          你的过程看起来像这样:

          use AnyData;
          my @columns = qw/date type1 type2 type3/;  ## Define your input columns.
          my $input = adTie( 'CSV', 'input_file.csv', 'r', {col_names => join(',', @columns)} );
          push @columns, 'total';  ## Add the total columns.
          my $output = adTie( 'CSV', 'output_file.csv', 'o', {col_names => join(',', @columns)} );
          my %totals;
          while ( my $row = each %$input ) {
              next if ($. == 1);  ## Skip the header row.  AnyData will add it to the output.
              my $sum = 0;
              foreach my $col (@columns[1..3]) {
                  $totals{$col} += $row->{$col};
                  $sum += $row->{$col};
              }
              $totals{total} += $sum;
              $row->{total} = $sum;
              $output->{$row->{date}} = $row;
          }
          $output->{Total} = \%totals;
          print adDump( $output ); ## Prints a little table to see the data.  Not required.
          undef $input; ## Close the file.
          undef $output;
          

          输入:

          date,type1,type2,type3
          2009-07-01,1,2,3
          2009-07-03,31,32,33
          2009-07-06,61,62,63
          "Dec 31, 1969",81,82,83
          

          输出:

          date,type1,type2,type3,total
          2009-07-01,1,2,3,6
          2009-07-03,31,32,33,96
          2009-07-06,61,62,63,186
          "Dec 31, 1969",81,82,83,246
          Total,174,178,182,534
          

          【讨论】:

            【解决方案6】:

            Perl 中的以下内容可以满足您的需求,它并不优雅,但可以:-) 以输入文件为参数调用脚本,结果为标准输出。

            chop($_ = <>);
            
            print "$_,Total\n";
            
            while (<>) {
            
                chop;
                split(/,/);
                shift(@_);
            
                $sum = 0;
            
                for ($n = 0; 0 < scalar(@_); $n++) {
                    $c = shift(@_);
                    $sum += $c;
                    $sums[$n] += $c;
                }
            
                $total += $sum;
            
                print "$_,$sum\n";
            }
            
            print "Total";
            
            for ($n = 0; $n <= $#sums; $n++) {
            
                print "," . $sums[$n];
            }
            
            print ",$total\n";
            

            编辑:固定为 0 值。

            输出是这样的:

            date,type1,type2,type3,Total
            2009-07-01,1, 2, 3,6
            2009-07-02,4, 5, 6,15
            Total,5,7,9,21
            

            【讨论】:

            • 谢谢,这可行,但可以计算值直到 0 出现。您能否建议如何添加具有 0 值的字段。
            • 不要使用chop,使用chomp
            • @Sinan,不要因为这样一个小问题而贬低。 (并非所有 Perl 版本都有 chomp)
            • @rsp:我还面临一个问题。如果下一列有空列,则其添加值直到为空。你能帮帮我吗?
            • @Virus,已编辑以解决该问题,我更改了确定何时到达列末尾的方法。
            猜你喜欢
            • 1970-01-01
            • 2017-08-08
            • 2012-07-03
            • 2014-05-29
            • 1970-01-01
            • 2016-01-03
            • 2010-10-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多