我是Python的新手并且正在努力学习和进步。我对TRIE和DAWG很感兴趣,我一直在阅读它,但我不明白输出TRIE或DAWG文件应该是什么样的。
我想了解最佳的输出结构,以便弄清楚如何创建和使用它。
我也很欣赏DAWG的输出以及 TRIE 。
我不希望看到彼此相关的气泡的图形表示,我在阅读时看到它们很多。
一旦将一组单词转换为TRIE或DAWG,我想知道输出对象。
谢谢。
答案 0 :(得分:138)
Unwind基本上是正确的,有许多不同的方法来实现trie;对于大型,可扩展的特里,嵌套字典可能变得麻烦 - 或者至少空间效率低下。但是,既然你刚刚开始,我认为这是最简单的方法;你可以在几行中编写一个简单的trie
代码。首先,构建trie的函数:
>>> _end = '_end_'
>>>
>>> def make_trie(*words):
... root = dict()
... for word in words:
... current_dict = root
... for letter in word:
... current_dict = current_dict.setdefault(letter, {})
... current_dict[_end] = _end
... return root
...
>>> make_trie('foo', 'bar', 'baz', 'barz')
{'b': {'a': {'r': {'_end_': '_end_', 'z': {'_end_': '_end_'}},
'z': {'_end_': '_end_'}}},
'f': {'o': {'o': {'_end_': '_end_'}}}}
如果您不熟悉setdefault
,则只需在字典中查找密钥(此处为letter
或_end
)。如果密钥存在,则返回关联的值;如果没有,它会为该键分配一个默认值并返回值({}
或_end
)。 (它就像get
的版本一样,也会更新字典。)
接下来,测试单词是否在trie中的函数。这可能更简洁,但我将它留下冗长,以便逻辑清晰:
>>> def in_trie(trie, word):
... current_dict = trie
... for letter in word:
... if letter in current_dict:
... current_dict = current_dict[letter]
... else:
... return False
... else:
... if _end in current_dict:
... return True
... else:
... return False
...
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'baz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barzz')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'bart')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'ba')
False
我会留下插入和移除作为练习。
当然,Unwind的建议不会太难。可能存在轻微的速度劣势,因为找到正确的子节点将需要线性搜索。但搜索将仅限于可能的字符数 - 如果我们包含_end
,则为27。此外,根据他的建议,通过创建大量节点列表并通过索引访问它们,无法获得任何好处;你可能只是嵌套列表。
最后,我要补充一点,创建一个DAWG会有点复杂,因为你必须检测当前单词与结构中另一个单词共享后缀的情况。事实上,这可能会变得相当复杂,这取决于你想要如何构建DAWG!您可能需要了解一些关于Levenshtein distance的内容才能做到正确。
答案 1 :(得分:23)
看看这个:
https://github.com/kmike/marisa-trie
Python的静态内存高效Trie结构(2.x和3.x)。
MARISA-trie中的字符串数据可能比内存少50x-100x 在标准的Python词典中;原始查找速度可比;特里 还提供快速高级方法,如前缀搜索。
基于marisa-trie C ++库。
以下是成功使用marisa trie的公司的博文:
https://www.repustate.com/blog/sharing-large-data-structure-across-processes-python/
在Repustate,我们在文本分析中使用的大部分数据模型都可以表示为简单的键值对,或Python术语中的词典。在我们的特殊情况下,我们的字典非常庞大,每个字典几百MB,并且需要不断访问它们。实际上,对于给定的HTTP请求,可以访问4或5个模型,每个模型执行20-30次查找。因此,我们面临的问题是如何为客户端保持快速,并尽可能为服务器提供照明。
...
我找到了这个包,marisa tries,这是一个围绕marisa trie的C ++实现的Python包装器。 “Marisa”是使用递归实现的StorAge的匹配算法的首字母缩写。关于marisa尝试的好处是存储机制真正缩小了你需要多少内存。 Python插件的作者声称缩小了50-100倍 - 我们的经验类似。
marisa trie软件包的优点在于底层的trie结构可以写入磁盘,然后通过内存映射对象读入。通过内存映射marisa trie,我们现在满足了所有要求。我们的服务器内存使用率急剧下降了大约40%,而且我们的性能与使用Python的字典实现时相比没有变化。
还有一些纯python实现,但除非你在受限制的平台上,否则你需要使用上面的C ++支持的实现来获得最佳性能:
答案 2 :(得分:15)
以下是实现Trie的python包列表:
答案 3 :(得分:13)
没有“应该”;由你决定。各种实现将具有不同的性能特征,需要花费不同的时间来实现,理解和正确。在我看来,这是整个软件开发的典型特征。
我可能首先尝试拥有目前创建的所有trie节点的全局列表,并将每个节点中的子指针表示为全局列表中的索引列表。有一本字典只是为了代表孩子的连接感觉太重了,对我来说。
答案 4 :(得分:11)
从senderle
方法修改(上图)。我发现Python defaultdict
非常适合创建trie或前缀树。
from collections import defaultdict
class Trie:
"""
Implement a trie with insert, search, and startsWith methods.
"""
def __init__(self):
self.root = defaultdict()
# @param {string} word
# @return {void}
# Inserts a word into the trie.
def insert(self, word):
current = self.root
for letter in word:
current = current.setdefault(letter, {})
current.setdefault("_end")
# @param {string} word
# @return {boolean}
# Returns if the word is in the trie.
def search(self, word):
current = self.root
for letter in word:
if letter not in current:
return False
current = current[letter]
if "_end" in current:
return True
return False
# @param {string} prefix
# @return {boolean}
# Returns if there is any word in the trie
# that starts with the given prefix.
def startsWith(self, prefix):
current = self.root
for letter in prefix:
if letter not in current:
return False
current = current[letter]
return True
# Now test the class
test = Trie()
test.insert('helloworld')
test.insert('ilikeapple')
test.insert('helloz')
print test.search('hello')
print test.startsWith('hello')
print test.search('ilikeapple')
答案 5 :(得分:4)
如果你想把一个TRIE实现为一个Python类,这是我在阅读之后写的东西:
class Trie:
def __init__(self):
self.__final = False
self.__nodes = {}
def __repr__(self):
return 'Trie<len={}, final={}>'.format(len(self), self.__final)
def __getstate__(self):
return self.__final, self.__nodes
def __setstate__(self, state):
self.__final, self.__nodes = state
def __len__(self):
return len(self.__nodes)
def __bool__(self):
return self.__final
def __contains__(self, array):
try:
return self[array]
except KeyError:
return False
def __iter__(self):
yield self
for node in self.__nodes.values():
yield from node
def __getitem__(self, array):
return self.__get(array, False)
def create(self, array):
self.__get(array, True).__final = True
def read(self):
yield from self.__read([])
def update(self, array):
self[array].__final = True
def delete(self, array):
self[array].__final = False
def prune(self):
for key, value in tuple(self.__nodes.items()):
if not value.prune():
del self.__nodes[key]
if not len(self):
self.delete([])
return self
def __get(self, array, create):
if array:
head, *tail = array
if create and head not in self.__nodes:
self.__nodes[head] = Trie()
return self.__nodes[head].__get(tail, create)
return self
def __read(self, name):
if self.__final:
yield name
for key, value in self.__nodes.items():
yield from value.__read(name + [key])
答案 6 :(得分:3)
此版本使用递归
import pprint
from collections import deque
pp = pprint.PrettyPrinter(indent=4)
inp = raw_input("Enter a sentence to show as trie\n")
words = inp.split(" ")
trie = {}
def trie_recursion(trie_ds, word):
try:
letter = word.popleft()
out = trie_recursion(trie_ds.get(letter, {}), word)
except IndexError:
# End of the word
return {}
# Dont update if letter already present
if not trie_ds.has_key(letter):
trie_ds[letter] = out
return trie_ds
for word in words:
# Go through each word
trie = trie_recursion(trie, deque(word))
pprint.pprint(trie)
输出:
Coool <algos> python trie.py
Enter a sentence to show as trie
foo bar baz fun
{
'b': {
'a': {
'r': {},
'z': {}
}
},
'f': {
'o': {
'o': {}
},
'u': {
'n': {}
}
}
}
答案 7 :(得分:3)
这是使用 TrieNode 类的完整代码。还实现了 auto_complete 方法以返回带有前缀的匹配词。
由于我们使用字典来存储子节点,因此不需要将char转换为整数,反之亦然,也不需要提前分配数组内存。
class TrieNode:
def __init__(self):
#Dict: Key = letter, Item = TrieNode
self.children = {}
self.end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def build_trie(self,words):
for word in words:
self.insert(word)
def insert(self,word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.end = True
def search(self, word):
node = self.root
for char in word:
if char in node.children:
node = node.children[char]
else:
return False
return node.end
def _walk_trie(self, node, word, word_list):
if node.children:
for char in node.children:
word_new = word + char
if node.children[char].end:
# if node.end:
word_list.append( word_new)
# word_list.append( word)
self._walk_trie(node.children[char], word_new , word_list)
def auto_complete(self, partial_word):
node = self.root
word_list = [ ]
#find the node for last char of word
for char in partial_word:
if char in node.children:
node = node.children[char]
else:
# partial_word not found return
return word_list
if node.end:
word_list.append(partial_word)
# word_list will be created in this method for suggestions that start with partial_word
self._walk_trie(node, partial_word, word_list)
return word_list
创建一个 Trie
t = Trie()
words = ['hi', 'hieght', 'rat', 'ram', 'rattle', 'hill']
t.build_trie(words)
搜索词
words = ['hi', 'hello']
for word in words:
print(word, t.search(word))
hi True
hel False
使用前缀搜索词
partial_word = 'ra'
t.auto_complete(partial_word)
['rat', 'rattle', 'ram']
答案 8 :(得分:2)
Trie数据结构可用于将数据存储在O(L)
中,其中L是字符串的长度,因此要插入N个字符串,时间复杂度将是O(NL)
,可在{{1}中搜索该字符串}删除同样如此。
可以从https://github.com/Parikshit22/pytrie.git克隆
O(L)
class Node:
def __init__(self):
self.children = [None]*26
self.isend = False
class trie:
def __init__(self,):
self.__root = Node()
def __len__(self,):
return len(self.search_byprefix(''))
def __str__(self):
ll = self.search_byprefix('')
string = ''
for i in ll:
string+=i
string+='\n'
return string
def chartoint(self,character):
return ord(character)-ord('a')
def remove(self,string):
ptr = self.__root
length = len(string)
for idx in range(length):
i = self.chartoint(string[idx])
if ptr.children[i] is not None:
ptr = ptr.children[i]
else:
raise ValueError("Keyword doesn't exist in trie")
if ptr.isend is not True:
raise ValueError("Keyword doesn't exist in trie")
ptr.isend = False
return
def insert(self,string):
ptr = self.__root
length = len(string)
for idx in range(length):
i = self.chartoint(string[idx])
if ptr.children[i] is not None:
ptr = ptr.children[i]
else:
ptr.children[i] = Node()
ptr = ptr.children[i]
ptr.isend = True
def search(self,string):
ptr = self.__root
length = len(string)
for idx in range(length):
i = self.chartoint(string[idx])
if ptr.children[i] is not None:
ptr = ptr.children[i]
else:
return False
if ptr.isend is not True:
return False
return True
def __getall(self,ptr,key,key_list):
if ptr is None:
key_list.append(key)
return
if ptr.isend==True:
key_list.append(key)
for i in range(26):
if ptr.children[i] is not None:
self.__getall(ptr.children[i],key+chr(ord('a')+i),key_list)
def search_byprefix(self,key):
ptr = self.__root
key_list = []
length = len(key)
for idx in range(length):
i = self.chartoint(key[idx])
if ptr.children[i] is not None:
ptr = ptr.children[i]
else:
return None
self.__getall(ptr,key,key_list)
return key_list
t = trie()
t.insert("shubham")
t.insert("shubhi")
t.insert("minhaj")
t.insert("parikshit")
t.insert("pari")
t.insert("shubh")
t.insert("minakshi")
真实
假
['minakshi','minhaj']
7
水市
米哈吉尔
帕里
拼凑
ub
舒巴姆
shubhi
答案 9 :(得分:1)
from collections import defaultdict
_trie = lambda: defaultdict(_trie)
trie = _trie()
for s in ["cat", "bat", "rat", "cam"]:
curr = trie
for c in s:
curr = curr[c]
curr.setdefault("_end")
def word_exist(trie, word):
curr = trie
for w in word:
if w not in curr:
return False
curr = curr[w]
return '_end' in curr
print(word_exist(trie, 'cam'))
答案 10 :(得分:0)
class Trie:
head = {}
def printf(self):
print (self.head)
def add(self, word):
cur = self.head
for ch in word:
if ch not in cur:
cur[ch] = {}
cur = cur[ch]
cur['*'] = True
def search(self, word):
cur = self.head
for ch in word:
if ch not in cur:
return False
cur = cur[ch]
if '*' in cur:
return True
else:
return False
dictionary = Trie()
dictionary.add("hi")
dictionary.add("hello")
print(dictionary.search("hi"))
dictionary.printf()
答案 11 :(得分:0)
这很像先前的答案,但更易于阅读:
def make_trie(words):
trie = {}
for word in words:
head = trie
for char in word:
if char not in head:
head[char] = {}
head = head[char]
head["_end_"] = "_end_"
return trie
答案 12 :(得分:0)
使用defaultdict和reduce函数。
创建特里
from functools import reduce
from collections import defaultdict
T = lambda : defaultdict(T)
trie = T()
reduce(dict.__getitem__,'how',trie)['isEnd'] = True
Trie:
defaultdict(<function __main__.<lambda>()>,
{'h': defaultdict(<function __main__.<lambda>()>,
{'o': defaultdict(<function __main__.<lambda>()>,
{'w': defaultdict(<function __main__.<lambda>()>,
{'isEnd': True})})})})
在特里搜索:
curr = trie
for w in 'how':
if w in curr:
curr = curr[w]
else:
print("Not Found")
break
if curr['isEnd']:
print('Found')