删除给定列中的重复字段

删除给定列中的重复字段

我想从给定列(示例中的 $2)中删除重复字段(以逗号分隔)。

输入文件:

A    1,2,3,4   
B    4,5,6,3
C    2,15

预期输出:

A    1,2,3,4
B    5,6
C    15

答案1

perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e'

您可以像这样运行上面的代码:

$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e' afile 
A    1,2,3,4
B    5,6
C    15

怎么运行的

首先调用perlwith-lpe会执行以下 3 件事。

  • -l[octal] 启用行结束处理,指定行结束符
  • -p 假设像 -n 一样循环,但也打印行,像 sed
  • -e program 一行程序(允许多个-e,省略programfile)

这本质上是接收文件,去掉换行符,对一行进行操作,然后在完成后将换行符添加回其上。所以它只是循环遍历文件并依次对每个文件执行我们的 Perl 代码。

至于实际的 Perl 代码:

  • \s 表示空格字符(五个字符[ \f\n\r\t]\v在较新版本中perl,如[[:space:]])。
  • \K 保留 \K 左边的内容,不要将其包含在 $& 中
  • \S+ 不在集合中的一个或多个字符 [ \f\n\r\t\v]

join ",",获取结果并重新连接每个字段,以便用逗号分隔。

Thesplit ",", $&将获取由 the 找到的匹配项,\S+并将它们拆分为仅字段,不带逗号。

grep {!$seen{$_}++}获取每个字段的编号,将其添加到哈希中,$seen{}其中每个字段的编号是$_我们遍历每个字段时的编号。每次“看到”字段编号时,都会通过++运算符进行计数$seen{$_}++

grep{!$seen{$_}++}如果字段值只出现过一次,则将返回该字段值。

修改一下看看发生了什么

如果您使用这一经过修改的令人厌恶的内容,您可以看到当 Perl 单行代码从文件中跨行移动时发生了什么。

$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e; @a=keys %seen; @b=values %seen; print "keys: @a | vals: @b"' afile 
keys: 4 1 3 2 | vals: 1 1 1 1
A    1,2,3,4
keys: 6 4 1 3 2 5 | vals: 1 2 1 2 1 1
B    5,6
keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1
C    15

$seen{}这向您显示了处理文件中的一行结束时的内容。让我们看看文件的第二行。

B    4,5,6,3

这是我的修改版本将该行显示为:

keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1

这就是说我们已经看到了字段#6(1 次)、字段#4(2 次)等以及字段#5(1 次)。因此,当grep{...}返回结果时,如果该数组出现在这一行 (4,5,6,3) 中并且我们只看到它 1 次 (6,1,15,5),则只会返回该数组的结果。这两个列表的交集是 (5,6),这就是 所返回的内容grep

参考

答案2

{ 
    tr -cs '0-9ABC' '[\n*]' | 
    nl -w1 -s: |
    sort -t: -uk2,2 | sort -t: -k1,1n |
    sed 's/[^:]*://;/^[ABC]/!{H;$!d
        };x;y/\n/,/;s/,/\t/'

} <<\FILE
A       1,2,3,4
B       4,5,6,3
C       2,15
FILE

基本上,这只是采取一些措施来确保数据能够运行sort -u并且仍能以与发送时相对相同的形状返回。

首先tr将其输入中非数字的任何字节转换ABC为换行符并压缩任何重复。所以每个字段都有自己的行。

nl然后对输入中的每一行进行编号。它的输出看起来像:

1:A
2:1
3:2

...等等。

接下来,数据被sort编辑两次。第一个sort删除所有重复字段 - 它仅对每行的第二个数据字段进行操作,并用:.第二个通过这次对第一个字段进行 ingsort来恢复原始顺序 -nl的编号顺序。sort

最后sed将所有内容重新组合在一起。它收集其输入,删除所有的nl插入,并且 ex更改其行ABC$最后一行的保持和模式缓冲区。在每行中,它将y所有\newlines 转换为,逗号,并用最后的 a 替换<tab>每行的第一行。

结果如何?

输出

A       1,2,3,4
B       5,6
C       15

朋友们,这就是 Unix 的魅力所在。

而且,因为我很好奇,这里是sed单独的:

sed 's/\t\(.*\)/,\1,/;H;$!d;x;:t
     s/,\([^,]*,\)\(.*,\)\1/,\1\2/;tt
     s/,\(\n.\),/\1\t/g;s/,\(.*\),/\t\1/'
' <<\FILE
A       1,2,3,4
B       4,5,6,3
C       2,15
FILE

第一行和第三行就像准备/清理。第一个将整个文件拉入模式空间并确保每个字段都以逗号分隔,因此第一行,例如:

A,1,2,3,4,

通过后看起来是这样的。

第二行是完成所有工作的地方。它实际上是一个递归替换函数 - 它不断地替换任何可以在其他地方仅与自身匹配的逗号分隔字段,直到找不到更多的字段为止。这就是est命令的作用t

正如我所说,第三行只是清理所有内容。它将逗号和制表符等放回到原来的位置,最终结果是:

输出

A       1,2,3,4
B       5,6
C       15

这很有趣,但对任何相当大的数据块执行此操作都是计算机谋杀。这是递归正则表达式——可能是最糟糕的递归。因此,只需几行代码就可以完成这种工作,但以这种方式制作电子表格可能是不明智的。

答案3

有了awk,这很容易使用array,split,以及常规的loop:

{
    split($2, elements, ",")
    out = ""
    for (i in elements) {
        el = elements[i]
        if (!(el in used)) {
            out = out el ","
        }
        used[el] = 1
    }
    sub(/,$/, "", out)
    $2 = out
}
1

对于每一行,我们用逗号分隔第二列并将这些位保存到数组中elements。然后,我们使用循环构建该列的新值,检查我们之前是否见过该值。我们保留在(关联)数组中已经看到的值集used。如果el in used,我们以前见过这个,不应该把它放在输出中;否则,它是新的,我们将它连接起来out并将其添加到我们看到的值集中,这样我们就不会再次使用它。最后,我们将构建的列表放回第二列。这本质上是您在任何其他语言中采用的方法。

将上面的代码放入一个文件中并使用awk -f或单引号将其全部作为命令行上的参数运行。

相关内容