【问题标题】:Automatically sum numeric columns and print total自动对数字列求和并打印总计
【发布时间】:2010-11-17 23:51:55
【问题描述】:

给定git ... --stat的输出:

 3 files changed, 72 insertions(+), 21 deletions(-)
 3 files changed, 27 insertions(+), 4 deletions(-)
 4 files changed, 164 insertions(+), 0 deletions(-)
 9 files changed, 395 insertions(+), 0 deletions(-)
 1 files changed, 3 insertions(+), 2 deletions(-)
 1 files changed, 1 insertions(+), 1 deletions(-)
 2 files changed, 57 insertions(+), 0 deletions(-)
 10 files changed, 189 insertions(+), 230 deletions(-)
 3 files changed, 111 insertions(+), 0 deletions(-)
 8 files changed, 61 insertions(+), 80 deletions(-)

我想生成数字列的总和,但保留行的格式。 为了方便起见,我制作了这个 awk 脚本,它自动对任何数字列求和并生成一个摘要行:

{
    for (i = 1; i <= NF; ++i) {
        if ($i + 0 != 0) {
            numeric[i] = 1;
            total[i] += $i;
        }
    }
}
END {
    # re-use non-numeric columns of last line
    for (i = 1; i <= NF; ++i) {
        if (numeric[i])
            $i = total[i]
    }
    print
}

产量:

 44 files changed, 1080 insertions(+), 338 deletions(-)

Awk 有几个可以简化问题的特性,例如自动字符串->数字转换、所有数组作为关联数组,以及覆盖自动拆分位置参数然后打印等效行的能力。

对于这个 hack 有更好的语言吗?

【问题讨论】:

  • 这确实变成了一个有趣的代码高尔夫问题,但我觉得我应该问你在这里想要完成什么。您的“X 文件已更改”行是否来自 git log?如果是这样,如果您在多个变更集中更改了同一个文件,那么简单地将更改的文件数相加是不准确的。如果你想找出两点之间发生了什么,你可能需要 git diff --stat .
  • @Mike:“X 文件已更改”肯定是错误的,其他总数接近我想要的。有问题的修订不是完全按顺序进行的。

标签: language-agnostic awk code-golf


【解决方案1】:

Perl - 47 个字符

受到 ChristopheD 的 awk 解决方案的启发。与-an 命令行开关一起使用。 43 个字符 + 4 个用于命令行开关的字符:

$i-=@a=map{($b[$i++]+=$_)||$_}@F}{print"@a"

我可以通过一点作弊将它提高到 45(41 + -ap 开关):

$i=0;$_="Ctrl-M@{[map{($b[$i++]+=$_)||$_}@F]}"

较旧的、基于散列的 66 字符解决方案:

@a=(),s#(\d+)(\D+)#$b{$a[@a]=$2}+=$1#gefor<>;print map$b{$_}.$_,@a

【讨论】:

  • 我还没有弄清楚它是如何工作的,但我注意到真正的输入恰好以空格开头(直接来自 git),这会插入一个虚假的“0”列:0 44 files changed, 1080 insertions(+), 338 deletions(-)
  • @Ben Jackson - 真可惜 - 我会看看我能做什么
  • 我是否将“gefor”正确解读为s///ge for?我不知道 perl 会在有效替换选项的末尾找到这样的标记!
【解决方案2】:

鲁比 — 87

puts ' '+[*$<].map(&:split).inject{|i,j|[0,3,5].map{|k|i[k]=i[k].to_i+j[k].to_i};i}*' '

【讨论】:

  • 哇。 +1 为[*$&lt;] 这是真棒高尔夫球。我想这是可行的,因为$&lt; 定义了一个读取所有内容的.to_a 方法?因为 splat 显然调用了.to_a? (我不确定这个响应是否真的符合条件,因为你硬连线了这些列,但我仍然喜欢它。)
  • 硬编码的列号?哇!当您使用它时,为什么不缩写为puts '44 files changed, 1080 insertions(+), 338 deletions(-)'
  • 我的原件会自动检测数字列。我正在考虑制作一个通用工具,可以保留在我的 bin 目录中,以汇总任何输出的列。
【解决方案3】:

Python - 101 个字符

import sys
print" ".join(`sum(map(int,x))`if"A">x[0]else x[0]for x in zip(*map(str.split,sys.stdin)))'

使用 reduce 会更长,为 126 个字符

import sys
print" ".join(reduce(lambda X,Y:[str(int(x)+int(y))if"A">x[0]else x for x,y in zip(X,Y)],map(str.split,sys.stdin)))

【讨论】:

    【解决方案4】:

    AWK - 63 个字符

    (在 bash 脚本中,$1 是作为命令行参数提供的文件名):

    awk -F' ' '{x+=$1;y+=$4;z+=$6}END{print x,$2,$3,y,$5,z,$7}' $1
    

    当然也可以通过管道输入(如果允许的话,还会保存另外 3 个字符)。

    【讨论】:

    • 我认为“程序”是“{ ..}”之间的东西,所以我数了 47 个字符。干得好!
    • 输出中缺少初始空间。
    • 这不灵活,列是硬编码的
    • 这个问题定义得太少,无法提供通用、灵活的解决方案(不对格式做很多假设)恕我直言......
    • @Nas 好吧……一列就是一列……两个空隙之间的东西……你如何对其进行软编码? :)
    【解决方案5】:

    这个问题没有挑战性或难度……但它很“可爱”。

    这是 Python 中的解决方案:

    import sys
    r = []
    for s in sys.stdin:
        r = map(lambda x,y:(x or 0)+int(y) if y.isdigit() else y, r, s.split())
    print ' '.join(map(str, r))
    

    它有什么作用...它在逐行进行时保持在r 中的计数。拆分行,然后对于列表的每个元素,如果它是一个数字,则将其添加到计数中或将其保留为字符串。最后,它们都被重新映射到字符串并与要打印的空格合并。

    替代的,更“代数”的实现,如果我们不关心一次读取所有输入:

    import sys
    
    def totalize(l):
        try:    r = str(sum(map(int,l)))
        except: r = l[-1]
        return r
    
    print ' '.join(map(totalize, zip(*map(str.split, sys.stdin))))
    

    这个是做什么的? totalize() 获取字符串列表并尝试计算数字的总和;如果失败,它只会返回最后一个。 zip() 输入了一个矩阵,该矩阵是行列表,每个行都是行中的列项列表 - zip 转置矩阵,使其变成列项列表,然后在每一列上调用 totalize,然后结果像以前一样加入。

    【讨论】:

    • 唯一的反对意见:Awk 版本也可以处理浮点数。或许不是什么难的问题,但是我确实学到了一些我不知道的python!
    • @Ben Jackson:使用第二种解决方案,将float 替换为int
    【解决方案6】:

    以使您的代码稍长一些为代价,我将主要解析移到了BEGIN 子句中,因此主要子句仅处理数字字段。对于稍大的输入文件,我能够测量到速度的显着提高。

    BEGIN {
        getline
        for (i = 1; i <= NF; ++i) {
            # need to test for 0, too, in this version
            if ($i == 0 || $i + 0 != 0) {
                numeric[i] = 1;
                total[i] = $i;
            }
        }
    }
    {
        for (i in numeric) total[i] += $i
    }
    END {
        # re-use non-numeric columns of last line
        for (i = 1; i <= NF; ++i) {
            if (numeric[i])
                $i = total[i]
        }
        print
    }
    

    我使用您的数据制作了一个测试文件,并执行了paste file file file ...cat file file file ...,因此结果有 147 个字段和 1960 条记录。我的版本大约是你的版本的 1/4。在原始数据上,差异无法衡量。

    【讨论】:

    • 不知何故,我从来没有想过BEGIN { getline ... }init == 0 { ...; init = 1} 之类的技巧更干净。不过,我确实喜欢原版忽略标题的能力。
    【解决方案7】:

    JavaScript (Rhino) - 183 154 139 字节

    打高尔夫球:

    x=[n=0,0,0];s=[];readFile('/dev/stdin').replace(/(\d+)(\D+)/g,function(a,b,c){x[n]+=+b;s[n++]=c;n%=3});print(x[0]+s[0]+x[1]+s[1]+x[2]+s[2])
    

    可读性:

    x=[n=0,0,0];
    s=[];
    
    readFile('/dev/stdin').replace(/(\d+)(\D+)/g,function(a,b,c){
        x[n]+=+b;
        s[n++]=c;
        n%=3
    });
    
    print(x[0]+s[0]+x[1]+s[1]+x[2]+s[2]);
    

    【讨论】:

      【解决方案8】:

      PHP 152 130 个字符

      输入:

      $i = "
      3 files changed, 72 insertions(+), 21 deletions(-) 
      3 files changed, 27 insertions(+), 4 deletions(-) 
      4 files changed, 164 insertions(+), 0 deletions(-) 
      9 files changed, 395 insertions(+), 0 deletions(-) 
      1 files changed, 3 insertions(+), 2 deletions(-) 
      1 files changed, 1 insertions(+), 1 deletions(-) 
      2 files changed, 57 insertions(+), 0 deletions(-) 
      10 files changed, 189 insertions(+), 230 deletions(-) 
      3 files changed, 111 insertions(+), 0 deletions(-) 
      8 files changed, 61 insertions(+), 80 deletions(-)";
      

      代码:

      $a = explode(" ", $i);
      
      foreach($a as $k => $v){
          if($k % 7 == 0)
              $x += $v;
      
          if(3-$k % 7 == 0)
              $y += $v;
      
          if(5-$k % 7 == 0)
              $z += $v;   
      
      }
      
      echo "$x $a[1] $a[2] $y $a[4] $z $a[6]";
      

      输出:

      44 files changed, 1080 insertions(+), 338 deletions(-)
      

      注意:explode() 将要求在新行之前有一个空格字符。

      【讨论】:

        【解决方案9】:

        Haskell - 151 135 字节

        import Char
        c a b|all isDigit(a++b)=show$read a+read b|True=a
        main=interact$unwords.foldl1(zipWith c).map words.filter(not.null).lines
        

        ...但我相信它可以做得更好/更小。

        【讨论】:

        • 就在我的脑海中:我想硬编码列的位置并使用读取进行转换会更短。
        【解决方案10】:

        Lua,140 字节

        我知道 Lua 不是最好的高尔夫语言,但与运行时的大小相比,我认为它做得很好。

        f,i,d,s=0,0,0,io.read"*a"for g,a,j,b,e,c in s:gmatch("(%d+)(.-)(%d+)(.-)(%d+)(.-)")do f,i,d=f+g,i+j,d+e end print(table.concat{f,a,i,b,d,c})
        

        【讨论】:

          【解决方案11】:

          PHP,176 166 164 159 158 153

          for($a=-1;$a<count($l=explode("
          ",$i));$r=explode(" ",$l[++$a]))for($b=-1;$b<count($r);$c[++$b]=is_numeric($r[$b])?$c[$b]+$r[$b]:$r[$b]);echo join(" ",$c);
          

          然而,这需要 $i 中的整个输入... $i 的变体替换为 $_POST["i"] 所以它将在文本区域中发送...具有 162 个字符:

          for($a=-1;$a<count($l=explode("
          ",$_POST["i"]));$r=explode(" ",$l[$a++]))for($b=0;$b<count($r);$c[$b]=is_numeric($r[$b])?$c[$b]+$r[$b]:$r[$b])$b++;echo join(" ",$c);
          

          这是一个版本

          没有硬编码的列

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-12-24
            • 2013-01-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-12-19
            相关资源
            最近更新 更多