8

我有一个大约 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()
4

6 回答 6

6

我正在考虑您对字典键的循环,以及一个 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”方式处理行数)

于 2012-04-20T16:46:03.953 回答
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)

编辑:另一种方法是从您的字典中构建一个单一的大型正则表达式:

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))
于 2012-04-20T16:29:44.730 回答
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的回答提出了一种可行的方法,但我认为这取决于文件中的数据,因为替换键可能与明显的单词边界不对应。(不过,他添加的正则表达式方法解决了这个问题。)

于 2012-04-20T16:48:21.057 回答
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])
于 2012-04-20T17:37:56.387 回答
1

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

于 2012-04-20T16:25:39.680 回答
-1

他们在 Python 中很明显的一个是列表理解- 这是一种更快(且更易读)的方式:

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}

这里还值得注意的是,您似乎正在使用制表符分隔的文件。在这种情况下,模块csv可能是比使用更好的选择split()

另请注意,使用with语句将增加可读性并确保您的文件被关闭(即使在异常情况下)。

如果在每个循环上都执行打印语句,它们也会大大减慢速度——它们对于调试很有用,但是当在你的主要数据块上运行时,可能值得删除它们。

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

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')

编辑:根据评论中的要求,列出 comp 与正常循环:

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 与 msec。它不是巨大的,但它是一些东西。

于 2012-04-20T16:27:12.953 回答