很大一部分问题是由于嵌套循环太多所致。通过消除循环,您可能可以使程序更快:
- 其中一个循环是在文件中的每一行上执行的。但是,如果您只想确定文件中是否存在任何匹配项,则可以在一次操作中搜索整个文件正文。确保这样做会搜索更长的字符串,但它是在本机代码中进行而不是在Python中进行。
- 您遍历所有匹配字符串。但是,在开始之前,您已经知道了它们,并且对于每个文件都是相同的。因此,这是一个很好的例子,说明在前期做更多工作将在程序的其余时间中节省时间的情况。
这里是结合了这两个想法的代码版本:
import re
import random
import sys
import time
def get_patterns():
rng = random.Random(1)
n = 300
return list(set([str(rng.randint(60000, 80000)) for _ in xrange(n)]))
def original(rs, matches):
for eachcsv in matches:
with open(eachcsv, 'r') as f:
lines = f.readlines()
for entry in rs:
for line in lines:
if entry in line:
print("found %s in %s" % (entry, eachcsv))
def mine(rs, matches):
my_rx = re.compile(build_regex(rs))
for eachcsv in matches:
with open(eachcsv, 'r') as f:
body = f.read()
matches = my_rx.findall(body)
for match in matches:
print "found %s in %s" % (match, eachcsv)
def build_regex(literal_patterns):
return "|".join([re.escape(pat) for pat in literal_patterns])
def print_elapsed_time(label, callable, args):
t1 = time.time()
callable(*args)
t2 = time.time()
elapsed_ms = (t2 - t1) * 1000
print "%8s: %9.1f milliseconds" % (label, elapsed_ms)
def main(args):
rs = get_patterns()
filenames = args[1:]
for function_name_and_function in (('original', original), ('mine', mine)):
name, func = function_name_and_function
print_elapsed_time(name, func, [rs, filenames])
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
你的原始代码在original
中,我的替换代码在mine
中。
对于300个模式,在我的电脑上实现的运行时间为400毫秒,速度提高了约30倍。对于更多的匹配字符串,改进应该会更大。将模式数量翻倍,您的实现运行时间大致翻倍,但基于正则表达式的实现仅需要多约3%的时间(尽管这部分是因为我的测试模式都有相似的前缀,而真实数据可能不是这样)。
编辑:更新代码以打印每个匹配项的消息。我的代码现在有点慢,但仍然是一种改进,并且在匹配更多字符串时应该相对更快:
~/source/stackoverflow/36923237$ python search.py example.csv
found green fox in example.csv
original: 9218.0 milliseconds
found green fox in example.csv
mine: 600.4 milliseconds
编辑: 根据要求解释正则表达式技术。
假设您想在文件中搜索字符串foobar和umspquux。一种方法是先搜索文件中的foobar,然后再搜索umspquux。这是您开始使用的方法。
另一种方法是同时搜索两个字符串。想象一下,您检查文件的第一个字符。如果它是'f'或'u',那么您可能正在寻找一个匹配项,并且应该检查第二个字符是否分别为'o'或'm'。依此类推。如果到达文件结尾,则已经找到了文件中要查找的所有匹配项。
一种告诉计算机同时查找多个字符串的方便方法是使用正则表达式。普通字符串也是正则表达式。正则表达式'foobar'匹配'the foobar is all fuzzy'中的子字符串'foobar'。但是,您可以做更复杂的事情。您可以将两个分别匹配某些内容的正则表达式组合成一个组合的正则表达式,该组合的正则表达式将匹配其中任何一个内容。这是使用备选符号'|'完成的。因此,正则表达式'foobar|umspquux'将匹配'foobar'或'umspquux'。您还可以通过反斜杠'\'转义'|'的含义来匹配真正的'|'。
这就是'build_regex_literal_patterns'的全部内容。它将把列表['foobar','umspquux']转换为字符串'foobar | umspquux'。试一下-将函数定义放入自己的文件中,并使用一些尝试性参数调用它,以查看其行为。
顺便说一句,这是弄清楚任何代码如何工作的好方法-运行其中的一部分并打印中间结果。当然,对于具有副作用的程序来说,这样做更难,但这个程序没有。
build_regex_literal_patterns
方法中调用re.escape
函数,该函数简单地确保任何特殊的正则表达式运算符(如'|')都被转义(在这种情况下变成'\|'),以便它们能够与自身匹配。
这里正则表达式方法的最后一部分是使用编译好的正则表达式的findall方法。这仅仅返回输入字符串(即文件内容)中所有与我们的正则表达式匹配的结果。
你可以在Python正则表达式文档中了解Python正则表达式。那个文档基本上是参考材料,因此你可能会发现Google开发者网站上的Python正则表达式介绍更适合作为起点的一个更加友善的介绍。Jeffrey Friedl的《精通正则表达式》是关于正则表达式的一本非常全面的著作,虽然它没有涵盖Python方言的正则表达式。
for line in f:
代替lines = f.readlines(); for line in lines:
是否有助于提高速度,因为它不需要在开始循环之前将400k行加载到内存中? - jonhopkins