使用python在大文本文件中搜索字符串的快速方法

时间:2013-02-22 22:48:44

标签: python regex trie

这是我目前的情况:

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

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

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

6 个答案:

答案 0 :(得分:5)

最简单,最快速的解决方案:

#!/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

答案 1 :(得分:2)

由于文件已经被排序和读入,因此您可以在其上使用二进制搜索,而无需借助任何奇特的数据结构。 Python有一个内置的二进制搜索功能,bisect.bisect_left`

答案 2 :(得分:1)

使用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']
>>> 

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

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

然后,在那时,您可能正在接近b树或b +树实现,以使索引有效。所以你可以使用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']

答案 5 :(得分:0)

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