LAB Tipos abstractos de datos
Contents
!wget --no-cache -O init.py -q https://raw.githubusercontent.com/rramosp/20192.L3/master/init.py
import init; init.init(force_download=False); init.get_weblink()
from local.lib.rlxmoocapi import submit, session
student = session.Session(init.endpoint).login( course_id=init.course_id, 
                                                session_id="UDEA", 
                                                lab_id="lab_01" )
LAB Tipos abstractos de datos¶
Ejercicio 1: Implementación de una pila¶
Definimos el siguiente TAD, como una estructura de tipo LIFO (Last In First Out)
TAD \(Stack\)
\(\forall S \in Stack, d \in AnyType\)
signatures: $$
$$
axioms:
- \(new().len() ::= 0 \) 
- \(S.put(d).get() ::= d\) 
- \(S.put(d).len() ::= S.len()+1\) 
- \(S.get() \;\;and\;\; S.len()==0::=Error\) 
- \(S.len()=n \;\;and\;\;S.get() \Rightarrow S.len()=n-1\) 
## Ejercicio 1
completa la función siguiente para añadir un elemento a una lista
ejecución de ejemplo:
> x = [1,4,3]
> print st.append(x,5)
[1, 4, 3, 5]
def append(l,d):
    return ...
verifica manualmente tu código
x = [1,4,3]
append(x,5)
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_01");
Ejercicio 2: Eliminación de elementos¶
completa la función siguiente para que, dada una lista, devuelva dos resultados:
- el valor del último elemento de la lista 
- una nueva lista en la que se eliminó el último elemento 
- si la lista es vacía debe de generar un - AssertionError
Ejemplo de ejecución:
> x = [1,5,6]
> print "initial", x
> for _ in range(len(x)+1):
>     v,x = getremove_last(x)
>     print "last val", v, ", remaining list", x
initial [1, 5, 6]
last val 6 , remaining list [1, 5]
last val 5 , remaining list [1]
last val 1 , remaining list []
----------------------------------------------------------------------
AssertionError                       Traceback (most recent call last)
<ipython-input-4-578f9f1609fe> in <module>()
def getremove_last(l):
    assert <... TU CODIGO AQUI ...>
    val, rest_list = <... TU CODIGO AQUI ...>
    return val, rest_list
comprueba manualmente tu código
x = [1,5,6]
print ("initial", x)
for _ in range(len(x)+1):
    v,x = getremove_last(x)
    print ("last val", v, ", remaining list", x)
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_02");
Ejercicio 3: Implementación de un stack¶
Completa la clase Stack con la semántica del TAD definido anteriormente. Usa las funciones anteriores si lo consideras necesario. Ejemplo de ejecución:
> print "stacking elements"
> s = st.Stack()
> s.put(4).put(2).put("hola")
> print s.len()
> print s.elements
> 
> print ("\n--\nunstacking")
> for _ in range(s.len()):
>     print s.get()
> 
> print "this next call must fail"
> s.get()
stacking elements
3
[4, 2, 'hola']
--
unstacking
hola
2
4
this next call must fail
AssertionErrorTraceback (most recent call last)
<ipython-input-13-749203dd226f> in <module>()
     13 
     14 print "this next call must fail"
---> 15 s.get()
    ...
def Stack(**kwargs):
    def append(l,d):
        <... TU CODIGO AQUI DEL EJERCICIO ANTERIOR ...>       
    def getremove_last(l):
        <... TU CODIGO AQUI DEL EJERCICIO ANTERIOR ...>       
    class Stack__class:
        def __init__(self):
            self.elements = []
        def put(self, d):
            <... TU CODIGO AQUI ...>
            return self
        def get(self):
            return <... TU CODIGO AQUI ...>
        def len(self):
            return <... TU CODIGO AQUI ...>
        
    return Stack__class(**kwargs)
print ("stacking elements")
s = Stack()
s.put(4).put(2).put("hola")
print (s.len())
print (s.elements)
print ("\n--\nunstacking")
for _ in range(s.len()):
    print (s.get())
    
try:
    s.get()
    print ("*** cuidado! tu código no contiene asserts")
except AssertionError:
    print ("*** tu codigo está generando asserts correctos")
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_03");
Implementación de una Cola¶
Definimos el siguiente TAD, como una estructura de tipo FIFO (First In First Out)
TAD \(Queue\)
\(\forall Q \in Queue, d \in AnyType\)
signatures: $$
$$
axioms:
- \(new().len() ::= 0 \) 
- \(Q.get()^n==d_i \iff Q=new().put(d_i)^n\) 
- \(Q.get(d).len() ::= Q.len()+1\) 
- \(Q.get() \;\;and\;\; Q.len()==0::=Error\) 
- \(Q.len()=n \;\;and\;\;Q.get() \Rightarrow Q.len()=n-1\) 
y definimos la notación:
- \(Q.put(d_i)^n ::= Q.put(d_1).put(d_2)....put(d_n)\) 
- \(Q.get()^n ::= Q.get()\;,\;... n\; veces\; ...\;,\; Q.get()\) y tomando el resultado del último 
## Ejercicio 4
Completa la clase Queue con la semántica que acabamos de definir. Ejemplo de ejecución
> import PS01_04 as st  
> reload(st)
> 
> print "enqueueing elements"
> q = st.Queue()
> q.put(4).put(2).put("hola")
> print q.len()
> print q.elements
>
> print ("\n--\ndequeueing")
> for _ in range(q.len()):
>     print q.get()
>
< print "this next call must fail"
> q.get()
enqueueing elements
3
[4, 2, 'hola']
--
dequeueing
4
2
hola
this next call must fail
AssertionErrorTraceback (most recent call last)
<ipython-input-284-50dc775a67ed> in <module>()
     13 
     14 print "this next call must fail"
---> 15 q.get()
def Queue(**kwargs):
    
    <... INSERTA AQUI LAS FUNCIONES QUE CREES QUE NECESITES ...>
    class Queue__class:
        def __init__(self):
            self.elements = []
        def put(self, d):
            <... TU CODIGO AQUI ...>
            return self
        def get(self):
            return <... TU CODIGO AQUI ...>
        def len(self):
            return <... TU CODIGO AQUI ...>
        
    return Queue__class(**kwargs)
print ("enqueueing elements")
q = Queue()
q.put(4).put(2).put("hola")
print (q.len())
print (q.elements)
print ("\n--\ndequeueing")
for _ in range(q.len()):
    print (q.get())
print ("this next call must fail")
q.get()
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_04");
Medición de renidimientos y complejidades algorítmicas¶
Ejercicio 5¶
completa la función siguiente que debe de llenar una cola o una pila t, pasada como argumento, con n números enteros aleatorios con valores entre 0 y 9, ambos incluidos. Usa np.random.randint
import numpy as np
def fill(t, n):
    return <... TU CODIGO AQUI ...>
comprueba manualmente tu código, cada ejecución ha de generar valores distintos
q = fill(Queue(), 100)
print ("len=",q.len())
for _ in range(q.len()):
    print (q.get(), end=" ")
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_05");
Ejercicio 6¶
completa la función siguiente que debe de vaciar una cola o pila t y devolver el conjunto de datos obtenido tras vaciarla.
def empty(t):
    return <... TU CODIGO AQUI ...>
a = [1,4,1,10,2,3]
q = Queue()
for i in a:
    q.put(i)
k = empty(q)
k
registra tu solución en línea¶
student.submit_task(globals(), task_id="task_06");
Experimento¶
ya acabaste el taller, fíjate ahora como hacemos un experimento con pilas y colas de varios tamaños y comparamos los tiempos de ejecución. Comparamos también con las implementaciones que ya vienen en las librerías estándar de Python: Queue LifoQueue. Observa que queue.Queue es funcionalmente equivalente a nuestro Queue y queue.LifoQueue es equivalente a nuestro Stack.
Hazte las siguientes preguntas:
- ¿Qué implementaciones son más lentas y más rápidas? ¿Por qué? 
- ¿Qué complejidades tenemos en inserción y recuperación? \(\mathcal{O}(n)\), \(\mathcal{O}(\log n)\), \(\mathcal{O}(c)\) ? 
Observa que las métricas de tiempo tienen dos componentes:
- El debido a la complejidad intrínseca del algoritmo 
- El debido a la implementación del mismo 
import matplotlib.pyplot as plt
%matplotlib inline
def compare(class_1, class_2, n_powers=5):
    global n, class_A, class_B, q
    class_A = class_1
    class_B = class_2
    t1, t2, t3, t4 = [], [], [], []
    ns = [int(10**i) for i in range(n_powers)]
    
    for n in ns:
        print (n,end=" ")
        k = %timeit -o -q fill(class_A(), n)
        t1.append(k.best*1000)
        k = %timeit -o -q fill(class_B(), n)
        t2.append(k.best*1000)
        
        q = fill(class_A(), n)
        k = %timeit -o -r 1 -n 1 -q empty(q)
        t3.append(k.best*1000)
        q = fill(class_B(), n)
        k = %timeit -o -r 1 -n 1 -q empty(q)
        t4.append(k.best*1000)
                
    plt.figure(figsize=(10,3))
    
    plt.subplot(121)
    plt.plot(t1, label=class_A.__name__+" fill")
    plt.plot(t2, label=class_B.__name__+" fill")
    plt.xticks(range(len(ns)), ns)
    plt.xlabel("number of elements")
    plt.ylabel("time (ms)")
    plt.legend()
    
    plt.subplot(122)
    plt.plot(t3, label=class_A.__name__+" empty")
    plt.plot(t4, label=class_B.__name__+" empty")
    plt.xticks(range(len(ns)), ns)
    plt.xlabel("number of elements")
    plt.legend()
compare(Queue, Stack);
import queue
class wQueue(queue.Queue):
    def len(self):
        return self.qsize()
class wLifoQueue(queue.LifoQueue):
    def len(self):
        return self.qsize()
    
compare(wQueue, wLifoQueue)