Estoy intentando implementar una Patricia Trie con los métodos addWord()
, isWord()
y isPrefix()
como un medio para almacenar un gran diccionario de palabras para la recuperación rápida (incluida la búsqueda de prefijos). He leído sobre los conceptos, pero simplemente no están aclarando en una implementación. Quiero saber (en código Java o Python) cómo implementar el Trie, particularmente los nodos (o debo implementarlo recursivamente). Vi a una persona que lo implementó con una matriz de 26 nodos secundarios configurados como nulos/Ninguno. ¿Hay una mejor estrategia (como tratar las letras como bits) y cómo implementarla?Implementando una Patricia Trie para usar como diccionario
Respuesta
Alguien más hizo una pregunta sobre Patricia hace un tiempo y pensé en hacer una implementación de Python entonces, pero esta vez decidí intentarlo (Sí, esto es demasiado por la borda, pero parecía un buen pequeño proyecto). Lo que he hecho quizás no sea una implementación pura de Patricia, pero me gusta mucho mejor. Otros intentos de Patricia (en otros idiomas) usan solo una lista para los niños y verifican que coincida, pero pensé que esto era bastante ineficiente, así que uso diccionarios. Aquí está básicamente cómo lo configuré:
Comenzaré en el nodo raíz. La raíz es solo un diccionario. El diccionario tiene claves que son todos los caracteres individuales (las primeras letras de las palabras) que llevan a las ramas. Los valores correspondientes a cada clave son listas donde el primer elemento es una cadena que proporciona el resto de la cadena que coincide con esta rama del trie, y el segundo elemento es un diccionario que conduce a otras ramas de este nodo. Este diccionario también tiene teclas de un solo carácter que se corresponden con la primera letra del resto de la palabra y el proceso continúa hacia abajo del trie.
Otra cosa que debo mencionar es que si un nodo dado tiene ramas, pero también es una palabra en el trie mismo, eso se denota teniendo una clave ''
en el diccionario que conduce a un nodo con la lista ['',{}]
.
Aquí hay un pequeño ejemplo que muestra cómo se almacenan las palabras (el nodo raíz es la variable _d
):
>>> x = patricia()
>>> x.addWord('abcabc')
>>> x._d
{'a': ['bcabc', {}]}
>>> x.addWord('abcdef')
>>> x._d
{'a': ['bc', {'a': ['bc', {}], 'd': ['ef', {}]}]}
>>> x.addWord('abc')
{'a': ['bc', {'a': ['bc', {}], '': ['', {}], 'd': ['ef', {}]}]}
en cuenta que en el último caso, se añadió una llave '' al diccionario para denotar que 'abc' es una palabra en adición a 'abcdef' y 'abcabc'.
Código Fuente
class patricia():
def __init__(self):
self._data = {}
def addWord(self, word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
if data:
data[word[i:i+1]] = [word[i+1:],{}]
else:
if word[i:i+1] == '':
return
else:
if i != 0:
data[''] = ['',{}]
data[word[i:i+1]] = [word[i+1:],{}]
return
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
except KeyError:
data = node[1]
data[''] = ['',{}]
return
else:
i += len(node[0])
data = node[1]
else:
ii = i
j = 0
while ii != len(word) and j != len(node[0]) and \
word[ii:ii+1] == node[0][j:j+1]:
ii += 1
j += 1
tmpdata = {}
tmpdata[node[0][j:j+1]] = [node[0][j+1:],node[1]]
tmpdata[word[ii:ii+1]] = [word[ii+1:],{}]
data[word[i-1:i]] = [node[0][:j],tmpdata]
return
def isWord(self,word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
return False
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
except KeyError:
return False
return True
else:
i += len(node[0])
data = node[1]
else:
return False
def isPrefix(self,word):
data = self._data
i = 0
wordlen = len(word)
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
return False
i += 1
if word.startswith(node[0][:wordlen-i],i):
if wordlen - i > len(node[0]):
i += len(node[0])
data = node[1]
else:
return True
else:
return False
def removeWord(self,word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
print "Word is not in trie."
return
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
node[1].pop('')
except KeyError:
print "Word is not in trie."
return
data.pop(word[i-1:i])
return
else:
i += len(node[0])
data = node[1]
else:
print "Word is not in trie."
return
__getitem__ = isWord
Usted puede haber notado que al final me puse __getitem__
al método isWord. Esto significa que
x['abc']
devolverá si 'abc' en el trie o no.
Creo que tal vez debería hacer un módulo de esto y enviarlo a PyPI, pero necesita más pruebas y al menos un método removeWord. Si encuentras algún error, avísame, pero parece funcionar bastante bien. Además, si observa grandes mejoras en la eficiencia, también me gustaría saber sobre ellos. He considerado hacer algo para tener diccionarios vacíos en la parte inferior de cada rama, pero lo dejo por ahora. Estos diccionarios vacíos pueden reemplazarse con datos vinculados a la palabra para expandir los usos de la implementación, por ejemplo.
De todos modos, si no le gusta la forma en que lo implementé, al menos tal vez le dará algunas ideas sobre cómo le gustaría implementar su propia versión.
@John Sí, pero los etiqueté de esa manera para que el código sea más fácil de descifrar. Podría hacer ese tipo de cambios para la versión final. Gracias por el aporte. –
@Justin: Perdón, en mi prisa dejé una: wlen -> l –
@John Sí, todas son buenas para hacer que el tamaño del archivo .py sea más pequeño (lo que en realidad no era mi objetivo en este momento), pero, ¿cómo realmente ayuda a la eficiencia? –
He aquí una implementación recursiva utilizando métodos más Pythonic:
def matching_prefix_index(word1, word2):
max_len = min(len(word1),len(word2))
for i in range(max_len):
if word2[i] != word1[i]:
return i
return max_len
class PatriciaTrie(object):
def __init__(self):
self._storage = {}
self._complete_prefix_flag = False
def _find_storage_key(self, word):
for key in self._storage:
prefix_index = matching_prefix_index(key, word)
if prefix_index > 0:
return (key, prefix_index)
return (None, None)
def add(self, word):
if word == '':
self._complete_prefix_flag = True
return True
key, prefix_index = self._find_storage_key(word)
if key is not None:
if prefix_index == len(key):
return self._storage[key].add(word[len(key):])
else:
new_tree = PatriciaTrie()
new_tree._storage[key[prefix_index:]] = self._storage.pop(key)
self._storage[key[0:prefix_index]] = new_tree
return new_tree.add(word[prefix_index:])
else:
self._storage[word] = PatriciaTrie()
self._storage[word].add('')
return True
def remove(self, word):
if word == '':
self._complete_prefix_flag = False
return True
key, prefix_index = self._find_storage_key(word)
if key is None or prefix_index != len(key):
return False
subword = word[prefix_index:]
subtrie = self._storage[key]
if subtrie.remove(subword):
if (not subtrie._complete_prefix_flag) and len(subtrie._storage) == 0:
self._storage.pop(key)
return True
else:
return False
def __contains__(self, word):
if word == '':
return self._complete_prefix_flag
key, prefix_index = self._find_storage_key(word)
if key is None or prefix_index != len(key):
return False
else:
return (word[prefix_index:] in self._storage[key])
def has_prefix(self, word):
if word == '':
return True
key, prefix_index = self._find_storage_key(word)
if key is None:
return False
elif len(key) > len(word):
return (prefix_index == len(word))
elif len(key) != prefix_index:
return False
else:
return self._storage[key].has_prefix(word[prefix_index:])
- 1. STLish función low_bound para Radix/Patricia Trie
- 2. Índice de Tela (capas trie Patricia)
- 3. algoritmo/pasos para encontrar la búsqueda más larga Prefijo de Patricia Trie
- 4. Usar un objeto como una clave de diccionario genérica
- 5. Qué estructura de datos de nodo usar para un trie
- 6. Diccionario .NET como una propiedad
- 7. trie o árbol de búsqueda binaria equilibrada para almacenar el diccionario?
- 8. Trie & subsequences
- 9. ¿Hay algún árbol de raíz/patricia/critbit para Python?
- 10. Trie ahorra espacio, pero ¿cómo?
- 11. Usar un diccionario en una propiedad cuadrícula
- 12. ¿Puedo usar la Lista de objetos como claves del diccionario?
- 13. ¿Trie basado en disco?
- 14. Trie vs B + tree
- 15. Usar un diccionario de recursos como tema en Silverlight
- 16. Python: Usar un diccionario como interruptor no funciona
- 17. ¿es posible usar un lambda como valor predeterminado del diccionario?
- 18. Trie vs. suffix tree vs. sufijo array
- 19. Buscando una buena introducción en trie
- 20. Implementación de Trie
- 21. ¿Hay un Trie en Java?
- 22. Python y diccionario como objeto
- 23. Implementando MVVM en WPF sin usar System.Windows.Input.ICommand
- 24. ¿Está bien utilizar una NSLocalizedString como clave en un diccionario?
- 25. Argumentos de Python como diccionario
- 26. Clase genérica estática como diccionario
- 27. Usar para variable de bucle como clave del diccionario en la plantilla de Django
- 28. Implementando una Play! Aplicación Framework
- 29. Usar cPickle para serializar un diccionario grande causa MemoryError
- 30. Implementando IEnumerable con una matriz
¿Es esta una tarea? –
Se basa en una tarea asignada, pero el alcance de esta pregunta es solo una fracción de esa tarea y es (particularmente ahora) más relevante para mi comprensión personal de cómo funciona el trabajo que obtener una calificación. –
Solo quiero decir que quería explorar a Patricia una vez, también, y terminé rindiéndome. Encontré la exposición de Knuth demasiado escueta, y el documento original no tenía ningún sentido para mí. –