0% found this document useful (0 votes)
7 views7 pages

Exam Parcial 5 Mates

Exam parcial 5 mates

Uploaded by

pictabuweb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views7 pages

Exam Parcial 5 Mates

Exam parcial 5 mates

Uploaded by

pictabuweb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

EDA - GCD

2º parcial de teorı́a
15 de enero de 2024 TIPO A
NOMBRE: Fila: Col:

1. (2.5 puntos) Dada la clase MinHeap:


class MinHeap :
def __init__ ( self ) :
self . _data = []
def _heapify ( self , pos ) :
...
Se pide implementar los siguientes métodos:
a) (1.25 puntos) Un método aplicar que recibe una función f y devuelve un bool. Hace lo siguiente:
Reemplaza cada elemento del MinHeap por el resultado de aplicar f a ese elemento.
Si tras este proceso se sigue preservando la propiedad de MinHeap, lo deja ası́ y devuelve True.
En otro caso, deja los valores originales (como si nunca se hubiese llamado a este método aplicar)
y devuelve False.
b) (1.25 puntos) Un método extractAdd que recibe un nuevo elemento value. Este método extrae el menor
elemento (y lo devuelve) e inserta el nuevo elemento value en la misma operación. En lugar de llamar a
remove_min y a add realiza una sola llamada a _heapify en el momento adecuado. Si el heap estaba vacı́o
dispara un KeyError(’MinHeap is empty.’) y no hace nada más.
Atención: Si utilizas algún método de MinHeap (aparte de _heapify) debes incluir el código.
Solución:
a) (1.25 puntos) Método aplicar: Es necesario guardarse los datos originales del vector por si acaso, tras
aplicar la función, éste deja de ser un heap. Una vez aplicada la función, hecemos uso de un método
auxiliar is_heap (aunque se podrı́a poner ese dódigo dentro del método que nos piden) que comprueba si
todos los elementos que tienen padre son mayores o iguales a su padre. En caso afirmativo, devolvemos
True descartando la copia de los datos originales, en caso contrario, utilizamos los datos guardados para
revertir el vector y devolvemos False:
class MinHeap :
...
def is_heap ( self ) :
return all ( self . _data [( i -1) //2] <= self . _data [ i ]
for i in range (1 , len ( self . _data ) ) )

def aplicar ( self , f ) :


guardo = self . _data
self . _data = [ f ( x ) for x in guardo ]
if self . is_heap () :
return True
else :
self . _data = guardo
return False
Es posible combinar la aplicación de la función con la comprobación de que el vector tiene la propiedad
de heap. Eso harı́a que se dejase de calcular la función en cuanto se viese que ya no cumple la propiedad,
pero no mejorarı́a el coste en el caso peor y complicarı́a el código:
class MinHeap :
...
def aplicar ( self , f ) :
if len ( self . _data ) > 0:
nuevo = [ f ( self . _data [0]) ]
for i in range ( 1 , len ( self . _data ) ) :
nuevo . append ( f ( self . _data [ i ]) )
if nuevo [( i -1) //2] > nuevo [ i ]:
return False
self . _data = nuevo
return True
b) (1.25 puntos) Método extractAdd, tras comprobar si el heap está vacı́o, se guarda el valor mı́nimo, pone
en su lugar el nuevo valor y aplica _heapify en la raı́z para restaurar (si hace falta) la propiedad de
MinHeap. Finalmente devuelve el valor mı́nimo guardado:

class MinHeap :
...
def extractAdd ( self , value ) :
if len ( self . _data ) == 0:
raise KeyError ( ’ MinHeap is empty . ’)
themin = self . _data [0]
self . _data [0] = value
self . _heapify (0) # restaurar propiedad de heap
return themin

Se puede observar que la primera parte es similar al método remove_min, salvo que en lugar de tomar
el último elemento para ponerlo en la raı́z pone el que vamos a insertar a continuación. Como el nuevo
elemento se situa en la raı́z de un vector que ya era heap, se puede restaurar la propiedad de heap (si
hiciese falta) por medio de self._heapify(0).

2. (2 puntos) Dada la clase MFset vista en clase y el siguiente MFset de ejemplo:


class MFset :
def __init__ ( self ) :
self . _p = {} # inicialmente todos representantes

def find ( self , x ) :


while x in self . _p : # mientras exista padre
x = self . _p [ x ] # subimos a su padre
return x # devolvemos el representante
...

Se pide implementar un método llamado relacion que recibe 2 argumentos que llamaremos quien y
referencia. El resultado de la llamada al método es una de estas cadenas según el caso:
’mismo’ si quien y referencia son el mismo.
’descendiente’ si quien está colgado por debajo de referencia en el bosque con el que se representa
internamente un MFset.
’antecesor’ si referencia está colgado por debajo de quien.
’parientes’ si no es ninguna de las anteriores pero ambos comparten un antecesor común.
’nada’ si no están en la misma clase de equivalencia
Por ejemplo, si m es el MFset de la figura, la llamada m.relacion(3,7) devuelve ’antecesor’, la llamada
m.relacion(9,2) devuelve ’parientes’ y la llamada m.relacion(4,8) devuelve ’nada’.
Atención: Si utilizas algún método (aparte de find) debes incluirlo en el código.
Solución: Hacemos uso de un método auxiliar debajo que recibe 2 elementos y comprueba si el primero
está debajo del segundo en el árbol donde se encuentre el primero. Este método lo utilizaremos dos veces.
También utilizremos find para ver si dos elementos están en la misma clase de equivalencia:
class MFset :
...
def debajo ( self , x , y ) : # devuelve True si x debajo de y
while x in self . _p : # mientras x tenga padre
x = self . _p [ x ] # subimos a su padre
if x == y :
return True
return False

def relacion ( self , quien , referencia ) :


if quien == referencia :
return ’ mismo ’
if self . debajo ( quien , referencia ) :
return ’ descendiente ’
if self . debajo ( referencia , quien ) :
return ’ antecesor ’
if self . find ( quien ) == self . find ( referencia ) :
return ’ parientes ’
return ’ nada ’

Otra opción (entre muchas otras) consiste en aprovechar el recorrido ascendente del método find para
localizar el antecesor o el descendiente (según el caso) y ası́ ahorrarnos repetir ese recorrido con el método
find:

class MFset :
...
def relacion ( self , quien , referencia ) :
if quien == referencia :
return ’ mismo ’
rquien = quien
while rquien in self . _p :
rquien = self . _p [ rquien ]
if rquien == referencia :
return ’ descendiente ’
rref = referencia
while rref in self . _p :
rref = self . _p [ rref ]
if rref == quien :
return ’ antecesor ’
if rquien == rref :
return ’ parientes ’
return ’ nada ’
3. (3.5 puntos) Dada la clase DiGraph vista en clase que representa un grafo como lista de adyacencia:

class DiGraph :
def __init__ ( self ) :
self . _adj = {}
...
def Dijkstra ( self , src ) :
...
return dist , pred

Se pide:
a) (2 puntos) Implementa un método caminoPorTodas que recibe una lista de vértices y debe devolver True
si se trata de un camino en el grafo que pasa al menos una vez por cada una de las aristas del grafo. En
cualquier otro caso devolverá False.
b) (1.5 puntos) Implementa un método caminoMasCorto que recibe 3 vértices origen, parada, destino. Este
método debe calcular el camino más corto del origen a la parada y de ahı́ el más corto al destino. Devuelve
una tupla con la distancia total del camino y luego el camino entero como secuencia de vértices. Si no
existe el camino se devolverá None. Puedes utilizar Dijkstra que devuelve un diccionario de distancias y
otro de predecesores.
Atención: Si utilizas algún método de DiGraph (aparte de Dijkstra) debes incluir el código.
Solución:
a) (2 puntos) Método caminoPorTodas se puede implementar de formas más complicadas que la presentada
aquı́. En principio hay que ver que es un camino. Para ello hay que ver que todas las aristas del camino
están en el grafo. Por otra parte, también hay que comprobar que todas las aristas del grafo aparecen
en el camino. Ambas cosas se corresponden, en teorı́a de conjuntos, a comprobar si ambos conjuntos de
aristas son iguales (es decir, A ⊆ B ∧ B ⊆ A ⇔ A = B):
class DiGraph :
...
def caminoPorTodas ( self , camino ) :
toda s_las_ arist as = set (( u , v ) for u , lista in self . _adj . items ()
for v , w in lista )
aristas_camino = set ( ( camino [ i ] , camino [ i +1])
for i in range ( len ( camino ) -1) )
# tb sirve aristas_camino = set ( zip ( camino , camino [1:]) )
return aristas_camino == to das_la s_aris tas
Una forma más complicada e ineficiente de resolver el problema serı́a, por ejemplo, ası́:

class DiGraph :
...
def existe_arista ( self , u , v ) :
return u in self . _adj and any ( v2 == v for ( v2 , w ) in self . _adj [ u ])

def ca mi no Tod as Ar is ta s ( self , camino ) :


aristas = set ()
for u , v in zip ( camino , camino [1:]) :
# comprobamos que camino es un camino en el grafo :
if not self . existe_arista (u , v ) :
return False
# y de paso nos guardamos las aristas del camino :
aristas . add (( u , v ) )
# ahora recorremos todas las aristas del grafo :
for u , lista in self . _adj . items () :
for v , w in lista :
# comprobar que estan entre las aristas del camino :
if (u , v ) not in aristas :
return False
return True
b) (1.5 puntos) Método caminoMasCorto: basta con llamar a Dijkstra desde el origen y comprobar que llega a
la parada. Si hay camino entonces lanzamos otro Dijkstra desde la parada para ver si alcanza el destino.
En caso afirmativo sumamos las distancias, calculamos los caminos y los juntamos. Si alguno de los
caminos no existe devolvemos None. El único detalle es no replicar el vértice parada al concatenar los
caminos. A la hora de calcular los caminos debemos de tener en cuenta la forma en que se ha inicializado
el diccionario de predecesores. Vamos a suponer que hemos inicializado los predecesores con pred = {}:

class DiGraph :
...
def caminoMasCorto ( self , origen , parada , destino ) :
# camino del origen a la parada :
dist1 , pred1 = self . Dijkstra ( origen )
if parada not in dist1 :
return None
# camino de la parada al destino :
dist2 , pred2 = self . Dijkstra ( parada )
if destino not in dist2 :
return None
# la distancia total :
dist = dist1 [ parada ]+ dist2 [ destino ]
# recuperamos camino desde el destino hasta la parada :
path = []
r = destino
while r in pred2 :
path . append ( r )
r = pred2 [ r ]
# no ponemos el vértice parada ( se pone debajo ) :
r = parada
while r in pred1 :
path . append ( r )
r = pred1 [ r ]
path . append ( r )
# tenı́amos el camino al revés :
path . reverse ()
# lo que nos pedı́an :
return dist , path

Si se hubiese inicializado pred={ src:None } la parte final del método serı́a como sigue:

...
# recuperamos camino desde el destino hasta la parada :
path = []
r = destino
while r is not None :
path . append ( r )
r = pred2 [ r ]
r = parada
while r is not None :
r = pred1 [ r ]
path . append ( r )
# tenı́amos el camino al revés :
path . reverse ()
# lo que nos pedı́an :
return dist , path
4. (2 puntos) Tenemos una lista de tuplas con alumnos y las notas que han sacado en las asignaturas:
n =[( ’ Juan ’ ,{ ’ mates ’ :6.7 , ’ fisica ’ :7.8 , ’ quimica ’:9 , ’ historia ’ :4.7 , ’ dibujo ’ :7.5}) ,
( ’ Maria ’ ,{ ’ fisica ’ :8.7 , ’ quimica ’ :5.6 , ’ historia ’ :7.1 , ’ literatura ’ :7.7}) ,
( ’ Sandra ’ ,{ ’ mates ’ :7.5 , ’ fisica ’ :7.8 , ’ literatura ’:9 , ’ historia ’ :7.3}) ,
( ’ Pablo ’ ,{ ’ fisica ’ :3.4 , ’ mates ’ :7.1 , ’ quimica ’ :9.5 , ’ historia ’ :9.3}) ,
( ’ Nuria ’ ,{ ’ literatura ’ :6.7 , ’ fisica ’ :7.8 , ’ quimica ’ :9.5 , ’ mates ’ :5.2}) ]

También nos proporcionan la siguiente función:


def tipoNota ( nota ) :
if nota <5:
return ’ suspenso ’
elif nota <7:
return ’ aprobado ’
elif nota <9:
return ’ notable ’
else :
return ’ sobresaliente ’
Nos piden utilizar el paradigma de programación Map-Reduce para determinar, para cada una de las cate-
gorı́as de nota (’suspenso’, ’aprobado’, ’notable’ y ’sobresaliente’) cómo se distribuyen las asignaturas.
Para el ejemplo anterior obtendrı́amos este resultado:
{
’ suspenso ’: [( ’ historia ’ ,0.5) ,( ’ fisica ’ ,0.5) ] ,
’ aprobado ’: [( ’ quimica ’ ,0.25) ,( ’ literatura ’ ,0.25) ,( ’ mates ’ ,0.5) ] ,
’ notable ’: [( ’ fisica ’ ,0.4) ,( ’ dibujo ’ ,0.1) ,( ’ historia ’ ,0.2) , ( ’ literatura ’ ,0.1) ,
( ’ mates ’ ,0.2) ] ,
’ sobresaliente ’: [( ’ quimica ’ ,0.6) ,( ’ literatura ’ ,0.2) ,( ’ historia ’ ,0.2) ]
}

Si nos fijamos en la entrada ’suspenso’ vemos que un 50 % de suspensos son en ’historia’ y otro 50 % en
’fisica’. Por otra parte, un 25 % de los aprobados se han dado en ’quimica’, otro 25 % en ’literatura’
y ası́ sucesivamente.
Se pide completar el siguiente código para resolver este problema:
class PorcentajesTipo ( MapReduce ) :
# COMPLETAR

obj = PorcentajesTipo ()
print ( obj ( n ) ) # n es la variable declarada al inicio del ejercicio

Solución: La idea básica es que el mapper recibe el resultado de iterar los pares clave y valor del objeto
n descrito en el enunciado (las claves son los alumnos, cada valor es un diccionario que a cada asignatura
matriculada le asocia una nota). Por tanto, cada llamada a mapper nos proporciona el nombre de un
alumno y sus asignaturas con las notas. El nombre lo ignoramos y emitimos la nota categórica (tipo de
nota) asociada a la asignatura.
Hay una llamada a reducer por cada tipo de nota que recibe una lista de asignaturas. Lo que tiene que
hacer es obtener las frecuencias relativas de cada asignatura. Para ello las contamos (podemos utilizar un
defaultdict). Finalmente el reducer va devolviendo la frecuencia relativa de cada asignatura.
El código podrı́a quedar de la siguiente forma:
class PorcentajesTipo ( MapReduce ) :
def mapper ( self , nom , notas ) :
for asignatura , nota in notas . items () :
yield tipoNota ( nota ) , asignatura
def reducer ( self , tipo , listaAsig ) :
d = collections . defaultdict ( int )
for asig in listaAsig :
d [ asig ] += 1
for asig , count in d . items () :
yield ( asig , count / len ( listaAsig ) )
Podemos utilizar collections.Counter en lugar de defaultdict:
class PorcentajesTipo ( MapReduce ) :
def mapper ( self , nom , notas ) :
for asignatura , nota in notas . items () :
yield tipoNota ( nota ) , asignatura
def reducer ( self , tipo , listaAsig ) :
d = collections . Counter ( listaAsig )
for asig , count in d . items () :
yield asig , count / len ( listaAsig )

o bien utilizar un diccionario estándar:


class PorcentajesTipo ( MapReduce ) :
def mapper ( self , nom , notas ) :
for asignatura , nota in notas . items () :
yield tipoNota ( nota ) , asignatura
def reducer ( self , tipo , listaAsig ) :
d = {}
for asig in listaAsig :
d [ asig ] = d . get ( asig ,0) + 1
for asig , count in d . items () :
yield asig , count / len ( listaAsig )

You might also like