如果第 2 列对应于第 1 列的两个或多个值,则删除所有行

如果第 2 列对应于第 1 列的两个或多个值,则删除所有行

我有一个制表符分隔的表,其中:

GL89     AADAC
GL89     AFGAC
GL89     AFDAC
GL50     AC923
GL50     AC923
GL79     AC923
GL99     AC923
GL99     AC923
GL60     AC100
GL60     AC100
GL20     AC200
GL30     AC300
GL30     AC400

我想消除其中第 2 列的一个值至少对应于第 1 列的 2 个或更多值的行,在这种情况下,应删除下面的行。

GL50     AC923
GL79     AC923
GL99     AC923
GL99     AC923

并保留表的其余部分:

GL89     AADAC
GL89     AFGAC
GL89     AFDAC
GL60     AC100
GL60     AC100
GL20     AC200
GL30     AC300
GL30     AC400

有表格吗?谢谢你!

答案1

awk 'BEGIN{ FS=OFS="\t" }
{ data[$2]= (data[$2]==""?"":(k[$2]==$1? data[$2] ORS: "@") ) $0; k[$2]=$1 }

END{ for(x in data) if(data[x] !~/^@/) print data[x] }' infile

注意:我使用@字符来标记打印时不应打印的记录,因此该字符不应出现在您的输入文件中,否则您需要将其更改为该字符以外的字符(或选择字符集作为而是一个字符串)。

  • data[$2]= (data[$2]==""?"":(XXX) ) $0,如果数组不为空,则data用该部分的结果更新数组的值,并将当前行也附加到其中。(XXX)该列$2用作数组键。

  • (XXX)如果(k[$2]==$1? data[$2] ORS: "@")相同键的值(我们使用数组作为帮助程序来保存键的最新值对)不同,则设置 at 符号字符,否则t为该键附加其内容 + 换行符 (ORS)。

  • 最后,所有具有相同第二列但不同 >1 多个第一列的行将被删除,因为我们@在代码中用特定字符标记了这些行。


为了更好地理解代码,你可以print ...在任何时候使用这个语句来查看发生了什么,

awk 'BEGIN{ FS=OFS="\t" }
{ data[$2]= (data[$2]==""?"":(k[$2]==$1? data[$2] ORS: "***") ) $0; k[$2]=$1 }

END{ for(x in data) print "<" data[x] ">" }' infile

我刚刚在原始命令中用 at 符号标记了已删除的记录,此处显示了以该***标记开头的记录。

答案2

使用任何支持的 awk length(array)

$ cat tst.awk
BEGIN { FS=OFS="\t" }
$2 != prev {
    if ( NR > 1 ) {
        prt()
    }
    prev = $2
}
{ cnt[$1]++ }
END { prt() }

function prt(   val,i) {
    if ( length(cnt) == 1 ) {
        for (val in cnt) {
            for (i=1; i<=cnt[val]; i++) {
                print val, prev
            }
        }
    }
    delete cnt
}

$ sort -t$'\t' -k2,2 -k1,1 file | awk -f tst.awk
GL89    AADAC
GL60    AC100
GL60    AC100
GL20    AC200
GL30    AC300
GL30    AC400
GL89    AFDAC
GL89    AFGAC

答案3

该问题的适当数据结构是setdictionary

而且 python 都内置了这两种功能。

python3 -c 'import sys
ifile = sys.argv[1]
fs,ors = "\t","\n"

d = {}; L = {}
with open(ifile) as fh:
  for l in fh:
    c1,c2 = l.rstrip().split(fs)
    if c2 in d:
      d[c2].add(c1)
      L[c2].append(l.rstrip())
    else:
      d[c2] = { c1 }
      L[c2] = [ l.rstrip() ]

print(*[l
  for k,v in d.items()
  if len(v) == 1
  for l in L[k]
  ], sep=ors)
' file

输出:

GL89    AADAC
GL89    AFGAC
GL89    AFDAC
GL60    AC100
GL60    AC100
GL20    AC200
GL30    AC300
GL30    AC400

答案4

只是为了好玩1 , 使用磨坊主

基本步骤是

  1. 用于将每个对应的nest --implode所有值组装成一个分隔列表$1$2

  2. 过滤列表以计算唯一值$1

  3. 用于将nest --explode过滤后的值扩展$1为单独的记录

对于步骤(2),我们可以删除分隔列表中的重复元素,就像GL50;GL50;GL79;GL99;GL99将它们转换为哈希图中的键一样。不幸的是,内置 DSL 字符串到哈希图函数splitkvsplitkvx适用于键值- 似乎没有办法(例如通过传递空对分隔符)使它们将单独的分隔键字符串转换为哈希映射(具有任意值或空值)。因此,我们必须通过将字符串拆分为索引映射来滚动我们自己的映射,然后将价值观进入新地图的键。

请注意,步骤(2)和(3)只是必要的,因为我们想要过滤掉多个值相同的 $1value - 否则我们可以简单地测试内爆字符串的索引映射的长度(并且不需要爆炸结果)。

所以

$ mlr --nidx --fs tab nest --ivar ';' -f 1 then filter '
  func splitkx(s,t):map {
    var m = {};
    for(k,v in splitnvx(s,t)){m[v] = 1};
    return m
  }
  length(splitkx($1,";")) == 1' then nest --evar ';' -f 1 file
GL89    AADAC
GL89    AFGAC
GL89    AFDAC
GL60    AC100
GL60    AC100
GL20    AC200
GL30    AC300
GL30    AC400

笔记:

  1. 确实更多地供我自己参考,因为我怀疑我是否能够再次弄清楚

相关内容