python初学者 - 更快的方式来查找和替换大文件?

时间:2012-04-20 16:23:25

标签: python replace

我有一个大约1亿行的文件,我想用替代文本替换文本,这些文本存储在制表符分隔的文件中。我工作的代码,但是花了大约一个小时来处理前70K行。在尝试逐步提高我的python技能时,我想知道是否有更快的方法来执行此操作。谢谢! 输入文件如下所示:

  

CHROMOSOME_IV ncRNA基因5723085 5723105。 - 。 ID =基因:WBGene00045518   CHROMOSOME_IV ncRNA ncRNA 5723085 5723105。 - 。父=基因:WBGene00045518

并且具有替换值的文件如下所示:

  

WBGene00045518 21ur-5153

这是我的代码:

infile1 = open('f1.txt', 'r')
infile2 = open('f2.txt', 'r')
outfile = open('out.txt', 'w')

import re
from datetime import datetime
startTime = datetime.now()

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)   
linecounter = 0
for line in infile2:
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
            outfile.write(line + '\n')
        else:
            outfile.write(line + '\n')
    linecounter += 1
    if linecounter in mult10K:
        print linecounter   
        print (datetime.now()-startTime)
infile1.close()
infile2.close()
outfile.close()

6 个答案:

答案 0 :(得分:6)

您应该将行拆分为“单词”,并且只在词典中查找这些单词:

>>> re.findall(r"\w+", "CHROMOSOME_IV ncRNA gene 5723085 5723105 . - . ID=Gene:WBGene00045518 CHROMOSOME_IV ncRNA ncRNA 5723085 5723105 . - . Parent=Gene:WBGene00045518")
['CHROMOSOME_IV', 'ncRNA', 'gene', '5723085', '5723105', 'ID', 'Gene', 'WBGene00045518', 'CHROMOSOME_IV', 'ncRNA', 'ncRNA', '5723085', '5723105', 'Parent', 'Gene', 'WBGene00045518']

这将消除你为每一行所做的字典循环。

这里是完整的代码:

import re

with open("f1.txt", "r") as infile1:
    udict = dict(line.strip().split("\t", 1) for line in infile1)

with open("f2.txt", "r") as infile2, open("out.txt", "w") as outfile:
    for line in infile2:
        for word in re.findall(r"\w+", line):
            if word in udict:
                line = line.replace(word, udict[word])
        outfile.write(line)

编辑:另一种方法是从您的字典中构建单个mega-regex:

with open("f1.txt", "r") as infile1:
    udict = dict(line.strip().split("\t", 1) for line in infile1)
regex = re.compile("|".join(map(re.escape, udict)))
with open("f2.txt", "r") as infile2, open("out.txt", "w") as outfile:
    for line in infile2:
        outfile.write(regex.sub(lambda m: udict[m.group()], line))

答案 1 :(得分:6)

我正在思考你对dicionary键的循环,以及wqya来优化它,并让我们稍后对你的代码做出其他评论。

但后来我偶然发现了这一部分:

if linecounter in mult10K:
    print linecounter   
    print (datetime.now()-startTime)

这个看起来无用的片段,实际上让Python按顺序查看并比较文件中每行的“linecounter”列表中的10000个项目。

将此部分替换为:

if linecounter % 10000 == 0:
    print linecounter   
    print (datetime.now()-startTime)

(并忘记所有mult10k部分)​​ - 你应该加快速度。

此外,您似乎正在为每个输入行录制多个输出行 - 你的主循环是这样的:

linecounter = 0
for line in infile2:
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
            outfile.write(line + '\n')
        else:
            outfile.write(line + '\n')
    linecounter += 1

替换它:

for linecounter, line in enumerate(infile2):
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
    outfile.write(line + '\n')

正确地为每个输入行只写一个输出行(除了重复代码重复,并以“pythonic”方式处理行计数)

答案 2 :(得分:5)

此代码是线性搜索的完整。难怪它运行缓慢。在不了解更多输入的情况下,我无法就如何解决这些问题给出建议,但我至少可以指出问题所在。我会注意到主要问题,以及一些小问题。

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

此处不要使用update;只需将项目添加到字典中:

    udict[linelist[0]] = linelist[1]

这比为每个条目创建字典要快。 (实际上,Sven Marnach基于生成器的创建这个字典的方法更好。但这虽然很小。

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)

这完全没必要。删除这个;如果没有这个,我会告诉你一种间隔打印的方法。

linecounter = 0
for line in infile2:
    for key, value in udict.items():

这是你的第一个大问题。你正在通过字典线性搜索每行的行中的键。如果字典非常大,这将需要大量的操作:100,000,000 * len(udict)。

        matches = line.count(key)

这是另一个问题。您正在寻找使用线性搜索的匹配项。然后你做replace,它进行相同的线性搜索!你不需要检查匹配; replace只返回相同的字符串(如果没有)。这也不会产生很大的不同,但它会带给你一些东西。

        line = line.replace(key, value)

继续执行这些替换,然后只在完成所有替换后写入行:

    outfile.write(line + '\n')

最后,

    linecounter += 1
    if linecounter in mult10K:

请原谅我,但这是一种荒谬的方式!您正在通过linecounter进行线性搜索,以确定何时打印一条线。同样,这又增加了近100,000,000 * 100次操作。您应该在至少中搜索一组;但最好的方法(如果你真的必须这样做)就是做一个模运算并测试它。

    if not linecounter % 10000: 
        print linecounter   
        print (datetime.now()-startTime)

要使此代码高效,您需要摆脱这些线性搜索。 Sven Marnach的答案提出了一种可行的方法,但我认为这取决于文件中的数据,因为替换键可能与明显的单词边界不对应。 (他补充说的正则表达式的方法虽然如此。)

答案 3 :(得分:1)

这不是特定于Python的,但您可能会稍微展开double for循环,以便在循环的每次迭代中都不会发生文件写入。也许每1000或10,000行写入文件。

答案 4 :(得分:1)

我希望为每行输入编写一行输出次数替换字符串的数量是一个错误,你真的只打算为每个输入写一个输出。

您需要找到一种方法来尽快测试匹配的输入行。循环整个字典可能是你的瓶颈。

我认为正则表达式被预编译到可以高效的状态机中。我不知道当你生成一个巨大的表达时性能如何受到影响,但值得一试。

freakin_huge_re = re.compile('(' + ')|('.join(udict.keys()) + ')')
for line in infile2:
    matches = [''.join(tup) for tup in freakin_huge_re.findall(line)]
    if matches:
        for key in matches:
            line = line.replace(key, udict[key])

答案 5 :(得分:-1)

他们在Python中显而易见的是list comprehension - 这是一种更快(更可读)的方式:

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)

这样:

mult10K = [x*10000 for x in range(100)]

同样,你有:

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

我们可以使用dict理解(使用生成器表达式):

lines = (line.strip().split('\t') for line in infile1)
udict = {line[0]: line[1] for line in lines}

此处值得注意的是,您似乎正在使用制表符分隔文件。在这种情况下,the csv module可能比使用split()更好。

另请注意,使用the with statement会提高可读性并确保您的文件已关闭(即使是例外情况)。

如果在每个循环中执行打印语句也会使事情变得非常缓慢 - 它们对于调试很有用,但是当在主要数据块上运行时,可能值得删除它们。

您可以做的另一个“更多pythonic”事情是使用enumerate()而不是每次都向变量添加一个。 E.g:

linecounter = 0
for line in infile2:
   ...
   linecouter += 1

可替换为:

for linecounter, line in enumerate(infile2):
    ...

在计算密钥出现次数的地方,更好的解决方案是使用in

if key in line:

在查找实例后发生短路。

添加所有这些,让我们看看我们有什么:

import csv
from datetime import datetime
startTime = datetime.now()

with open('f1.txt', 'r') as infile1:
    reader = csv.reader(delimiter='\t')
    udict = dict(reader)

with open('f2.txt', 'r') as infile2, open('out.txt', 'w') as outfile:
    for line in infile2:
        for key, value in udict.items():
            if key in line: 
                line = line.replace(key, value)
        outfile.write(line + '\n')

编辑:按照评论中的要求列出补偿与正常循环:

python -m timeit "[i*10000 for i in range(10000)]"
1000 loops, best of 3: 909 usec per loop

python -m timeit "a = []" "for i in range(10000):" "  a.append(i)"
1000 loops, best of 3: 1.01 msec per loop

注意usec vs msec。它并不大,但它确实存在。