我有一个制表符分隔的表,其中:
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
该问题的适当数据结构是set
和dictionary
而且 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 , 使用磨坊主
基本步骤是
用于将每个对应的
nest --implode
所有值组装成一个分隔列表$1
$2
过滤列表以计算唯一值
$1
用于将
nest --explode
过滤后的值扩展$1
为单独的记录
对于步骤(2),我们可以删除分隔列表中的重复元素,就像GL50;GL50;GL79;GL99;GL99
将它们转换为哈希图中的键一样。不幸的是,内置 DSL 字符串到哈希图函数splitkv
仅splitkvx
适用于键值对- 似乎没有办法(例如通过传递空对分隔符)使它们将单独的分隔键字符串转换为哈希映射(具有任意值或空值)。因此,我们必须通过将字符串拆分为索引映射来滚动我们自己的映射,然后将价值观进入新地图的键。
请注意,步骤(2)和(3)只是必要的,因为我们不想要过滤掉多个值相同的 $1
value - 否则我们可以简单地测试内爆字符串的索引映射的长度(并且不需要爆炸结果)。
所以
$ 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
笔记:
- 确实更多地供我自己参考,因为我怀疑我是否能够再次弄清楚