【问题标题】:Fast method to search for a string in a big text file with python使用python在大文本文件中搜索字符串的快速方法
【发布时间】:2013-02-22 22:48:43
【问题描述】:

这就是我现在的情况:

  • 我有一个 2.5MB 的文本文件,其中包含大约 250k 个字符串,按字母顺序排序
  • 每个字符串都是唯一的
  • 我不需要修改文本文件中的条目:文本文件一旦加载,就永远不会被编辑
  • 文本文件在开始时加载,然后我只需要通过它搜索字符串

最后一点是问题。实际上我需要搜索字符串的完全匹配和部分匹配。我写的算法只是涉及使用正则表达式并结合一些尝试使过程更快:例如,我将识别字母表中单数字母的字典索引硬编码到我的脚本中,然后拆分大文本文件虚构成 26 个小字典。 那完全没用,脚本仍然非常慢。 浏览了这里的一些帖子,我被说服尝试 mmap:但在给定正则表达式的情况下,找到所有部分匹配项看起来毫无用处。 最终我得出结论,尝试可以解决我的问题,尽管我几乎不知道这是什么。我应该尝试吗?如果是这样,我应该如何继续在 python 中创建 trie? Is marisa-trie module good?谢谢大家

编辑:通过“部分匹配”,我的意思是我有一个字符串的前缀。我不需要在结尾或中间匹配,只需要在开头。

【问题讨论】:

  • 请详细说明您所说的部分匹配是什么意思。
  • 是的,是部分匹配每个字符串的前缀,还是包含每个字符串的任何子字符串?如果匹配需要是子字符串,则构建 trie 将无济于事。
  • 如果您需要在字符串的中间或末尾匹配,Trie 不会帮助您:
  • @user1068051 你可以试试 whoosh 库。您可以搜索确切的字符串或通配符搜索

标签: python regex trie


【解决方案1】:

最简单、最快的解决方案:

#!/usr/bin/env python

d = {}

# open your file here, i'm using /etc/hosts as an example...
f = open("/etc/hosts","r")
for line in f:
    line = line.rstrip()
    l = len(line)+1
    for i in xrange(1,l):
        d[line[:i]] = True
f.close()


while True:
    w = raw_input('> ')
    if not w:
        break

    if w in d:
        print "match found", w

这里稍微复杂一些,但内存效率高:

#!/usr/bin/env python

d = []

def binary_search(a, x, lo=0, hi=None):
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        midval = a[mid]
        if midval < x:
            lo = mid+1
        elif midval > x:
            hi = mid
        else:
            return mid
    return -1


f = open("/etc/hosts","r")
for line in f:
    line=line.rstrip()
    l = len(line)+1
    for i in xrange(1,l):
        x = hash(line[:i])
        d.append(x)
f.close()

d.sort()

while True:
    w = raw_input('> ')
    if not w:
        break

    if binary_search(d, hash(w)) != -1:
        print "match found", w

【讨论】:

  • 非常感谢!我不能选择使用字典,因为我需要按字母顺序对字符串进行排序——这对于字典是不可能的。我会尝试你的最后一个解决方案
  • 您可以尝试使用 OrderedDict 模块,而不是 dict。它也可能有助于第一个解决方案。 :)
【解决方案2】:

由于文件已经排序并读入,您可以对其使用二进制搜索,而无需求助于任何花哨的数据结构。 Python 内置了二分查找功能,bisect.bisect_left`

【讨论】:

    【解决方案3】:

    使用trie

    #dictionary is a list of words
    def parse_dictionary(dictionary):
        dictionary_trie = {}
        for word in dictionary:
            tmp_trie = dictionary_trie
            for letter in word:
                if letter not in tmp_trie:
                    tmp_trie[letter] = {}
                if 'words' not in tmp_trie[letter]:
                    tmp_trie[letter]['words'] = []
    
                tmp_trie[letter]['words'].append(word)
                tmp_trie = tmp_trie[letter]
        return dictionary_trie
    
    def matches(substring, trie):
        d = trie
        for letter in substring:
            try:
                d = d[letter]
            except KeyError:
                return []
        return d['words']
    

    使用示例:

    >>> import pprint
    >>> dictionary = ['test', 'testing', 'hello', 'world', 'hai']
    >>> trie = parse_dictionary(dictionary)
    >>> pprint.pprint(trie)
    {'h': {'a': {'i': {'words': ['hai']}, 'words': ['hai']},
           'e': {'l': {'l': {'o': {'words': ['hello']}, 'words': ['hello']},
                       'words': ['hello']},
                 'words': ['hello']},
           'words': ['hello', 'hai']},
     't': {'e': {'s': {'t': {'i': {'n': {'g': {'words': ['testing']},
                                         'words': ['testing']},
                                   'words': ['testing']},
                             'words': ['test', 'testing']},
                       'words': ['test', 'testing']},
                 'words': ['test', 'testing']},
           'words': ['test', 'testing']},
     'w': {'o': {'r': {'l': {'d': {'words': ['world']}, 'words': ['world']},
                       'words': ['world']},
                 'words': ['world']},
           'words': ['world']}}
    >>> matches('h', trie)
    ['hello', 'hai']
    >>> matches('he', trie)
    ['hello']
    >>> matches('asd', trie)
    []
    >>> matches('test', trie)
    ['test', 'testing']
    >>> 
    

    【讨论】:

      【解决方案4】:

      您可以创建一个列表,让每一行成为列表的一个元素并进行二分搜索。

      【讨论】:

      • @nhahtdh,问题明确指出要找到的文本是前缀,即在行首。
      【解决方案5】:

      因此,为了解释 arainchi 的非常好的答案,请制作一个字典,其中包含文件中每一行的条目。然后,您可以将搜索字符串与这些条目的名称进行匹配。字典对于这种搜索非常方便。

      【讨论】:

        【解决方案6】:

        使用 trie 仍然需要您构建一个 trie,它是 O(n) 来迭代整个文件——利用排序将使其成为 O(log_2 n)。因此,这个更快的解决方案将使用二分搜索(见下文)。

        此解决方案仍需要您读入整个文件。在更快的解决方案中,您可以预处理文件并填充所有行,使它们具有相同的长度(或在文件中构建某种索引结构,以使寻找到列表中间可行) - - 然后寻找文件的中间会带你到列表的中间。 “更快”的解决方案可能只需要一个非常非常大的文件(千兆字节或数百兆字节)。你会让他们把这个和二分搜索结合起来。

        可能,如果文件系统支持sparse files-- 执行上述填充方案不会增加磁盘上实际使用的文件块。

        然后,此时,您可能正在接近 b-tree 或 b+tree 实现以提高索引效率。所以你可以使用b-tree library

        类似这样的:

        import bisect
        
        entries = ["a", "b", "c", "cc", "cd", "ce", "d", "e", "f" ]
        
        def find_matches(ls, m):
        
            x = len(ls) / 2
            match_index = -1
        
            index = bisect.bisect_left(ls, m)
            matches = []
        
            while ls[index].startswith(m):
                matches.append(ls[index])
                index += 1
        
            return matches
        
        print find_matches(entries, "c")
        

        输出:

        >>> ['c', 'cc', 'cd', 'ce']
        

        【讨论】:

          猜你喜欢
          • 2016-10-25
          • 2016-10-08
          • 2014-09-19
          • 2016-08-23
          • 2013-01-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多