计算某个字符串在多列中出现 n 次的行数

计算某个字符串在多列中出现 n 次的行数

我有数百个文本文件,每个文件由五个制表符分隔的列组成。第一列包含一个索引,后面四列包含出现次数。现在我想计算包含 3 列且值为 0 的行数(即下面示例中的 7 行)。

1   0   0   0   9
2   0   9   0   0
3   10  0   0   0
4   0   10  4   0
5   0   0   0   10
6   0   0   0   10
7   0   0   0   10
8   0   10  0   0
9   5   0   5   0

我可以将其编码为 R 中的循环,但由于原始文件每个包含 60 多万行,我想知道 awk 或 sed 和 wc -l 是否没有解决方法。

答案1

是的,您可以在以下位置执行此操作awk

awk '{ 
       k=0; 
       for(i=2;i<=NF;i++){ 
         if($i == 0){
             k++
         }
       }
       if(k==3){
         tot++
       }
      }
      END{
          print tot
      }' file 

还有 (GNU)sedwc

$ sed -nE '/\b0\b.*\b0\b.*\b0\b/p' file | wc -l
7

但是,就我个人而言,我会用 perl 代替:

$ perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' file 
7

或者,稍微不那么浓缩:

$ perl -ale 'if( (grep{$_ == 0 } @F) == 3 ){
                  $tot++ 
              }
              END{
                  print $tot
              }' file 
7

对于你们当中的高尔夫球手来说也是如此:

$ perl -ale '(grep{$_==0}@F)==3&&$t++}{print$t' file
7

解释

  • -ale:-a使 Perl 表现得像 awk。它将读取输入文件的每一行并将其按空格分割到数组中@F-l\n每次调用添加print并从输入中删除尾随换行符,并且 是-e应应用于每行输入的脚本。
  • $tot++ if (grep{$_ == 0 } @F) == 3$tot每次恰好有 3 个字段为 时,递增 1 0。由于第一个字段从 1 开始,我们知道它永远不会是 0,因此我们不需要排除它。
  • }{:这只是一种简写方式END{},给出将在文件处理后执行的代码块。因此,}{ print $tot将打印包含三个值为 的字段的总行数0

答案2

GNU grepripgrep

$ LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' ip.txt 
7

$ rg -c '\t0\b.*\b0\b.*\b0\b' ip.txt
7

where$'\t'将匹配制表符,因此即使第一列是 也可以工作0


使用大文件运行示例:

$ perl -0777 -ne 'print $_ x 1000000' ip.txt > f1
$ du -h f1
92M f1

$ time LC_ALL=C grep -c $'\t''0\b.*\b0\b.*\b0\b' f1 > f2
real    0m0.416s

$ time rg -c '\t0\b.*\b0\b.*\b0\b' f1 > f3  
real    0m1.271s

$ time LC_ALL=C awk 'gsub(/\t0/,"")==3{c++} END{print c+0}' f1 > f4
real    0m8.645s

$ time perl -ale '$tot++ if (grep{$_ == 0 } @F) == 3 }{ print $tot' f1 > f5
real    0m14.349s

$ time LC_ALL=C sed -n 's/\t0\>//4;t;s//&/3p' f1 | wc -l > f6
real    0m14.075s
$ time LC_ALL=C sed -n 's/\t0\>/&/3p' f1 | wc -l > f8    
real    0m6.772s

$ time LC_ALL=C awk '{ 
       k=0; 
       for(i=2;i<=NF;i++){ 
         if($i == 0){
             k++
         }
       }
       if(k==3){
         tot++
       }
      }
      END{
          print tot
      }' f1 > f7 
real    0m10.675s

LC_ALL=C如果文件可以包含非 ASCII 字符,则删除。ripgrep通常比GNU grep但在测试运行中GNU grep更快。根据ripgrep作者的说法,(?-u:\b)可以用来避免 unicode 字边界,但这导致上述情况的时间相似。

答案3

使用 GNU sed:

sed -E 's/\t0\>/&/3;t;d' file  | wc -l

正如 Isaac 所指出的,如果我们想精确计算 3,那么可以这样做:

sed -n 's/\t0\>//4;t;s//&/3p' file | wc -l

答案4

使用 Perl 计算左侧被 TAB 包围、右侧被单词边界包围的 0 的行数,共 3 次。最后打印这些行的行数。

perl -lne '$c += 3 == (() = /\t0\b/g)}{print $c' file
7

另一种方法是查看字段:

perl -F'\t' -lane '$c++ if 3 == grep ! $_, @F[1..$#F]}{print $c' file

s///另一种方法是在标量上下文中使用该命令:

perl -lne '$c += s/\t0\b//g == 3}{print $c' file 

我们使用 Gnu awk 来执行此操作:

awk -F'\t' '
  {
      gsub(FS, FS FS)
      $0 = $0 FS
      if ($0 != gensub(FS"0"FS, "", 3, $0))  ++c
  }
  END{print c}
' file

Gnu grep 还可以帮助您:

grep -cP '(.*\t0\b.*){3}' file

相关内容