如何在初始化方面改进我的Trie实现?

时间:2011-10-01 20:40:14

标签: python optimization data-structures trie

我正在尝试从一个巨大的单词列表中读取并以一种允许我稍后快速检索的方式存储它们。我首先想到使用trie,我会承认我的实现很幼稚,它基本上是嵌套的哈希表,每个键都是不同的字母。现在,在trie中插入一个单词需要花费很长时间(运行此程序需要20多秒),我想知道是否有人有任何想法可以做些什么来改善我的插入?这不是作业。

import string
import time

class Trie:

    def __init__(self):
        self.root = TrieNode()

    def insert_word(self, word):
        current_node = self.root
        for letter in word:
            trie_node = current_node.get_node(letter)
            current_node = trie_node

class TrieNode:

    def __init__(self):
        self.data = {}

    def get_node(self, letter):
        if letter in self.data:
            return self.data[letter]
        else:
            new_trie_node = TrieNode()
            self.data[letter] = new_trie_node
            return new_trie_node

def main():
    start_time = time.time()
    trie = Trie()

    with open('/usr/share/dict/words', 'r') as dictionary:
        word_list = dictionary.read()
    word_list = word_list.split("\n")

    for word in word_list:
        trie.insert_word(word.lower())

    print time.time() - start_time, "seconds"


if __name__ == "__main__":
    main()

2 个答案:

答案 0 :(得分:0)

在考虑您的搜索工具是否正常工作之前,加速您的trie初始化是完全没有意义的。

在@unutbu提到的代码中,为什么你认为它与{'end':False}pt['end']=True混在一起?

以下是您的一些测试数据:

words_to_insert = ['foo', 'foobar']
queries_expecting_true = words_to_insert
queries_expecting_false = "fo foe foob food foobare".split()

这是另一个想法:你没有表明你想要的东西比确定是否存在查询词的能力更多。如果这是正确的,您应该考虑使用内置的set对DIY trie进行基准测试。标准:加载速度(考虑从泡菜中执行此操作),查询速度和内存使用情况。

如果您确实需要检索的内容超过bool,请将dict替换为set并重新阅读此答案。

如果您确实想在输入字符串中搜索单词,那么您可以考虑@unutbu引用的代码,修复了错误并在find函数中加了一些加速(仅评估len(input)一次) ,使用xrange代替range(Python 2.x))并删除不必要的TERMINAL: False条目:

TERMINAL = None # Marks the end of a word

def build(words, trie=None): # bugs fixed
    if trie is None:
        trie = {}
    for word in words:
        if not word: continue # bug fixed
        pt = trie # bug fixed
        for ch in word:
            pt = pt.setdefault(ch, {})
        pt[TERMINAL] = True
    return trie

def find(input, trie):
    len_input = len(input)
    results = []
    for i in xrange(len_input):
        pt = trie
        for j in xrange(i, len_input + 1):
            if TERMINAL in pt:
                results.append(input[i:j])
            if j >= len_input or input[j] not in pt:
                break
            pt = pt[input[j]]
    return results    

或者您可以查看Danny Yoo's fast implementationAho-Corasick algorithm

答案 1 :(得分:-1)

还有Trie here的替代实现。

Trie.insert_wordbuild进行比较:

def build(words,trie={'end':False}):
    '''
    build builds a trie in O(M*L) time, where
        M = len(words)
        L = max(map(len,words))
    '''
    for word in words:
        pt=trie
        for letter in word:
            pt=pt.setdefault(letter, {'end':False})
        pt['end']=True
    return trie

使用Trie,对于letter中的每个wordinsert_word调用current_node.get_node(letter)。此方法具有ifelse块,并且必须在到达TrieNode块时实例化新的else,然后将新的键值对插入到self.data dict。

使用build,特里本身就是一个字典。对于letter中的每个word,只需拨打pt.setdefault(...)一次。 dict方法在C中实现,并且比在Python中实现类似代码更快。

timeit显示速度差异大约2倍(赞成build):

def alt_main():
    with open('/usr/share/dict/words', 'r') as dictionary:
        word_list = dictionary.read()
    word_list = word_list.split("\n")
    return build(word_list)


% python -mtimeit -s'import test' 'test.main()'
10 loops, best of 3: 1.16 sec per loop

% python -mtimeit -s'import test' 'test.alt_main()'
10 loops, best of 3: 571 msec per loop