【问题标题】:how to remove duplicate lines of a huge file in python如何在python中删除大文件的重复行
【发布时间】:2021-07-03 07:17:55
【问题描述】:

我有一个 32GB 左右的 txt 文件,需要检查是否有一些重复的行。 删除大型文本文件的重复行而不逐行读取的最佳方法是什么?

【问题讨论】:

  • 逐行阅读。如果您不阅读该文件,您打算如何识别重复项?
  • 我愿意听任何魔法,但我认为不读一些东西你就无法知道它是什么。在不知道它实际上是什么的情况下,您无法将其与其他事物进行比较,因此无法揭示一个是否与另一个重复。我认为您应该探索在给定内存和计算能力限制的情况下如何有效读取和删除重复项的方法。
  • 是否可以将整个文件与您的环境一起保存在内存中?
  • @dawg 不,这是不可能的
  • 您的问题更多是如何处理一个大文件而不将其完全加载到内存中,而不是如何在不读取文件的情况下删除重复行。

标签: python file duplicates line


【解决方案1】:

您可以制作一组到目前为止看到的行,为了节省内存,存储每行的哈希而不是行本身(以极小的误报机会为代价):

seen = set()
with open('src-text.txt', 'r') as fin, open('src-text-unique.txt', 'w') as fout:
    for line in fin:
        h = hash(line)
        if h not in seen:
            fout.write(line)
            seen.add(h)

注意事项

  • 如果您担心 64 位哈希冲突,您可以使用不同的哈希,例如 hashlib.md5()hashlib.sha256()
  • 如果您没有足够的内存来存储行的哈希值,您可以使用 BloomFilter 来代替内存的有限使用(以较高的误报率为代价)。
  • 作为第三种选择,在某种程度上受到@dawg 的“装饰线条”想法的启发,如果您的内存实在太紧而无法存储所有哈希或足够大的BloomFilter,您可以将文件拆分为@987654326 @,根据hash(line) % n_parts;在这些临时文件中,将原始行号与每一行一起存储。然后分别对每个部分应用重复数据删除,然后合并(已经排序的部分文件)。这避免了n log n 排序,而是O(n)。然而,该技术不适用于流处理,而前两个(散列集或 BloomFilter)适用,直到不同项目的数量太大并导致 MemoryError(散列集)或太大误报率 (BloomFilter)。

附录:碰撞率和内存大小的简短分析

假设您的 32GB 文件平均有 60 个字符的行,并且 10% 的行是重复的(90% 不同的行)。

这会导致:

n_distinct = int(0.9 * 32 * 1024**3 / 60)
>>> n_distinct
515_396_075

换句话说,大约有 1/2 亿条不同的行。这就是我们必须存储在内存中的哈希数,以及 BloomFilter 分析中的 n 值。

哈希的大小和碰撞概率:

参考Birthday attack中描述的用于估计碰撞概率的众所周知的公式,并使用泰勒展开的前两项 1 - e-x ≈ x - x2/2,我们得到n 位和k 不同项的分布良好的哈希的冲突概率为:

def prob_collision(n_bits, n_distinct):
    n, k = n_bits, n_distinct  # usual notation
    x = k**2 / 2**(n + 1)
    return x - x**2 / 2  # approx of 1 - exp(-x)
  • 使用内置的hash(),每个散列是一个64位整数:8字节。所以我们需要 3.8GiB 的内存。整个文件中至少发生一次冲突(意思是:至少有一行被错误地识别为已被看到而实际上并未被看到)的概率为 0.7%。
  • 使用md5(128 位),您需要双倍内存,7.7GiB,但冲突概率为 3.9*10-22

布隆过滤器的大小和碰撞概率:

具有m 位的Bloom Filter 并打算存储多达n 项应使用最小化误报率的最佳散列函数数量:k = m / n * log(2)。使用该数量的函数,误报率 (FPR) 大致为:(1 - e-kn/m)k。至少发生一次碰撞的概率(远)小于FPR * n(它大致是 FPR 对 0..n_distinct 中的 n 的积分)。

def bloom_k(m, n):
    return int(np.ceil(m / n * np.log(2)))

def bloom_false_positive_rate(m, n, k=None):
    k = bloom_k(m, n) if k is None else k
    fpos = np.power(1 - np.exp(-k*n/m), k)
    return fpos

def bloom_prob_collision(m, n, k=None):
    # too lazy to figure out the integral, so
    # using this instead which is slow as molasses
    k = bloom_k(m, n) if k is None else k
    i = np.arange(0, n + 1)
    p_coll = np.sum(np.power(1 - np.exp(-k*i/m), k))
    return p_coll
  • 使用与上面提到的n_distinct 64 位散列 (3.8Gib) 相同的内存量,我们得到m = 32_641_751_449 位、k = 44 散列函数,并且至少发生一次冲突的概率要小得多:大约 0.0001%。

【讨论】:

    【解决方案2】:

    如 cmets 所述,您的问题是处理的文件大于计算机内存。重复数据删除通常很简单。

    处理此问题的最佳方法可能是装饰、排序、取消装饰的“DSU”方法:

    1. 创建一个 TEMP 文件并使用以下内容装饰每一行:

      a) 一个足够大的散列来识别重复项——内置的 hash() 非常适合 SHA 256 可能是防弹的;

      b) 行号。

    2. 使用多种算法之一对大文件进行排序,基于哈希键进行排序

    3. 消除找到的近似重复项

    4. 根据行号重新排序

    5. 取消装饰回到新文件。

    这假设每行的哈希不适合内存。如果该哈希确实适合内存,请使用Pierre D's approach

    【讨论】:

    • 您也可以只使用 BloomFilter,甚至使用不同的盐运行几次,以达到满足您需求的误报水平。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-08
    • 2019-01-30
    • 2012-04-05
    • 1970-01-01
    • 2017-01-14
    • 2023-02-08
    相关资源
    最近更新 更多