Python 在文件的每一行上循环的最有效方式是什么?

Python 在文件的每一行上循环的最有效方式是什么?,python,regex,loops,Python,Regex,Loops,我有一个文件dataset.nt,它不是太大(300Mb)。我还有一个列表,其中包含大约500个元素。对于列表中的每个元素,我想计算文件中包含它的行数,并将该键/值对添加到字典中(键是列表元素的名称,值是该元素在文件中出现的次数) 这是我为达到这一结果所做的第一件事: mydict = {} for i in mylist: regex = re.compile(r"/Main/"+re.escape(i)) total = 0 with open("dataset.n

我有一个文件dataset.nt,它不是太大(300Mb)。我还有一个列表,其中包含大约500个元素。对于列表中的每个元素,我想计算文件中包含它的行数,并将该键/值对添加到字典中(键是列表元素的名称,值是该元素在文件中出现的次数)

这是我为达到这一结果所做的第一件事:

mydict = {}

for i in mylist:
    regex = re.compile(r"/Main/"+re.escape(i))
    total = 0
    with open("dataset.nt", "rb") as input:
        for line in input:
            if regex.search(line):
                total = total+1
    mydict[i] = total
它不起作用(如中所示,它无限期地运行),我想我应该找到一种方法,不让每行读500次。所以我试了一下:

mydict = {}

with open("dataset.nt", "rb") as input:
    for line in input:
        for i in mylist:
            regex = re.compile(r"/Main/"+re.escape(i))
            total = 0
            if regex.search(line):
                total = total+1
            mydict[i] = total
mydict = {}

file = open("dataset.nt", "rb")

while 1:
    lines = file.readlines(100000)
    if not lines:
        break
    for line in lines:
        for i in list:
            regex = re.compile(r"/Main/"+re.escape(i))
            total = 0
            if regex.search(line):
                total = total+1
            mydict[i] = total
性能没有提高,脚本仍然无限期运行。所以我在谷歌上搜索了一下,我尝试了这个:

mydict = {}

with open("dataset.nt", "rb") as input:
    for line in input:
        for i in mylist:
            regex = re.compile(r"/Main/"+re.escape(i))
            total = 0
            if regex.search(line):
                total = total+1
            mydict[i] = total
mydict = {}

file = open("dataset.nt", "rb")

while 1:
    lines = file.readlines(100000)
    if not lines:
        break
    for line in lines:
        for i in list:
            regex = re.compile(r"/Main/"+re.escape(i))
            total = 0
            if regex.search(line):
                total = total+1
            mydict[i] = total
这台已经运行了30分钟了,所以我想不会有任何好转


我应该如何构造此代码以使其在合理的时间内完成?

我希望对您的第二个版本稍作修改:

mydict = {}

re_list = [re.compile(r"/Main/"+re.escape(i)) for i in mylist]
with open("dataset.nt", "rb") as input:
    for line in input:
        # any match has to contain the "/Main/" part
        # -> check it's there
        # that may help a lot or not at all
        # depending on what's in your file
        if not '/Main/' in line:
            continue 

        # do the regex-part
        for i, regex in zip(mylist, re_list):
            total = 0
            if regex.search(line):
                total = total+1
            mydict[i] = total
正如@matsjoyce已经建议的那样,这避免了在每次迭代中重新编译正则表达式。 如果你真的需要这么多不同的正则表达式模式,那么我认为你没什么可以做的


也许值得检查一下,您是否可以用正则表达式捕获“/Main/”之后的内容,然后将其与您的列表进行比较。这可能有助于减少“真实”正则表达式搜索的数量。

看起来是一些map/reduce-like并行化的好选择。。。您可以将数据集文件拆分为N个块(其中N=您有多少个处理器),启动N个子进程,每个子进程扫描一个块,然后对结果求和

当然,这并不妨碍您首先优化扫描,即(基于sebastian的代码):


请注意,如果您从数据集中发布了一个示例,那么这可能会得到更好的优化。例如,如果您的数据集可以在“/Main/{i}”上排序(例如,使用系统
sort
程序),您就不必为
i
的每个值检查每一行。或者,如果行中“/Main/”的位置已知且固定,则可以在字符串的相关部分使用简单的字符串比较(这可能比regexp快)。

其他解决方案非常好。但是,由于每个元素都有一个正则表达式,并且如果元素在每行中出现不止一次,这一点并不重要,因此可以使用来计算包含目标表达式的行数

另外,在一定数量的行之后,最好将孔文件(如果您有足够的内存,并且它不是设计限制)读取到内存中

    import re

    mydict = {}
    mylist = [...] # A list with 500 items
    # Optimizing calls
    findall = re.findall  # Then python don't have to resolve this functions for every call
    escape = re.escape

    with open("dataset.nt", "rb") as input:
        text = input.read() # Read the file once and keep it in memory instead access for read each line. If the number of lines is big this is faster.
        for elem in mylist:
            mydict[elem] = len(findall(".*/Main/{0}.*\n+".format(escape(elem)), text)) # Count the lines where the target regex is.
我用一个800Mb大小的文件来测试这一点(我想看看把这么大的文件加载到内存中需要多长时间,比你想象的要快)


我不使用真实数据测试整个代码,只测试
findall
部分。

对于上一部分,您肯定希望将regex创建移出循环。首先将它们构建到一个列表中,然后进行查找或其他操作。这可能对其中的一部分有用尝试在中使用
而不是regex,看看这是否有帮助。如果文件中有任何结构使得这些单词只出现在某些位置,它也可能会减少搜索。@goncalopp你能给我一个“in”函数的文档链接吗?同意@matschyce和goncaloop。。。看起来您不需要正则表达式,如果需要,您应该将正则表达式编译移出循环,因为
mylist
re_list
都是不变的,您也可以从循环中提取
zip()
调用。另外,对于每一行,你都会丢弃前面的总数,是的:优化是一门很难的艺术,我们通常不善于猜测真正的瓶颈在哪里,所以一旦你完成了显而易见的工作(分解循环中的不变量等)你最好还是使用一个分析器。这个答案让我有点困惑——我对Python并没有非常高级的知识。1) “按主目录排序”是指按字母顺序对文件的每一行进行排序吗?2) 如果文件已排序,我需要更改什么以避免检查每一行?@kormak I的意思是“根据目标值对行进行排序”-应该在“/Main/”之后。如果您可以轻松地对输入(行)进行排序,那么就可以避免内部循环(=每行约500次迭代)。我认为ypu的意思是:len(findall(.*/Main/{0}.*\n+)。format(escape(elem)),text))。此外,我不确定在试图提高性能时在正则表达式中使用*是否是一个好主意?关于
,text])
缺少的部分,您是对的。但是关于正则表达式,您可以修改它,以便不使用
*
,也许积极的展望可以完成这项工作。但是我想您应该明白了:只需加载一次整个文件,然后使用如下语句优化函数调用:
findall=re.findall