如何在许多文件中一次有效地搜索多个字符串?

时间:2016-04-28 18:54:43

标签: python shell csv awk

您好我发布了我的部分代码,然后我将解释我的目标:

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

所以"匹配"我得到了一个csv文件列表(它们的路径)。我打开每个csv文件,然后用readlines()将它们加载到内存中。 " RS"是唯一ID的列表。对于列表中的每个元素" rs"我需要搜索csv文件的每一行并在每次在文件中找到id时打印(稍后我将测试该行是否包含另一个固定的单词)。

上面的代码适用于我的目的,但我不知道为什么处理400k行文件需要10多分钟,我需要为数千个文件执行此任务,因此它不可能我完成任务。在我看来,缓慢的部分是测试过程,但我不确定。

请注意我使用python是因为我对它更加舒适,如果使用其他工具我的问题有任何其他解决方案我可以使用它。

编辑: 我会尝试发布一些例子

"rs" list:
rs12334435
rs3244567
rs897686
....

files

# header data not needed
# data
# data
# data
# data
# data [...]
#COLUMN1    COLUMN2               COLUMN3   ...
data        rs7854.rs165463       dataSS=1(random_data)
data        rs465465data          datadata
data        rs798436              dataSS=1  
data        datars45648           dataSS=1

最终目标是计算每个文件在每个文件上出现的次数,如果在第3列中有SS = 1则在输出中标记它。 像

这样的东西
found rs12345 SS yes file 3 folder /root/foobar/file
found rs74565 SS no file 3 folder /root/foobar/file

2 个答案:

答案 0 :(得分:1)

很多问题是因为你有这么多嵌套循环。你可以通过消除循环来加快程序的运行:

  1. 其中一个循环位于文件中的每一行。但如果全部 你想要做的是确定是否存在任何匹配 文件,您可以在一个操作中搜索整个文件正文。成为 当然,这会搜索更长的字符串,但它会在一次操作中执行 在本机代码中,而不是在Python中。

  2. 循环遍历所有匹配字符串。但你知道那些在你面前的人 开始,每个文件都是相同的。所以这是一个很好的案例 在做什么时 - 更多的前期工作将在时间上节省下来 其余的计划。 Stand back, I'm going to use a regular expression

  3. 这是一个结合了以下两个想法的代码版本:

    import re
    import random
    import sys
    import time
    
    # get_patterns makes up some test data.
    def get_patterns():
        rng = random.Random(1)  # fixed seed, for reproducibility
        n = 300
        # Generate up to n unique integers between 60k and 80k.
        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种模式,我的实现在我的计算机上运行400ms。这大约是加速的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'或'你',你可能正在查看匹配,并应检查第二个字符,看它是否分别为“o”或“m”。等等。如果你到达文件的结尾od,你将找到文件中的所有匹配qre来查找。

    告诉计算机一次查找多个字符串的便捷方法是使用正则表达式。普通字符串是正则表达式。正则表达式'foobar'匹配'foobar里面的子字符串'foobar'是模糊的'。但是,你可以做更复杂的事情。您可以将两个正则表达式组合成一个组合的正则表达式,每个正则表达式都匹配一些东西,这些表达式将匹配这些事件中的任这是通过交替符号“|”完成的。所以正则表达式'foobar | umspquux'将匹配'foobar'或'umspquux'。你也可以匹配一个真正的'|'通过转义“|”的意义用反斜杠'\'。

    这就是build_regex_literal_patterns的全部内容。它会将列表['foobar','umspquux']转换为字符串'foobar | umspquux'。尝试一下 - 将函数定义放入您自己的文件中,并使用一些try-out参数调用它以查看它的行为。

    顺便说一句,这是一个好方法,可以弄清楚任何一段代码是如何工作的 - 运行部分代码并打印中间结果。对于当然有副作用的程序,这很难安全地做,但是这个程序没有。

    re.escape中对build_regex_literal_patterns的调用只是确保对任何特殊的正则表达式运算符(例如“|”)进行转义(在本例中为'\ |'),以便它们匹配自己

    这里正则表达式方法的最后一部分是使用已编译正则表达式的 findall 方法。这只是在输入字符串(即文件正文)中返回正则表达式的所有匹配项。

    您可以在Python documentation on regular expressions中阅读Python正则表达式。该文档基本上是参考资料,因此您可以在Google Develoeprs site has an introduction to Python regular expressions更好地找到更温和的介绍作为起点。 Jeffrey Friedl's Mastering Regular Expressions是关于正则表达式的非常全面的工作,认为它不会覆盖正则表达式的Python方言。

答案 1 :(得分:0)

你最大的内存密集型操作是读取每一行,这应该在外循环上。

您也不想使用readlines一次阅读整个文档,而是使用readline。 Readline的内存密集程度要低得多。

for eachcsv in matches:
    with open(eachcsv, 'r') as f:
        for line in f:
            for entry in rs:
                if entry in line:
                    print("found %s in %s" % (entry, eachcsv))

更多阅读https://docs.python.org/2/tutorial/inputoutput.html

您还可以采取其他超出此问题范围的优化,例如使用线程或使用多处理同时读入许多csv文件。 https://docs.python.org/2/library/threading.html

import threading

def findRSinCSVFile(csvFile,rs)
    with open(csvFile, 'r') as f:
        for line in f:
            for entry in rs:
                if entry in line:
                    print("found %s in %s" % (entry, eachcsv))

for csvFile in csvFiles():
    threads.append(threading.Thread(target=findRSinCSVFile,args=(csvFile,rs)))

for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

这将允许您同时解析所有csv文件。

(注意我没有测试任何此代码,仅作为示例)