View difference between Paste ID: weDyFe7Y and S5DUw5gW
SHOW: | | - or go back to the newest paste.
1-
# 99373 ALVAREZ - 99192 OTERO
1+
import sys
2-
import random
2+
from grafo import Grafo
3
import heapq
4-
import math
4+
import collections
5-
from time import time
5+
import re
6-
from collections import deque
6+
RANGO_WALKS = 100
7
CANTIDAD_WALKS = 50
8-
visitar_nulo = lambda a,b,c,d: True
8+
RANGO_CENTRALIDAD = 700
9-
heuristica_nula = lambda actual,destino: 0
9+
OPCIONES = ['1','2','3','4','5','6','7','8']
10
11-
class Grafo(object):
11+
def crear_grafo(ruta):
12-
    '''Clase que representa un grafo. El grafo puede ser dirigido, o no, y puede
12+
    ''' Recibe una ruta de un archivo y devuelve un grafo a partir de sus datos
13-
       no indicarsele peso a las aristas (se comportara como peso = 1). 
13+
        PRE: la ruta debe existir, el archivo debe ser de la siguiente manera:
14-
      Implementado como "diccionario de diccionarios"
14+
            Vertices cant
15
            .
16
            .
17-
    def __init__(self, es_dirigido = False):
17+
            Arcs*
18-
        '''Crea el grafo. El parametro 'es_dirigido' indica si sera dirigido, o no.'''
18+
            .
19-
        self.es_dirigido = es_dirigido
19+
20-
        self.cant_vertices = 0
20+
    with open(ruta) as archivo:
21-
        self.vertices = {}
21+
        grafo = Grafo(True)
22-
        self.personajes = {}
22+
        for i,linea in enumerate(archivo):
23-
        self.nodos = {}
23+
            if i == 0: continue
24-
        self.cant_aristas = 0
24+
            linea = linea.rstrip("\n")
25
            if linea == "*Arcs": break
26-
    def __iter__(self):
26+
            vertice = linea.split(" ", 1)
27-
        '''Devuelve un iterador para el grafo''' 
27+
            vertice[1] = vertice[1].rstrip('"')
28-
        return iter(self.vertices)
28+
            vertice[1] = vertice[1].lstrip('"')
29
            grafo.__setitem__(vertice[0], vertice[1])
30-
    def __len__(self):
30+
        for linea in archivo:
31-
        '''Devuelve la cantidad de vertices del grafo'''
31+
            linea = linea.rstrip("\n")
32-
        return self.cant_vertices
32+
            arista = linea.split(" ")
33
            grafo.agregar_arista(arista[0],arista[1],int(arista[2]))
34-
    def __setitem__(self, id, valor):
34+
    return grafo
35-
        '''Agrega un nuevo vertice con el par <id, valor> indicado. ID debe 
35+
36-
           ser de identificador unico del vertice. En caso que el identificador 
36+
37-
           ya se encuentre asociado a un vertice, se actualizara el valor.
37+
def frecuencia_random_walks(grafo, cantidad , id = None, recomendados = False,  rang_walk = RANGO_WALKS):
38-
        '''
38+
    ''' Ejecuta varias veces la funcion random_walks.
39
      Parametros:
40-
        if not id in self.nodos:
40+
       *grafo: el grafo sobre el cual se aplicara la funcion
41-
            self.vertices[id] = {}
41+
       *cantidad: la cantidad de veces que se hara random_walks
42-
            self.personajes[valor] = id
42+
       *id: es la id origen, por defecto es None
43-
            self.cant_vertices += 1
43+
       *rang_walk: es el rango del random_walks
44-
            self.nodos[id] = Nodo_Grafo(id, valor)
44+
       *recomendados: es un booleano que indica si se esta utilizando para la funcion recomendados
45-
        self.nodos[id].nombre = valor
45+
      Salida:
46
        Devuelve una lista con los id's que mas aparecen en los recorridos randoms, 
47-
    def __contains__(self, id):
47+
        en caso de que recomendados = True devolvera una lista con los que aparecen
48-
        ''' Determina si el grafo contiene un vertice con el identificador indicado.'''
48+
        mas veces en los recorridos a partir del origen, pero no estaran sus                    
49-
        return id in self.vertices.keys()
49+
        adyacentes
50
    '''
51-
    def cantidad_aristas(self):
51+
    cantidad += 1 #Le sumo 1 por el tema de que most_common va desde 0 - cant-1
52-
        return self.cant_aristas
52+
    frecuencia = []
53
    frecuentes = []
54
    
55-
    def agregar_arista(self, desde, hasta, peso = 1):
55+
    if id:
56-
        '''Agrega una arista que conecta los vertices indicados.
56+
        if not id in grafo:
57-
           Parametros:
57+
            raise KeyError("No se encuentra el personaje") 
58-
            * desde y hasta: identificadores de vertices dentro del grafo. Si alguno 
58+
    for i in range(rang_walk):    
59-
              de estos no existe dentro del grafo, lanzara KeyError.
59+
        id_origen = id
60-
            * Peso: valor de peso que toma la conexion. Si no se indica, valdra 1.
60+
        lista_aux = grafo.random_walk(CANTIDAD_WALKS, id_origen, True)
61-
            Si el grafo es no-dirigido, tambien agregara la arista reciproca.
61+
        frecuencia += lista_aux
62-
        '''
62+
63-
        if not desde or not hasta in self.vertices:
63+
    if recomendados:
64-
            raise KeyError("No se encontro uno de los vertices")
64+
        frecuencia = [v for v in frecuencia if v not in grafo.adyacentes(id_origen)]
65-
        self.cant_aristas += 1
65+
    apariciones = collections.Counter(frecuencia).most_common(cantidad)
66-
        self.vertices[desde][hasta] = peso
66+
67-
        if self.es_dirigido:
67+
    for id,aparicion in apariciones:
68-
            self.vertices[hasta][desde] = peso
68+
        if not id or id == id_origen: continue
69
        frecuentes.append(id)
70-
    def adyacentes(self, id):
70+
    return frecuentes
71-
        '''Devuelve una lista con los vertices (identificadores) adyacentes
71+
72-
           al indicado. Si no existe el vertice, se lanzara KeyError
72+
def imprimir_personajes(grafo, ids):
73-
        '''
73+
    '''A partir de una lista de ids imprime en pantalla los nombres
74-
        if not id in self.vertices:
74+
       de cada id respectivamente'''
75-
            raise KeyError("No se encontro el vertice")
75+
    for i, id in enumerate(ids):
76-
        return list(self.vertices[id].keys())
76+
        i += 1
77
        nombre = grafo.obtener_personaje(id)
78-
    def obtener_personaje(self, id):
78+
        print(str(i)+") "+ nombre)
79-
        '''Devuelve el valor de la id'''
79+
80-
        if not id in self.nodos:
80+
def op_recomendados(grafo, personaje, cantidad):
81-
            raise KeyError("No se encontro la id")
81+
82-
        return self.nodos[id].nombre
82+
    id = grafo.obtener_id(personaje)
83
    recomendados = frecuencia_random_walks(grafo, int(cantidad), id, True)
84-
    def grado(self):
84+
    print("Las recomendaciones respecto de", personaje,"son:")
85-
        '''Devuelve el grado total del grafo'''
85+
    imprimir_personajes(grafo, recomendados)
86-
        grado_total = 0
86+
87-
        for vertice in self.vertices:
87+
def op_similares(grafo, personaje, cantidad):
88-
            for adyacente in Grafo.adyacentes(self, vertice):
88+
    id = grafo.obtener_id(personaje)
89-
                grado_total += 1
89+
    similares = frecuencia_random_walks(grafo, int(cantidad), id)
90-
        return grado_total
90+
    personaje = grafo.obtener_personaje(id)
91
    print("Los similares respecto de", personaje,"son:")
92-
    def obtener_id(self, valor):
92+
    imprimir_personajes(grafo, similares)
93-
        '''Devuelve la ide del valor'''
93+
94-
        if not valor in self.personajes:
94+
def op_centralidad(grafo, cantidad):
95-
            raise KeyError("La No se encontro el nombre")
95+
    centrales = frecuencia_random_walks(grafo,int(cantidad), None, False, RANGO_CENTRALIDAD)
96-
        return self.personajes[valor]
96+
    imprimir_personajes(grafo, centrales)
97
        
98-
                    
98+
def op_camino(grafo, personaje1, personaje2):
99-
    def random_walk(self, largo, origen = None, pesado = False): 
99+
    id1 = grafo.obtener_id(personaje1)
100-
        ''' Devuelve una lista con un recorrido aleatorio de grafo.
100+
    id2 = grafo.obtener_id(personaje2)
101-
            Parametros:
101+
    camino = grafo.dijkstra(id1, id2, False)
102-
                * largo: El largo del recorrido a realizar
102+
    print("El camino es:")
103-
                * origen: Vertice (id) por el que se debe comenzar el recorrido.
103+
    imprimir_personajes(grafo, camino)
104-
                  Si origen = None, se comenzara por un vertice al azar.
104+
105-
                * pesado: indica si se tienen en cuenta los pesos de las aristas para determinar
105+
def op_distancia(grafo, personaje):
106-
                  las probabilidades de movernos de un vertice a uno de sus vecinos (False = todo equiprobable). 
106+
    id = grafo.obtener_id(personaje)
107-
            Devuelve:
107+
    orden, padres, vertice = grafo.bfs(id)
108-
                Una lista con los vertices (ids) recorridos, en el orden del recorrido. 
108+
    distancias = {}
109-
        '''
109+
    for id, distancia in orden.items():
110-
        if not self.vertices:
110+
        distancias.setdefault(distancia, []).append(id)
111-
            raise ValueError("No hay vertices en el grafo")
111+
    list_distancias = list(distancias.keys())
112-
        recorrido = []
112+
    list_distancias.sort()
113-
        cont_recorrido = 0
113+
    for distancia in list_distancias:
114-
        if origen:
114+
        if distancia == 0: continue
115-
            if not origen in self.vertices:
115+
        cantidad = len(distancias[distancia])
116-
                raise KeyError("No se encontro el vertice origen")
116+
        print("La cantidad de personajes a distancia "+str(distancia)+" es: "+ str(cantidad))
117-
            recorrido.append(origen)
117+
118-
        else: 
118+
def desviacion_estandar(grafo, grado_grafo, cantidad_vertices):   
119-
            lista_claves = list(self.vertices.keys())
119+
120-
            vertice_random = random.choice(lista_claves)
120+
    '''Devuelve la desviacion estandar'''
121-
            while not vertice_random:
121+
122-
                vertice_random = random.choice(lista_claves)
122+
    sumatoria = 0
123-
            recorrido.append(vertice_random)
123+
    for vertice in grafo.vertices:
124
        grado_vertice = 0
125-
        vertice_partida = recorrido[cont_recorrido]
125+
        for adyacente in grafo.adyacentes(vertice):
126
            grado_vertice += 1
127-
        while cont_recorrido < largo:
127+
        sumatoria += (grado_vertice - grado_grafo /cantidad_vertices)**2
128-
            if not vertice_partida: break
128+
    desvio_estandar = ((1/(cantidad_vertices - 1))* sumatoria)**(0.5)
129-
            diccionario_adyacentes = self.vertices[vertice_partida]
129+
    return desvio_estandar
130-
            if pesado:
130+
131-
                vertice_random = vertice_random_por_peso(diccionario_adyacentes)
131+
132-
            else:
132+
def op_estadisticas(grafo):
133-
                lista_claves = list(self.vertices[vertice_partida].keys())
133+
134-
                vertice_random = random.choice(lista_claves)
134+
    cantidad_vertices = grafo.cant_vertices
135-
                
135+
    cantidad_aristas = grafo.cant_aristas
136-
            recorrido.append(vertice_random)
136+
    grado_total = grafo.grado()
137-
            cont_recorrido+= 1
137+
    desv_std = desviacion_estandar(grafo, grado_total, cantidad_vertices)
138-
            vertice_partida = recorrido[cont_recorrido]
138+
    numero_max_aristas = cantidad_vertices * (cantidad_vertices - 1)
139
    
140
    print("Cantidad de vertices: {}".format(cantidad_vertices))
141-
        return recorrido
141+
    print("Cantidad de aristas: {}".format(cantidad_aristas))
142-
           
142+
    print("Promedio del grado de cada vértice: %.2f" % (grado_total/cantidad_vertices))
143-
    def dijkstra(self, origen, destino, minimo = True): 
143+
    print("Desvio estándar del grado de cada vértice: %.2f" % (desv_std))
144
    print("Densidad del grafo: %.6f" % (cantidad_aristas/numero_max_aristas))
145-
        '''Devuelve el recorrido dependiendo si es maximo o minimo desde el 
145+
146-
            origen hasta el destino, aplicando el algoritmo de Dijkstra.
146+
147-
           Parametros:
147+
def max_freq(list_adyacentes, nodos):
148-
            * minimo: indica si el camino devuelto sera el minimo si minimo es True
148+
149-
              devolvera el maximo si es False
149+
    mayor_freq = {} 
150-
            * origen y destino: identificadores de vertices dentro del grafo. 
150+
    for adyacente in list_adyacentes: #Contador de frecuencia de cada label
151-
              Si alguno de estos no existe dentro del grafo, lanzara KeyError. 
151+
        label = nodos[adyacente].label_act
152-
           Devuelve:
152+
        if not label in mayor_freq:
153-
            * Listado de vertices (identificadores) ordenado con el recorrido, incluyendo 
153+
            mayor_freq[label] = 0
154-
              a los vertices de origen y destino. En caso que no exista camino entre 
154+
        mayor_freq[label] += 1
155-
              el origen y el destino, se devuelve None. 
155+
156-
        '''
156+
    label_mayor_freq = 0
157-
        heap = []
157+
    frecuencia = 0
158-
        camino = []
158+
159-
        for vertice in self.vertices:
159+
    for label in mayor_freq: # Detector del que tiene mayor frecuencia
160-
            nodo = self.nodos[vertice]
160+
        if mayor_freq[label] > frecuencia:
161-
            nodo.visitado = False
161+
            label_mayor_freq = label
162-
            nodo.distancia = math.inf
162+
            frecuencia = mayor_freq[label]
163-
            if vertice == origen:
163+
164-
                heapq.heappush(heap,nodo)
164+
    return label_mayor_freq
165-
                nodo.distancia = 0
165+
166-
        while heap:
166+
def _op_comunidades(grafo, nodos):
167-
            nodo = heapq.heappop(heap)
167+
168-
            nodo.visitado = True
168+
    comunidades = {} #label : [vertices]
169-
            vertice = nodo.vertice
169+
    for vertice in nodos:
170-
            dist = nodo.distancia
170+
        nodos[vertice].label = max_freq(grafo.adyacentes(vertice), nodos)
171-
            for adyacente in self.vertices[vertice]:
171+
        label = nodos[vertice].label
172-
                if not adyacente: continue
172+
        if not label in comunidades:
173-
                peso = self.vertices[vertice][adyacente]
173+
            comunidades[label] = []
174-
                nodo_ad = self.nodos[adyacente]
174+
        comunidades[label].append(vertice)
175-
                if not nodo_ad.visitado and nodo_ad.distancia > (dist + peso):
175+
176-
                    
176+
    eliminar = []
177-
                    if minimo:
177+
    for comunidad in comunidades:
178-
                        nodo_ad.cambiar_dist(dist + peso)
178+
        if len(comunidades[comunidad]) <= 4 or len(comunidades[comunidad]) >= 1000:
179-
                    else:
179+
            eliminar.append(comunidad)
180-
                        nodo_ad.cambiar_dist(dist + 1/peso)
180+
181-
                    nodo_ad.cambiar_padre(vertice)
181+
    for comunidad in eliminar:
182-
                    heapq.heappush(heap,nodo_ad)
182+
        comunidades.pop(comunidad)
183
        
184-
        vertice = destino
184+
    return comunidades
185-
        camino.append(vertice)
185+
186-
        while vertice != origen:
186+
187-
            padre = self.nodos[vertice].padre
187+
def op_comunidades(grafo):
188-
            camino.append(padre)
188+
    nodos = grafo.nodos
189-
            vertice = padre
189+
    comunidades = _op_comunidades(grafo, nodos)
190-
        return camino 
190+
    desigualdad = False
191
    while True:
192
        test_comunidad = _op_comunidades(grafo,nodos)
193
        if comunidades == test_comunidad: break
194-
    def bfs(self, inicio = None, visitar = None, extra = None):
194+
        comunidades = test_comunidad
195
    for i, comunidad in enumerate(comunidades):
196-
        '''Realiza un recorrido BFS dentro del grafo
196+
        print("\n\nCantidad de Integrantes: {}".format(len(comunidades[comunidad])))
197-
        Parametros:
197+
        imprimir_personajes(grafo, comunidades[comunidad])
198-
            - inicio: identificador del vertice que se usa como inicio. Si se indica un vertice, el recorrido se comenzara en dicho vertice, 
198+
199-
            y solo se seguira hasta donde se pueda (no se seguira con los vertices que falten visitar)
199+
200-
        Salida:
200+
def verificar_personaje(grafo, personaje):
201-
            Tupla (padres, orden), donde :
201+
    if not personaje in grafo.personajes:
202-
                - '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+
        intentos = 0
203-
                - 'orden' es un diccionario que indica para un identificador, cual es su orden en el recorrido BFS
203+
        while intentos < 5 and not personaje in grafo.personajes:
204-
        '''
204+
            personaje = input("El personaje no se encuentra en la base de datos, vuelva a ingresar: ")
205-
        padres = {}
205+
            personaje = personaje.upper()
206-
        orden = {}
206+
            intentos += 1
207
        if intentos == 5 and not personaje in grafo.personajes:
208-
        for nodo in self.nodos:
208+
            return False
209-
            nodo = self.nodos[nodo]
209+
    return personaje
210-
            nodo.visitado = False
210+
211-
            padres[nodo.vertice] = None
211+
def verificar_numero(opcion):
212-
            orden[nodo.vertice] = math.inf
212+
    if not opcion.isdigit():
213
        intentos = 0
214-
        if inicio:
214+
        while intentos < 5 and not opcion.isdigit():
215-
            nodo = self.nodos[inicio]
215+
            opcion = input("Vuelva a seleccionar la cantidad: ")
216-
            vertice = inicio
216+
            intentos += 1
217-
        else:
217+
        if intentos == 5 or not opcion.isdigit(): return False
218-
            nodos = list(self.nodos.keys())
218+
    return opcion
219-
            vertice = random.choice(nodos)
219+
220-
            nodo = self.nodos[vertice]
220+
221-
        vertice_origen = vertice
221+
222-
        padres[vertice] = None
222+
def menu(grafo):
223-
        nodo.visitado = True
223+
    print("************** MENU - MARVEL ***************")
224-
        orden[vertice] = 0
224+
    print("1- Recomendados")
225-
        cola = deque([])
225+
    print("2- Similares")
226-
        cola.append(nodo)
226+
    print("3- Centralidad")
227-
        while cola:
227+
    print("4- Camino")
228-
            nodo = cola.popleft()
228+
    print("5- Distancias")
229-
            vertice = nodo.vertice
229+
    print("6- Estadisticas")
230-
            if visitar and not visitar(nodo,extra): break
230+
    print("7- Comunidades")
231-
            for adyacente in self.vertices[vertice]:
231+
    print("8- Salir")
232-
                nodo_ad = self.nodos[adyacente]
232+
    opcion = input("Ingrese una opcion: ")
233-
                if nodo_ad.visitado == False:
233+
    print("")
234-
                    nodo_ad.visitado = True
234+
    posibilidades = 0
235-
                    cola.append(nodo_ad)
235+
    while not opcion in OPCIONES and posibilidades != 5 :
236-
                    orden[adyacente] = orden[vertice] + 1
236+
        print("Error al ingresar la opcion")
237-
                    padres[adyacente] = vertice
237+
        opcion = input("Ingrese una opcion nuevamente: ")
238
        posibilidades += 1
239-
        return (orden, padres, vertice_origen)  
239+
240
    if posibilidades == 5: return
241
    
242-
class Nodo_Grafo():
242+
    opcion = int(opcion)
243
    
244-
    def __init__(self, vertice, nombre, distancia = 0, visitado = False, padre = None):
244+
    if opcion == 1:
245-
        self.vertice = vertice
245+
        print("************** Recomendados ***************")
246-
        self.distancia = distancia
246+
        personaje = input("Ingrese el nombre del personaje: ")
247-
        self.visitado = visitado
247+
        personaje = personaje.upper()
248-
        self.padre = padre
248+
        personaje = verificar_personaje(grafo, personaje)
249-
        self.nombre = nombre
249+
        if not personaje: return
250-
        self.label_act = int(vertice)
250+
        cantidad = input("Ingrese cantidad de recomendados: ")
251
        cantidad = verificar_numero(cantidad)
252-
    def visitado(self):
252+
        if not cantidad: return
253-
        return self.visitado
253+
        op_recomendados(grafo, personaje, cantidad)
254
        
255-
    def cambiar_dist(self, distancia_nueva):
255+
    elif opcion == 2: 
256-
        self.distancia = distancia_nueva
256+
        print("************** Similares ***************")
257
        personaje = input("Ingrese el nombre del personaje: ")
258-
    def cambiar_padre(self, padre_nuevo):
258+
        personaje = personaje.upper()
259-
        self.padre = padre_nuevo
259+
        personaje = verificar_personaje(grafo, personaje)
260
        if not personaje: return
261-
    def __lt__(self,otro):
261+
        cantidad = input("Ingrese cantidad de similares: ")
262-
        return self.distancia < otro.distancia
262+
        cantidad = verificar_numero(cantidad)
263
        if not cantidad: return
264-
    def __gt__(self,otro):
264+
        op_similares(grafo, personaje, cantidad)
265-
        return self.distancia > otro.distancia
265+
266
    elif opcion == 3: 
267-
    def __eq__(self,otro):
267+
        print("************** Centralidad ***************")
268-
        return self.distancia == otro.distancia
268+
        cantidad = input("Ingrese cantidad de personajes centrales: ")
269
        cantidad = verificar_numero(cantidad)
270-
    def __str__(self):
270+
        if not cantidad: return
271-
        return str(self.vertice)+"; "+str(self.distancia)
271+
        op_centralidad(grafo, cantidad)
272
    
273
    elif opcion == 4:  
274-
def vertice_random_por_peso(diccionario_adyacentes):
274+
        print("************** Camino ***************")
275-
    '''Devuelve un vertice adyacente random, con altas prioridades de que su peso sea mayor al de los demas'''
275+
        personaje1 = input("Ingrese el nombre del personaje de origen: ")
276
        personaje1 = personaje1.upper()
277-
    total = sum(diccionario_adyacentes.values())
277+
        personaje1 = verificar_personaje(grafo, personaje1)
278-
    rand = random.uniform(0, total)
278+
        if not personaje1: return
279-
    acum = 0
279+
        personaje2 = input("Ingrese el nombre del personaje de destino: ")
280-
    for vertice, peso_arista in diccionario_adyacentes.items():
280+
        personaje2 = personaje2.upper()
281-
        if acum + peso_arista >= rand:
281+
        personaje2 = verificar_personaje(grafo, personaje2)
282-
            return vertice
282+
        if not personaje2: return
283-
        acum += peso_arista
283+
        op_camino(grafo, personaje1, personaje2)
284
        
285
    elif opcion == 5: 
286
        print("************** Distancias ***************")
287
        personaje = input("Ingrese el nombre del personaje: ")
288
        personaje = personaje.upper()
289
        personaje = verificar_personaje(grafo, personaje)
290
        if not personaje: return
291
        op_distancia(grafo, personaje)
292
        
293
    elif opcion == 6: 
294
        print("************** Estadisticas ***************")
295
        op_estadisticas(grafo)
296
        
297
    elif opcion == 7: 
298
        print("************** Comunidades ***************")
299
        op_comunidades(grafo)
300
301
    elif opcion == 8: return
302
        
303
       
304
def main():
305
    ruta = sys.argv[1]
306
    grafo = crear_grafo(ruta)
307
    menu(grafo)
308
            
309
main()