-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrie.py
166 lines (135 loc) · 7.03 KB
/
trie.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ALT_library.py
#
# Copyright 2020: Profesores de Algorítmica (UPV, GII/ETSINF)
import collections
import numpy as np
class Trie:
"""
Clase que implementa un Trie para almacenar el vocabulario
"""
def __init__(self, vocabulary):
"""Método constructor de la clase Trie.
Llama al método build_trie para crear el trie a partir de un vocabulario de términos.
Args:
vocabulary (list of str): Vocabulario de términos.
Ejemplo: ["aa","ac","bb","c","cab","cac", "aá", "aà"].
Los términos deben estar ordenados lexicográficamente (sort() o sorted())
Raises:
Exception: si el vocabulario no es una lista de cadenas ordenada lexicográficamente.
"""
if not (isinstance(vocabulary,list) and
all(isinstance(w, str) and len(w) > 0 for w in vocabulary) and
all(w1 < w2 for w1, w2 in zip(vocabulary, vocabulary[1:]))):
raise Exception("vocabulario incorrecto")
self.vocabulary = vocabulary # nos quedamos una referencia, esto podría dar problemas
# si más adelante alguien cambia dicha lista, ¡cuidado!
# una alternativa sería hacer copia local pero ocupa más espacio
# si varios objetos van a compartir el vocabulario sin modificarlo
self.build_trie() # construimos el Trie propiamente dicho
def build_trie(self):
"""Método para construir el trie.
Crea los atributos que representan el trie (trasparencias 34-38 del boletín de prácticas):
label (np.array of unicode symbols): cada posición i del array contiene
el símbolo/letra/carácter/unicode_rune asociado al nodo i del trie.
firstchild (np.array of int): cada posición i del array contiene
el índice del PRIMER hijo del nodo i del trie.
La idea es que puedes recorrer todos los hijos de i haciendo un bucle
en range(self.firstchild[i], self.firstchild[i+1])
Se utiliza para programación dinámica hacia adelante (consultar a dónde vas)
parent (np.array of int): cada posición i del array contiene
el padre del nodo i. El nodo raiz y el centinela comparten otro nodo ficticio
como padre (-1).
Se utiliza para programación dinámica hacia atrás (consultar de dónde vienes)
output es un diccionario que a los nodos finales les asocia la cadena que se emite
la forma de saber si un nodo i es final es comprobar que i es una clave de este
diccionario.
"""
label = [] # lista python, después self.label un array numpy
firstchild = [] # lista python, después self.firstchild un array numpy
parent = [] # lista python, después self.parent array numpy
self.output = {} # diccionario asocia a cada nodo final una palabra del vocabulario
label.append(" ") # asociado al nodo raíz
firstchild.append(1) # asociado al nodo raíz
parent.append(-1) # asociado al nodo raíz
# esto básicamente hace un recorrido por niveles del Trie, de
# ahí que utilice una cola:
Q = collections.deque()
# cada elemento guardado en la cola es una tupla con estos campos:
# first_index, last_index, triedepth, root = Q.popleft()
# donde:
# first_index y last_index: ambos referidos al vector self.vocabulary,
# el 2º apunta a después como range
# ambos delimitan la zona a tratar
# triedepth se refiere a la prof. de root en el trie, donde
# root es el estado raíz del subárbol que se va a generar
Q.append((0, len(self.vocabulary), 1, 0))
while len(Q) > 0:
first_index, last_index, triedepth, root = Q.popleft()
firstchild[root] = len(firstchild) # ver más abajo el (*)
positions = []
lastletter = ""
for i in range(first_index, last_index):
word = self.vocabulary[i]
if len(word) >= triedepth:
letter = word[triedepth - 1]
if letter != lastletter:
lastletter = letter
label.append(letter)
parent.append(root)
firstchild.append(0) # se pone a 0 por poner algo, luego se modifica arriba en (*)
thischildpos = len(firstchild) - 1
if len(word) == triedepth:
self.output[thischildpos] = word
positions.append((i, thischildpos))
positions.append((last_index, 0))
for (frompos, root), (lastpos, dummy) in zip(positions, positions[1:]):
Q.append((frompos, lastpos, triedepth + 1, root))
# un nodo centinela al final para poder usar
# range(self.firstchild[i], self.firstchild[i+1]) en el último nodo
label.append(" ")
firstchild.append(len(firstchild))
parent.append(-1)
# convertimos todo a np.array y lo guardamos como atributos de la clase:
self.label = np.array(label, dtype=np.unicode_)
self.firstchild = np.array(firstchild, dtype=np.int)
self.parent = np.array(parent, dtype=np.int)
def get_root(self):
return 0
def get_num_states(self):
return len(self.firstchild)-1 # sentinel is not included
def get_label(self, node):
return self.label[node]
def get_parent(self, node):
return self.parent[node]
def iter_children(self, node):
return range(self.firstchild[node], self.firstchild[node + 1])
def is_leaf(self, node):
return self.firstchild[node] == self.firstchild[node+1]
def is_final(self, node):
return node in self.output
def get_output(self, node):
return self.output.get(node,"")
def num_children(self, node):
return self.firstchild[node + 1] - self.firstchild[node]
def __str__(self):
lines = ["vocabulary " + repr(self.vocabulary),
"pos label parent firstchild nºchild output"]
num_nodes = len(self.label)-1 # sentinel is not included
for node in range(num_nodes):
etiqueta = '"'+str(self.label[node])+'"'
num_children = self.firstchild[node + 1] - self.firstchild[node]
out = '' if node not in self.output else '"'+self.output[node]+'"'
line = f'{node:3} {etiqueta:4} {self.parent[node]:3} {self.firstchild[node]:3} {num_children:2} {out}'
lines.append(line)
return "\n".join(lines)
if __name__ == "__main__":
vocabulario = ["aa","ac","bb","c","cab","cac"]
trie = Trie(vocabulario)
print(trie)
print()
vocabulario = ['a', 'ata', 'ato', 'cama', 'casa', 'caso', 'casó', 'caña']
trie = Trie(vocabulario)
print(trie)