View difference between Paste ID: 8kFNaLvz and UrAUXniH
SHOW: | | - or go back to the newest paste.
1
import random
2
import heapq
3
import math
4
from time import time
5
from collections import deque
6
7
visitar_nulo = lambda a,b,c,d: True
8
heuristica_nula = lambda actual,destino: 0
9
10
class Grafo(object):
11
    '''Clase que representa un grafo. El grafo puede ser dirigido, o no, y puede
12
       no indicarsele peso a las aristas (se comportara como peso = 1). 
13
      Implementado como "diccionario de diccionarios"
14
    '''
15
    
16
    def __init__(self, es_dirigido = False):
17
        '''Crea el grafo. El parametro 'es_dirigido' indica si sera dirigido, o no.'''
18
        self.es_dirigido = es_dirigido
19
        self.cant_vertices = 0
20
        self.vertices = {}
21
        self.personajes = {}
22
        self.nodos = {}
23
        self.cant_aristas = 0
24
    
25
    def __iter__(self):
26
        '''Devuelve un iterador para el grafo''' 
27
        return iter(self.vertices)
28
    
29
    def __len__(self):
30
        '''Devuelve la cantidad de vertices del grafo'''
31
        return self.cant_vertices
32
33-
    def keys(self):
33+
34-
        '''Devuelve una lista de identificadores de vertices. Iterar sobre
34+
35-
           ellos es equivalente a iterar sobre el grafo.
35+
36
           ya se encuentre asociado a un vertice, se actualizara el valor.
37-
        return self.vertices.keys()
37+
38
        
39
        if not id in self.nodos:
40
            self.vertices[id] = {}
41
            self.personajes[valor] = id
42
            self.cant_vertices += 1
43
            self.nodos[id] = Nodo_Grafo(id, valor)
44
        self.nodos[id].nombre = valor
45
46
    def __contains__(self, id):
47
        ''' Determina si el grafo contiene un vertice con el identificador indicado.'''
48
        return id in self.vertices.keys()
49
    
50
    def cantidad_aristas(self):
51
        return self.cant_aristas
52
        
53-
    def __delitem__(self, id):
53+
54-
        '''Elimina el vertice del grafo. Si no existe el identificador en el grafo, 
54+
55-
           lanzara KeyError.
55+
56-
           Borra tambien todas las aristas que salian y entraban al vertice en cuestion.
56+
57
            * desde y hasta: identificadores de vertices dentro del grafo. Si alguno 
58
              de estos no existe dentro del grafo, lanzara KeyError.
59
            * Peso: valor de peso que toma la conexion. Si no se indica, valdra 1.
60-
            raise KeyError("No se encontro la clave")
60+
61-
        for i in self.vertices:
61+
62-
            if id in self.vertices[i]:
62+
63-
                self.vertices[i].pop(id)
63+
64-
        self.vertices.pop(id)
64+
65-
        self.nodos.pop(id)
65+
66
        if self.es_dirigido:
67
            self.vertices[hasta][desde] = peso
68
    
69
    def adyacentes(self, id):
70
        '''Devuelve una lista con los vertices (identificadores) adyacentes
71
           al indicado. Si no existe el vertice, se lanzara KeyError
72
        '''
73
        if not id in self.vertices:
74
            raise KeyError("No se encontro el vertice")
75
        return list(self.vertices[id].keys())
76
        
77
    def obtener_personaje(self, id):
78
        '''Devuelve el valor de la id'''
79
        if not id in self.nodos:
80
            raise KeyError("No se encontro la id")
81
        return self.nodos[id].nombre
82
        
83
    def grado(self):
84
        '''Devuelve el grado total del grafo'''
85
        grado_total = 0
86
        for vertice in self.vertices:
87
            for adyacente in Grafo.adyacentes(self, vertice):
88
                grado_total += 1
89
        return grado_total
90
        
91-
    def borrar_arista(self, desde, hasta):
91+
92-
        '''Borra una arista que conecta los vertices indicados. 
92+
93
        if not valor in self.personajes:
94-
            * desde y hasta: identificadores de vertices dentro del grafo. 
94+
95-
              Si alguno de estos no existe dentro del grafo, lanzara KeyError.
95+
96-
           En caso de no existir la arista, se lanzara ValueError.
96+
97
                    
98
    def random_walk(self, largo, origen = None, pesado = False): 
99
        ''' Devuelve una lista con un recorrido aleatorio de grafo.
100-
        if not hasta in self.vertices[desde][1]:
100+
101-
            raise ValueError("No existe la arista")
101+
102-
        if not self.es_dirigido:
102+
103-
            self.vertices[hasta].pop(desde)
103+
104-
        self.vertices[desde].pop(hasta)
104+
105
                  las probabilidades de movernos de un vertice a uno de sus vecinos (False = todo equiprobable). 
106-
    def obtener_peso_arista(self, desde, hasta):
106+
107-
        '''Obtiene el peso de la arista que va desde el vertice 'desde', hasta el vertice 'hasta'.
107+
108
        '''
109-
            * desde y hasta: identificadores de vertices dentro del grafo.
109+
110-
              Si alguno de estos no existe dentro del grafo, lanzara KeyError.
110+
111-
           En caso de no existir la union consultada, se devuelve None.
111+
112
        cont_recorrido = 0
113
        if origen:
114
            if not origen in self.vertices:
115-
        if not hasta in self.vertices[desde]: return None
115+
116-
        return self.vertices[desde][hasta]
116+
117
        else: 
118
            lista_claves = list(self.vertices.keys())
119
            vertice_random = random.choice(lista_claves)
120
            while not vertice_random:
121
                vertice_random = random.choice(lista_claves)
122
            recorrido.append(vertice_random)
123
        
124
        vertice_partida = recorrido[cont_recorrido]
125
        
126
        while cont_recorrido < largo:
127
            if not vertice_partida: break
128
            diccionario_adyacentes = self.vertices[vertice_partida]
129
            if pesado:
130
                vertice_random = vertice_random_por_peso(diccionario_adyacentes)
131
            else:
132
                lista_claves = list(self.vertices[vertice_partida].keys())
133
                vertice_random = random.choice(lista_claves)
134
                
135
            recorrido.append(vertice_random)
136
            cont_recorrido+= 1
137
            vertice_partida = recorrido[cont_recorrido]
138
            
139
            
140
        return recorrido
141
           
142
    def dijkstra(self, origen, destino, minimo = True): 
143
    
144
        '''Devuelve el recorrido dependiendo si es maximo o minimo desde el 
145
            origen hasta el destino, aplicando el algoritmo de Dijkstra.
146
           Parametros:
147
            * minimo: indica si el camino devuelto sera el minimo si minimo es True
148
              devolvera el maximo si es False
149
            * origen y destino: identificadores de vertices dentro del grafo. 
150
              Si alguno de estos no existe dentro del grafo, lanzara KeyError. 
151
           Devuelve:
152
            * Listado de vertices (identificadores) ordenado con el recorrido, incluyendo 
153
              a los vertices de origen y destino. En caso que no exista camino entre 
154
              el origen y el destino, se devuelve None. 
155
        '''
156
        heap = []
157
        camino = []
158
        for vertice in self.vertices:
159
            nodo = self.nodos[vertice]
160
            nodo.visitado = False
161
            nodo.distancia = math.inf
162
            if vertice == origen:
163
                heapq.heappush(heap,nodo)
164
                nodo.distancia = 0
165
        while heap:
166
            nodo = heapq.heappop(heap)
167
            nodo.visitado = True
168
            vertice = nodo.vertice
169
            dist = nodo.distancia
170
            for adyacente in self.vertices[vertice]:
171
                if not adyacente: continue
172
                peso = self.vertices[vertice][adyacente]
173
                nodo_ad = self.nodos[adyacente]
174
                if not nodo_ad.visitado and nodo_ad.distancia > (dist + peso):
175
                    
176
                    if minimo:
177
                        nodo_ad.cambiar_dist(dist + peso)
178
                    else:
179
                        nodo_ad.cambiar_dist(dist + 1/peso)
180
                    nodo_ad.cambiar_padre(vertice)
181
                    heapq.heappush(heap,nodo_ad)
182
        
183
        vertice = destino
184
        camino.append(vertice)
185
        while vertice != origen:
186
            padre = self.nodos[vertice].padre
187
            camino.append(padre)
188
            vertice = padre
189
        return camino 
190
        
191
        
192
    
193
    def bfs(self, inicio = None, visitar = None, extra = None):
194
195
        '''Realiza un recorrido BFS dentro del grafo
196
        Parametros:
197
            - inicio: identificador del vertice que se usa como inicio. Si se indica un vertice, el recorrido se comenzara en dicho vertice, 
198
            y solo se seguira hasta donde se pueda (no se seguira con los vertices que falten visitar)
199
        Salida:
200
            Tupla (padres, orden), donde :
201
                - 'padres' es un diccionario que indica para un identificador, cual es el identificador del vertice padre en el recorrido BFS (None si es el inicio)
202
                - 'orden' es un diccionario que indica para un identificador, cual es su orden en el recorrido BFS
203
        '''
204
        padres = {}
205
        orden = {}
206
        
207
        for nodo in self.nodos:
208
            nodo = self.nodos[nodo]
209
            nodo.visitado = False
210
            padres[nodo.vertice] = None
211
            orden[nodo.vertice] = math.inf
212
        
213
        if inicio:
214
            nodo = self.nodos[inicio]
215
            vertice = inicio
216
        else:
217
            nodos = list(self.nodos.keys())
218
            vertice = random.choice(nodos)
219
            nodo = self.nodos[vertice]
220
        vertice_origen = vertice
221
        padres[vertice] = None
222
        nodo.visitado = True
223
        orden[vertice] = 0
224
        cola = deque([])
225
        cola.append(nodo)
226
        while cola:
227
            nodo = cola.popleft()
228
            vertice = nodo.vertice
229
            if visitar and not visitar(nodo,extra): break
230
            for adyacente in self.vertices[vertice]:
231
                nodo_ad = self.nodos[adyacente]
232
                if nodo_ad.visitado == False:
233
                    nodo_ad.visitado = True
234
                    cola.append(nodo_ad)
235
                    orden[adyacente] = orden[vertice] + 1
236
                    padres[adyacente] = vertice
237
    
238
        return (orden, padres, vertice_origen)  
239
    
240
            
241
        
242
class Nodo_Grafo():
243
    
244
    def __init__(self, vertice, nombre, distancia = 0, visitado = False, padre = None):
245
        self.vertice = vertice
246
        self.distancia = distancia
247
        self.visitado = visitado
248
        self.padre = padre
249
        self.nombre = nombre
250
        self.label_act = int(vertice)
251
        
252
        
253
    def visitado(self):
254
        return self.visitado
255
        
256
    def cambiar_dist(self, distancia_nueva):
257
        self.distancia = distancia_nueva
258
259
    def cambiar_padre(self, padre_nuevo):
260
        self.padre = padre_nuevo
261
        
262
    def __lt__(self,otro):
263
        return self.distancia < otro.distancia
264
    
265
    def __gt__(self,otro):
266
        return self.distancia > otro.distancia
267
        
268
    def __eq__(self,otro):
269
        return self.distancia == otro.distancia
270
        
271
    def __str__(self):
272
        return str(self.vertice)+"; "+str(self.distancia)
273
        
274
        
275
        
276
def vertice_random_por_peso(diccionario_adyacentes):
277
    '''Devuelve un vertice adyacente random, con altas prioridades de que su peso sea mayor al de los demas'''
278
    
279
    total = sum(diccionario_adyacentes.values())
280
    rand = random.uniform(0, total)
281
    acum = 0
282
    for vertice, peso_arista in diccionario_adyacentes.items():
283
        if acum + peso_arista >= rand:
284
            return vertice
285
        acum += peso_arista