【问题标题】:Remove duplicate lines (including the line that is duplicated) [duplicate]删除重复行(包括重复的行)[重复]
【发布时间】:2013-04-30 17:04:35
【问题描述】:

我需要删除文件中多次出现的所有行。

例子:

Line1
Line2
Line3
Line2

结果:

Line1
Line3

Python、Perl 或 unix-util 无关紧要。谢谢。

【问题讨论】:

  • 需要保留原订单吗?
  • 这个问题已经回答过几次了;检查[这个][1]。 [1]:stackoverflow.com/questions/1215208/…
  • 不,顺序不重要。
  • @Hal:这不太一样,因为他想删除所有行的副本,而不是第一个。那里没有一个解决方案可以做到这一点,而且如何修改它们来做到这一点并不明显。
  • 那我很糟糕。谢谢@abernert

标签: python perl unix


【解决方案1】:

保持顺序,但在内存中保留文件的两个副本:

my @lines;
my %seen;
while (<>) {
   push @lines, $_;
   ++$seen{$_};
}

for (@lines) {
   print if $seen{$_} == 1;
}

作为单行:

perl -ne'push @l, $_; ++$s{$_}; }{ for (@l) { print if $s{$_} == 1; }'

不保留顺序,但在内存中只保留文件的一份副本:

my %seen;
++$seen{$_} while <>;

while (my ($k, $v) = each(%seen)) {
   print $k if $v == 1;
}

作为单行:

perl -ne'++$s{$_}; }{ while (my ($k, $v) = each(%s)) { print $k if $v == 1; }'

【讨论】:

  • 通过快速测试,这些似乎有效。扫描代码,它们在功能上似乎与我的两个 Python 实现相同,大小大致相同,速度为 +/- 50%,内存使用情况相似。对于熟悉 Perl 但不熟悉 Python 的人来说,它可能更具可读性,并且从快速测试来看,它似乎可以工作。所以……+1。您可能想阅读其他 perl 答案,因为我不确定它是否相同。 (我很久以前就故意用电极烧掉我大脑中原生读取 perl 的部分。)
  • @abarnert,是的,它实际上是相当简洁的 Perl 代码,但 Perl 看起来与其他语言有很大不同,因此外人很难阅读。 /// 我看到了另一个 Perl 答案。它使用与我的订单保留解决方案一样多的内存,但它不保留订单。 (它也可以排序,但只是出于美观的原因,因此如果性能有问题,可以将其删除。)
【解决方案2】:

这是一个 Python 实现。

如果需要保留行的初始顺序:

import collections
import fileinput

lines = list(fileinput.input())
counts = collections.Counter(lines)
print(''.join(line for line in lines if counts[line] == 1))

如果没有,它会更简单更快):

import collections
import fileinput

counts = collections.Counter(fileinput.input())
print(''.join(line for line, count in counts.iteritems() if count==1))

对于每一行,您需要查看它是否有任何重复。如果您不想以二次方式执行此操作(执行一次,然后每行执行第二次),则需要使用中间数据结构,允许您在两次线性遍历中执行此操作。

因此,您通过列表构建一个哈希表(collections.Counter 是一个专门的dict,它只是将每个键映射到它出现的次数)。然后,您可以对列表进行第二次遍历,在哈希表中查找每个列表(第一个版本),或者只是迭代哈希表(第二个版本)。


据我所知,命令行工具无法做到这一点;您至少必须 sort 输入(这是 O(N log N),而不是 O(N)),或者使用隐式执行等效操作的工具。

但对于许多用例来说,这没什么大不了的。对于 1M 行的 80MB 文件,N log N 只比 N 慢一个数量级,完全可以想象,两个工具之间的常数乘数差异会在同一个数量级上。


快速计时测试证实,在 1M 行规模上,sort | uniq -u 版本慢了 6 倍多一点,但仍然足够快,您可能不会在意(不到 10 秒,这比它更多的时间需要复制和粘贴 Python 代码,对吗?)除非您必须重复执行此操作。

根据进一步的测试,在 128K 行时,Python 版本仅快 4 倍;在 64M 行时,速度提高了 28 倍;在 5G 线路上……这两个版本都将系统驱动到交换系统的严重程度,以至于我终止了测试。 (用 dbm 键值数据库替换 Counter 可以解决这个问题,但对于较小的规模来说成本很高。)

【讨论】:

  • 对不起,我忽略了关于测试的一点。评论已撤消。
【解决方案3】:

*nix 命令uniq 可以做到这一点。

sort file.name | uniq -u

【讨论】:

  • 不,它不能。这将包括第一次出现的Line2,这是 OP 明确不想要的。即使他不想这样,uniq 也要求对数据进行排序,但事实并非如此。
  • 添加 -u 并不能解决任何问题。这只是默认设置(与-d 相反)。你试过这个吗?
  • @abarnert: 也许你的 uniq 能力较差
  • @ysth:我有一个遵循 POSIX 规范的uniq。见here
  • @abarnert:你试过了吗?我可以看到“禁止写入输入中重复的行”。意思是两种不同的东西;我的 uniq(来自 coreutils 8.13)当然可以满足要求。
【解决方案4】:

下面是 perl 中的示例:

my %line_hash;
open my $fh, "<", "testfile";
while(my $line = <$fh>) {
   $line_hash{$line}++; 
}
close $fh;

open my $out_fh, ">>", "outfile";
for my $key ( sort keys %line_hash ){
    print $out_fh $key if $line_hash{$key} == 1;
}
close $out_fh;

测试文件:

$ cat testfile
Line1
Line2
Line3
Line2

输出文件:

$ cat outfile
Line1
Line3

【讨论】:

    【解决方案5】:
    sort inputfile | uniq -u
    

    (假设 gnu coreutils uniq)

    虽然SUSv4 说:

    -u 禁止写入输入中重复的行。

    从 cmets 到某些答案,并非所有 uniq 都以相同的方式解释。

    【讨论】:

    • 谢谢大家,我尝试了你们的解决方案,这个似乎是最简单的一个并且为我工作(Debian 2.6.32)
    【解决方案6】:

    读取每一行,grep同一文件中的行查找计数,仅打印计数为1的行:

    #!/bin/bash
    while read line
    do
      if [ `grep -c ${line} sample.txt` -eq 1 ] ; then echo ${line} ; fi
    done < sample.txt
    

    【讨论】:

    • 这可行,但它是二次的:它每行重新读取整个文件一次。这对于大文件来说是一个非常糟糕的主意。
    • ...完成uniq sample.txt
    • uniq 没有sort 将不会产生任何有用的效果——即使有了它,它仍然是一个二次解,并且它不会在sortuniq自己已经给你了。
    • 仍然需要排序...
    猜你喜欢
    • 2014-03-16
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    • 2017-11-22
    • 1970-01-01
    相关资源
    最近更新 更多