保留每列的唯一值(以逗号分隔)

保留每列的唯一值(以逗号分隔)

我有一个.tsv在 Linux 系统上有一个(制表符分隔的列)文件,其中包含以下列,其中包含以逗号分隔的不同类型的值(字符串、数字):

col1    col2    
.       NS,NS,NS,true,true      
.       12,12,12,13 
1,1,1,2 door,door,1,1   

我想保留独特的价值观(不幸的是我尝试过但不能)。这将是输出:

col1 col2   
.    NS,true        
.    12,13  
1,2  door,1 

答案1

这是一个 Miller 变体,它将值字符串拆分为整数索引映射,然后通过交换映射的键和值(加入返回到以逗号分隔的字符串):

mlr --tsv put 'for (k,v in $*) {
  $[k] =  joink(apply(splitnvx(v,","), func(k,v) {return{v:k}}),",")
}' file.tsv

或者,在 perl5 的帮助下列表::实用程序模块:

perl -MList::Util=uniq -F'\t' -lpe '
  $_ = join "\t", map { join ",", uniq split /,/, $_ } @F
' file.tsv

答案2

使用磨坊主( mlr) 循环遍历每个输入记录中的制表符分隔字段,用逗号分割字段的值,将每个生成的字符串添加为名为 的映射中的键,最后通过使用逗号作为分隔符seen连接映射中的键来重写字段seen:

$ mlr --tsv put 'for (k,v in $*) { seen={}; for (i in splitax(v,",")) { seen[i]=1 } $[k] = joink(seen,",") }' file
col1    col2
.       NS,true
.       12,13
1,2     door,1

米勒的put表达式印得很漂亮:

for (k, v in $*) {
    seen = {};
    for (i in splitax(v, ",")) {
        seen[i] = 1
    }
    $[k] = joink(seen, ",")
}

将分割值添加为映射中的键可以对它们进行重复删除。该splitax()函数将分隔符上的字符串拆分为数组,而不推断每个生成项的类型(它们将是字符串)。该joink()函数将映射的键连接在一起形成由给定分隔符分隔的字符串。外循环中的k和的值v分别是字段名称及其值。

答案3

这是一个笨重的 Perl 方法:

$ perl -F'\t' -le '
%k=%l={}; 
print join("\t", 
  join(",",grep{++$k{$_}==1}split(/,/,$F[0])), 
  join(",",grep{++$l{$_}==1}split(/,/,$F[1]))
)' file.tsv
col1    col2
.   NS,true
.   12,13
1,2 door,1

答案4

下面的程序就可以了(应该适用于所有支持数组(1)awk操作的最近的 Awk 实现):delete

BEGIN{FS=OFS="\t"}

{
    for (i=1;i<=NF;i++)
    {
        n=split($i,sf,",")
        delete seen
        fld=""

        for (j=1;j<=n;j++)
        {
            if (!seen[sf[j]]++)
            {
                fld=fld (fld?",":"") sf[j]
            }
        }
        $i=fld
    }
    print
}

将其存储为例如dedup.awk并通过调用它

awk -f dedup.awk input.tsv

对您的输入文件进行操作。

这将设置\t为输入和输出字段分隔符。然后它会

  • 迭代 ( for (i=1;i<=NF;i++))行的所有字段
  • 将每个字段拆分为子字段 ( sf),
  • 迭代所有子字段并将该字段重新组装到临时变量中fld,但仅添加该字段中尚未看到的那些子字段值。在数组中保存一条记录seen
  • 最后将重新组合的字段赋值fld给当前字段$i
  • 打印整行,包括迄今为止所做的所有修改

(1)感谢@EdMorton 暗示如今支持已经相当广泛。

相关内容