在Python中从大文件中删除重复行

在Python中从大文件中删除重复行,python,duplicates,Python,Duplicates,我有一个csv文件,我想从中删除重复的行,但它太大,无法放入内存。我找到了一个方法来完成它,但我猜这不是最好的方法 每行包含15个字段和数百个字符,所有字段都需要确定唯一性。我不是比较整行以找到重复的,而是比较散列(row-as-string)以节省内存。我设置了一个过滤器,将数据划分成大致相等的行数(例如,一周中的几天),每个分区都足够小,以至于该分区的哈希值查找表可以放入内存中。我为每个分区传递一次文件,检查唯一的行并将它们写入第二个文件(伪代码): 我想加快速度的一种方法是找到一个更好的过

我有一个csv文件,我想从中删除重复的行,但它太大,无法放入内存。我找到了一个方法来完成它,但我猜这不是最好的方法

每行包含15个字段和数百个字符,所有字段都需要确定唯一性。我不是比较整行以找到重复的,而是比较
散列(row-as-string)
以节省内存。我设置了一个过滤器,将数据划分成大致相等的行数(例如,一周中的几天),每个分区都足够小,以至于该分区的哈希值查找表可以放入内存中。我为每个分区传递一次文件,检查唯一的行并将它们写入第二个文件(伪代码):

我想加快速度的一种方法是找到一个更好的过滤器来减少必要的通过次数。假设行的长度是均匀分布的,可以代替

for day in days: 

我们有

for i in range(n):

其中“n”尽可能小的内存将允许。但这仍然使用相同的方法

下面提供了一个很好的实用解决方案;我很好奇,从算法的角度来看,是否有更好/更快/更简单的方法来实现这一点


另外,我仅限于Python 2.5。

如果您想要一种非常简单的方法,只需创建一个sqlite数据库:

import sqlite3
conn = sqlite3.connect('single.db')
cur = conn.cursor()
cur.execute("""create table test(
f1 text,
f2 text,
f3 text,
f4 text,
f5 text,
f6 text,
f7 text,
f8 text,
f9 text,
f10 text,
f11 text,
f12 text,
f13 text,
f14 text,
f15 text,
primary key(f1,  f2,  f3,  f4,  f5,  f6,  f7,  
            f8,  f9,  f10,  f11,  f12,  f13,  f14,  f15))
"""
conn.commit()

#simplified/pseudo code
for row in reader:
    #assuming row returns a list-type object
    try:
        cur.execute('''insert into test values(?, ?, ?, ?, ?, ?, ?, 
                       ?, ?, ?, ?, ?, ?, ?, ?)''', row)
        conn.commit()
    except IntegrityError:
        pass

conn.commit()
cur.execute('select * from test')

for row in cur:
    #write row to csv file

这样,您就不必自己担心任何比较逻辑——只需让sqlite为您处理它。它可能不会比散列字符串快很多,但可能要容易得多。当然,如果需要,您可以修改存储在数据库中的类型,也可以不修改,视情况而定。当然,因为您已经将数据转换为字符串,所以可以只使用一个字段。这里有很多选项。

您基本上是在进行合并排序,并删除重复的条目

一般来说,将输入分成内存大小的部分,对每个部分进行排序,然后合并这些部分,同时删除重复项是一个好主意

实际上,我会让虚拟内存系统处理几次,然后写:

input = open(infilename, 'rb')
output = open(outfile, 'wb')

for key,  group in itertools.groupby(sorted(input)):
    output.write(key)

您当前的方法不能保证正常工作。

首先,实际上不同的两行产生相同哈希值的可能性很小
hash(a)==hash(b)
并不总是意味着
a==b

其次,您的“减少/减少”资本支出使概率更高:

顺便说一句,“.join(['foo','1','23'])不是更清楚一些吗

顺便说一句,为什么不为
htable
使用
set
而不是
dict

这里有一个实用的解决方案:从站点获取“core utils”包,然后安装它。然后:

  • 将文件的副本写入(比如)infle.csv,但不带标题
  • c:\gnuwin32\bin\sort--unique-ooutfile.csv infle.csv
  • 读取outfile.csv并编写一份带有标题的副本

  • 对于第1步和第3步中的每一步,您都可以使用Python脚本或其他一些GnuWin32实用程序(head、tail、tee、cat等等)。

    您的原始解决方案有点不正确:您可以将不同的行散列到同一个值(散列冲突),而您的代码将省略其中一行

    在算法复杂度方面,如果您希望副本相对较少,我认为最快的解决方案是逐行扫描文件,添加每行的哈希(就像您所做的那样),同时存储该行的位置。然后,当您遇到重复的散列时,查找到原始位置,以确保它是重复的,而不仅仅是散列冲突,如果是这样,请返回并跳过该行


    顺便说一句,如果CSV值被规范化(即,如果对应的CSV行是逐字节等效的,则记录被视为相等的),您根本不需要在这里进行CSV解析,只需处理纯文本行。

    因为我认为您必须在一定程度上定期进行此操作(或者您已经破解了一个一次性脚本),你提到你对一个理论解决方案感兴趣,这是一个可能性

    将输入行读入B树,按每个输入行的哈希值排序,当内存满时将其写入磁盘。我们注意将附加到散列的原始行存储在B树上(作为一个集合,因为我们只关心唯一的行)。当我们读取一个重复的元素时,我们检查存储元素上设置的行,如果它是一个新的行,并且恰好散列到相同的值,则添加它

    为什么是B-树?当您只能(或希望)将它们的一部分读取到内存中时,它们需要更少的磁盘读取。每个节点上的度(子节点数)取决于可用内存和行数,但您不希望节点太多

    一旦我们在磁盘上有了这些B树,我们就比较它们中最低的元素。我们从所有具有它的B-树中移除最低的。我们合并了它们的行集,这意味着这些行没有重复的行了(而且我们没有更多散列到该值的行)。然后,我们将此合并中的行写入输出csv结构

    我们可以将一半的内存用于读取B树,另一半用于将输出csv保留在内存中一段时间。当csv的一半已满时,我们将其刷新到磁盘,并附加到已写入的内容。我们在每个步骤中读取的每个B-树的数量可以通过(可用内存/2)/树的数量粗略计算,四舍五入以读取完整节点

    在伪Python中:

    ins = DictReader(...)
    i = 0
    while ins.still_has_lines_to_be_read():
        tree = BTree(i)
        while fits_into_memory:
            line = ins.readline()
            tree.add(line, key=hash)
        tree.write_to_disc()
        i += 1
    n_btrees = i
    
    # At this point, we have several (n_btres) B-Trees on disk
    while n_btrees:
        n_bytes = (available_memory / 2) / n_btrees
        btrees = [read_btree_from_disk(i, n_bytes)
                  for i in enumerate(range(n_btrees))]
        lowest_candidates = [get_lowest(b) for b in btrees]
        lowest = min(lowest_candidates)
        lines = set()
        for i in range(number_of_btrees):
            tree = btrees[i]
            if lowest == lowest_candidates[i]:
                node = tree.pop_lowest()
                lines.update(node.lines)
            if tree.is_empty():
            n_btrees -= 1
    
        if output_memory_is_full or n_btrees == 0:
            outs.append_on_disk(lines)
    

    如何使用heapq模块读取内存限制内的文件片段,并将其写出已排序的片段(heapq始终保持排序顺序)

    或者你可以抓住第一行中的第一个单词,用它把文件分成几部分。然后,您可以读取行(可能执行“”。如果可以更改,请连接(line.split())以统一行中的间距/制表符
    import sqlite3
    conn = sqlite3.connect('single.db')
    cur = conn.cursor()
    cur.execute("""create table test(
    f1 text,
    f2 text,
    f3 text,
    f4 text,
    f5 text,
    f6 text,
    f7 text,
    f8 text,
    f9 text,
    f10 text,
    f11 text,
    f12 text,
    f13 text,
    f14 text,
    f15 text,
    primary key(f1,  f2,  f3,  f4,  f5,  f6,  f7,  
                f8,  f9,  f10,  f11,  f12,  f13,  f14,  f15))
    """
    conn.commit()
    
    #simplified/pseudo code
    for row in reader:
        #assuming row returns a list-type object
        try:
            cur.execute('''insert into test values(?, ?, ?, ?, ?, ?, ?, 
                           ?, ?, ?, ?, ?, ?, ?, ?)''', row)
            conn.commit()
        except IntegrityError:
            pass
    
    conn.commit()
    cur.execute('select * from test')
    
    for row in cur:
        #write row to csv file
    
    input = open(infilename, 'rb')
    output = open(outfile, 'wb')
    
    for key,  group in itertools.groupby(sorted(input)):
        output.write(key)
    
    >>> reduce(lambda x,y: x+y, ['foo', '1', '23'])
    'foo123'
    >>> reduce(lambda x,y: x+y, ['foo', '12', '3'])
    'foo123'
    >>>
    
    ins = DictReader(...)
    i = 0
    while ins.still_has_lines_to_be_read():
        tree = BTree(i)
        while fits_into_memory:
            line = ins.readline()
            tree.add(line, key=hash)
        tree.write_to_disc()
        i += 1
    n_btrees = i
    
    # At this point, we have several (n_btres) B-Trees on disk
    while n_btrees:
        n_bytes = (available_memory / 2) / n_btrees
        btrees = [read_btree_from_disk(i, n_bytes)
                  for i in enumerate(range(n_btrees))]
        lowest_candidates = [get_lowest(b) for b in btrees]
        lowest = min(lowest_candidates)
        lines = set()
        for i in range(number_of_btrees):
            tree = btrees[i]
            if lowest == lowest_candidates[i]:
                node = tree.pop_lowest()
                lines.update(node.lines)
            if tree.is_empty():
            n_btrees -= 1
    
        if output_memory_is_full or n_btrees == 0:
            outs.append_on_disk(lines)