-
Notifications
You must be signed in to change notification settings - Fork 22
L11: Practica 4
- Tiempo: 2h
-
Objetivos de la sesión:
- Comprender los mecanismos para implementar funciones recursivas
- Aprender a implementar funciones recursivas
- Practicar, practicar, practicar
- Fecha: 2019/Dic/27
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
- Introducción
- Factorial de 3 con subrutinas encadenadas
- Factorial de 3 con subrutina recursiva
- Recursividad en cadenas
- Actividades NO guiadas
- Autores
- Licencia
- Enlaces
La recursividad nos permite modelar patrones que se repiten a diferentes niveles. Estos patrones pueden ser funcionales, en los que se define cómo funciona algo usando la propia función. Por ejemplo la función factorial: Para calcular el factorial de n, debes multiplicar n por el factorial de n-1
También pueden ser estructurales: estructuras similares dentro de una estructura, como por ejemplo las muñecas rusas. O las cadenas de caracteres: dentro de una cadena de caracteres podemos tomar una subcadena, que también será una cadena de caracteres
En informática la recursividad se utiliza un montón, especialmente en el área de compiladores. Cuando escribimos el código fuente de un programa, hay muchas estructuras que se repiten a diferentes niveles: en un bloque puede haber una sentencia if, que tiene en su interior otro bloque puede contener a su vez otras sentencias if... Estos patrones de estructuras recurrentes se modelan de manera muy natural con funciones recursivas
Aprenderemos los mecanismos que tenemos a nivel de procesador para implementar la recursividad
Las funciones recursivas son las que se llaman a sí mismas para realizar un cálculo. El ejemplo clásico es el cálculo del factorial de un número. Si fact(n) es la función que calcula el factorial del número n, sabemos que esta función se define de esta forma:
fact(n) = n * fact(n-1), con n>=2 y fact(1) = 1
¡Utilizamos la propia función fact() para definir el comportamiento de la función fact()!
Estas funciones las implementamos en el RISC-V usando subrutinas. Son un caso particular de las subrutinas encadenadas que vimos en la sesión anterior, por lo que tendremos que usar la pila para guardar las direcciones de retorno y los registros que se pasan como argumentos
Antes de aprender a hacer subrutinas recursivas en ensamblador vamor a hacer un ejemplo del cálculo del factorial de 3 usando subrutinas encadenadas. Así, el programa principal llamará a la función fact3() que calcula el factorial de 3. No tiene ningún parámetro de entrada, y devolverá 6
La función fact3(), a su vez, llamará a la función fact2() que tiene los mismos parámetros que fact3() pero devuelve el factorial de 2:
fact3() = 3 * fact2()
Por último, la función fact2() llamará a la función fact1():
fact2() = 2 * fact1()
La función fact1() devolverá 1, ya que el factorial de 1 es 1. Si representamos los niveles de profundidad tenemos lo siguiente:
En todos los niveles, excepto el último, se multiplica un número constante por lo que devuelve la función de nivel inferior. En el último caso sólo se devuelve la constante 1
Haremos que cada función imprima un mensaje para comprobar que se están ejecutando.
- Función fact1()
La función fact1() es la última (hoja), y no llama a ninguna otra, por lo que no será necesario crear una pila para guardar su dirección de retorno. La dirección de retorno está en ra
#----------------------------------------------------
#-- Subrutina que calcula el factorial del 1
#-- ENTRADAS:
#-- Ninguna
#-- SALIDAS:
#-- Devuelve 1
#----------------------------------------------------
.include "servicios.asm"
#-- Punto de entrada
.globl fact1
.data
msg: .string "\n> Fact1: 1"
.text
fact1:
#-- NO necesitamos pila
#-- La direccion de retorno está en ra
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Devolver 1
li a0, 1
ret
- Función fact2()
Se calcula el factorial de 2 multiplicando 2 por el resultado de llamar a fact1(): 2 * fact(1). En esta función necesitamos crear la pila para guardar la dirección de retorno. También se imprime un mensaje para saber por donde va la ejecución
#----------------------------------------------------
#-- Subrutina que calcula el factorial del 2
#-- ENTRADAS:
#-- Ninguna
#-- SALIDAS:
#-- Devuelve 2 * fact1()
#----------------------------------------------------
.include "servicios.asm"
#-- Punto de entrada
.globl fact2
.data
msg: .string "\n> Fact2: "
.text
fact2:
#-- Necesitamos pila para guardar
#-- la direccion de retorno
addi sp, sp, -16
sw ra, 12(sp)
#-- Calcular el factorial de 1
jal fact1
#-- Calcular 2 * fact1
li t0, 2
mul t1, a0, t0
#-- t1 contiene el resultado
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Imprimir el valor de factorial
mv a0, t1
li a7, PRINT_INT
ecall
#-- Recuperar direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
#-- Terminar
ret
- Función fact3()
Igual que fact2(), pero calculando el producto de 3 * fact2(). También es necesaria la pila para guardar la dirección de retorno
#----------------------------------------------------
#-- Subrutina que calcula el factorial del 3
#-- ENTRADAS:
#-- Ninguna
#-- SALIDAS:
#-- Devuelve 6
#----------------------------------------------------
.include "servicios.asm"
#-- Punto de entrada
.globl fact3
.data
msg: .string "\n> Fact3: "
.text
fact3:
#-- Necesitamos pila para guardar
#-- la direccion de retorno
addi sp, sp, -16
sw ra, 12(sp)
#-- Calcular el factorial de 2
jal fact2
#-- Calcular 3 * fact2
li t0, 3
mul t1, a0, t0
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- Recuperar a0
mv a0, t1
#-- Imprimir el valor de factorial
li a7, PRINT_INT
ecall
#-- Recuperar direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
ret
- Programa principal
Se llama a fact3() para calcular el factorial. Luego se imprime el resultado
#----------------------------------------------------------
#-- PROGRAMA PRINCIPAL
#--
#-- Calculo del factorial de 3, llamando a subrutinas
#-- de niveles inferiores
#----------------------------------------------------------
.include "servicios.asm"
#-- Constante. Queremos calcular el factorial de N
.eqv N 3
.data
msg1: .string "\n\nFactorial de "
msg2: .string ": "
.text
#-- Calcular el factorial
jal fact3
#-- a0 contiene el factorial de 3
#-- Lo guardamos en s0 para no perderlo
mv s0, a0
#-- Imprimir mensaje:
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Imprimir N
li a0, N
li a7, PRINT_INT
ecall
#-- Fin del mensaje
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Recuperar a0
mv a0, s0
#-- Imprimir el resultado
li a7, PRINT_INT
ecall
#-- Imprimir \n
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecall
Al ensamblar y ejecutar el programa (hay 4 ficheros en total), en la consola veremos lo siguiente:
El cálculo del factorial lo podemos simplificar mucho usando una subrutina recursiva. Ahora sólo hay una función: fact(), que tiene un argumento de entrada y retorna un valor de salida. El prototipo de la función es:
- int fact(int n)
Esta función se llama a sí misma para realizar el cálculo, pero es como si la hubiésemos implementado con varias subrutinas encadenadas, como en el ejemplo anterior. Dentro de ella hay que distinguir dos casos:
- Si n es 1, entonces la función simplemente deberá devolver el factorial de 1, que es 1
- Si n es mayor a 1, entonces deberá devolver el producto de n por el resultado de calcular fact(n-1): n * fact(n-1)
Puesto que la función recibe por a0 el número n, y tiene que llamar a una subrutina con n-1, será necesario guardar el valor n en la pila, para no perderlo, y respetar el convenio
Exactamente igual que en el ejemplo anterior, para calcular fact(3) se realizan dos llamadas a subrutinas. Una para calcular fact(2) y la última a fact(1)
- Función fact(n)
#-----------------------------------------------------
#-- Subrutina fact(n)
#-- Calcula el factorial de n, de forma recursiva
#-- ENTRADAS:
#-- a0: n
#-- DEVUELVE:
#-- a0: n * fact(a0-1)
#----------------------------------------------------
.include "servicios.asm"
.globl fact
.data
msg: .string "\n> Factorial "
.text
fact:
#-- En las funciones recursivas hay que comprobar
#-- primero en que nivel estamos: ¿El ultimo? ¿Intermedio?
#-- Si a0 > 1 no estamos en el nivel más profundo
li t0, 1
bgt a0, t0, fact_rec
#-- Estamos en el nivel mas profundo
#-- Es una subrutina Hoja
#-- No hace falta guardar direccion de retorno en la pila
#-- Imprimir mensaje
la a0, msg
li a7, PRINT_STRING
ecall
#-- El factorial de 1 es 1
li a0, 1
#-- Imprimir a0
li a7, PRINT_INT
ecall
#-- Ir al punto de salida
b fin
#--- Estamos en un nivel intermedio
fact_rec:
#-- Hay que crear la pila para guardar la direccion de retorno
addi sp, sp, -16
#-- Guardar direccion de retorno
sw ra, 12(sp)
#-- Guardar n en la pila
sw a0, 8(sp)
#-- Calcular fact(n-1)
addi a0, a0, -1
jal fact
r1: #-- Direccion de retorno
#-- (Para depurar)
#-- a0 = fact(n-1)
#-- Recuperar n
lw t0, 8(sp)
#-- Calcular t1 = n * fact(n-1)
mul t1, t0, a0
#-- Imprimir mensajes
la a0, msg
li a7, PRINT_STRING
ecall
#-- Imprimir n
mv a0, t0
li a7, PRINT_INT
ecall
#-- Colocar en a0 el resultado a devolver
mv a0, t1
#-- Recuperar direccion de retorno
lw ra, 12(sp)
addi sp, sp, 16
#-- Punto de salida
fin:
#-- Terminar
ret
- Programa principal:
#-----------------------------------------------------------------
#-- Programa principal: Calculo del factorial de un numero N
#-----------------------------------------------------------------
.include "servicios.asm"
#-- Numero del que queremos calcular el factorial
.eqv N 3
.data
msg1: .string "\n\nFactorial de "
msg2: .string ": "
.text
#-- Llamar a fact(N)
li a0, N
jal fact
r0: #-- Direccion de retorno al principal
#-- (para depurar)
#-- En a0 esta el resultado
#-- Guardarlo en s0
mv s0, a0
#-- Imprimir mensaje 1
la a0, msg1
li a7, PRINT_STRING
ecall
#-- Imprimir N
li a0, N
li a7, PRINT_INT
ecall
#-- Imprimir mensaje 2
la a0, msg2
li a7, PRINT_STRING
ecall
#-- Imprimir el resultado
mv a0, s0
li a7, PRINT_INT
ecall
#-- Imprimir \n
li a0, '\n'
li a7, PRINT_CHAR
ecall
#-- Terminar
li a7, EXIT
ecall
Cuando se ejecuta para N=3, el comportamiento es exactamente igual que en el ejemplo anterior. En los niveles 1 y 2 se crea pila para almacenar las direcciones de retorno a los niveles 0 y 1 respectivamente, así como los argumentos recibidos: 3 y 2
En la consola podemos ver lo que ha ocurrido. Si nos fijamos en la ventana del segmento de datos, si cambiamos la dirección a la de la pila, podemos ver todos los elementos que se han almacenado en ella: las dos direcciones de retorno y los argumentos
Las cadenas de caracteres que estamos utilizando las definimos como secuencias de caracteres que terminan con el carácter '\0'. Bien, dentro de cualquier cadena de caracteres encontramos a su vez más cadenas de caracteres. Es por tanto un ejemplo de estructura en la que podemos aplicar funciones recursivas.
Por ejemplo, si tenemos la cadena "Hola", en su interior encontramos la cadena "ola", que a su vez tiene la cadena "la", que a su vez tiene la cadena "a"
Puesto que para acceder a una cadena usamos el puntero a su primer carácter, basta con incrementar este puntero en una unidad para seleccionar la siguiente subcadena
Como las cadenas son estructuras recursivas, podemos definir funciones recursivas que trabajen con ellas. Así por ejemplo, para calcular la longitud de la cadena, basta con sumar 1 a la longitud de su primera subcadena (aquella que se obtiene al incrementar su puntero en una unidad)
En el lenguaje python, si cad es una cadena, cad[1:] representa la subcadena formada por todos los caracteres menos el primero. La función recursiva para calcular la longitud de una cadena se puede definir así:
def len(cad):
if cad=="":
return 0
else:
return 1 + len(cad[1:])
Usamos la propia función len() para implementar la función len()
La parte recursiva representa el caso general: el patrón que se repite en los diferentes niveles. Siempre hay un caso particular, final, que es diferente al general, donde la recursividad deja de existir. En el ejemplo de la longitud de la cadena es la cadena nula: '\0'. En ella ya no hay otra sub-cadena dentro. Por eso NO es recursiva. Hay que definir el valor de su longitud, que es 0
Los programas recursivos hay que tratarlos igual si fuesen llamadas a subrutina a funciones diferentes, que están en niveles inferiores. Hay que aplicar el mismo convenio de uso de los registros. La única diferencia es que se llama a una función con el mismo nombre. Pero no hay que olvidar que es una llamada a una subrutina en el nivel inferior, aunque tenga el mismo nombre.
La única forma de dominar la recursividad es practicarla. Haz los ejercicios. Practica. Si dominas esto, dominas todo lo que hemos hecho hasta ahora
Escribe un programa principal que calcule la longitud de una cadena introducida por el usuario y la imprima en la consola. Debe llamar a la función len(pcad) que recibe como argumento de entrada un puntero a la cadena y devuelve su longitud. Esta función se debe implementar de forma recursiva, siguiendo este algoritmo escrito en pseudocódigo python:
def len(cad):
if (cad[0] == "\n"):
return 0
else:
return len(cad[1:]) + 1
Donde cad([1:) es la cadena sin el primer carácter
La subrutina len() debe estar en un fichero separado
Escribe un programa principal que pida al usuario una cadena y la imprima del revés. Ej. Si el usuario introduce "Hola", se imprimirá "aloH". El programa principal llamará a la función print_reverse(pcad) que tiene como parámetro de entrada el puntero a la cadena a imprimir al revés. No devuelve ningún valor
La función print_reverse(pcad) se debe implementar de manera recursiva, siguiendo este algoritmo, dado en pseudocódigo python
def print_reverse(cad):
if (cad == “”):
return
else:
print_reverse(cad[1:])
print(cad[0])
La subrutina print_reverse() debe estar en un fichero separado
Escribir un programa principal que pida al usuario una cadena y luego un carácter. Se debe imprimir el número de esos caracteres que hay en la cadena. Para ello el programa principal deberá llamar a la función int count_char(pcad, car), cuyo primer argumento es un puntero a la cadena y el segundo es el carácter a contar. La función devuelve el número esos caracteres que hay en la cadena
La función int count_char(pcad, car) se debe implementar usando el siguiente algoritmo RECURSIVO, descrito mediante este pseudocódigo en python:
def count_char(cad, car):
if (cad==""):
return 0
else:
#-- Contar los caracteres de la subcadena
count = count_char(cad[1:], car)
#-- Sumar uno si el primer caracter es el pedido
if cad[0]==car:
count += 1
return count
En esta animación se muestra en funcionamiento:
Escribe un programa principal que pida al usuario un número, como cadena de caracteres, que lo convierta a su correspondiente número entero y que lo imprima. Se supondrá que el usuario sólo introduce los caracteres '0'-'9', de forma que NO hay que gestionar errores
El programa principal deberá llamar a la función conv(pcad) que realiza la conversión. Esta función devuelve 2 parámetros de salida. El primero es el número convertido. Y el segundo es el peso del dígito de mayor peso: 1, 10, 100, 1000, etc. Como parámetro de entrada tiene el puntero a la cadena
La función conv(pcad) se debe implementar usando el siguiente algoritmo RECURSIVO, descrito mediante este pseudocódigo en python:
def conv(cad):
if cad=="":
return 0,0
n,p = conv(cad[1:])
dig = ord(cad[0]) - ord('0')
if p == 0:
return dig, 1
else:
return dig*p*10+n, p*10
En esta animación se muestra en funcionamiento:
La subrutina conv() debe estar en un fichero separado
Escribir un programa principal para cifrar y descifrar una cadena introducida por el usuario. Primero se pide la cadena, luego la clave y se llama a la función cifrar(pcad1, pcad2, K) para cifrarla. Se imprime la cadena cifrada, se descifra llamando a la función cifrar con el parámetro -K y se imprime
En total debe haber 3 cadenas definidas en tiempo de ejecución:
- cad1: Contiene la cadena inicial introducida por el usuario
- cad2: Contiene la cadena cifrada
- cad3: Contiene la cadena descifrada a partir de cad2. Si todo ha ido bien, debe ser igual que la cadena cad1
La función cifrar(pcad1, pcad2, K) tiene 3 parámetros de entrada. El primero es el puntero a la cadena origen. El segundo es el puntero a la cadena destino, donde almacenar la cadena cifrada. El tercer parámetro es la clave: es el número que se suma a todos los caracteres para cifrarlos. Esta función NO devuelve ningún valor de retorno. Se debe implementar usando el siguiente algoritmo RECURSIVO, descrito mediante este pseudocódigo:
función cifrar(cad1, cad2, K):
Si el primer elemento de cad1 es '\n':
Almacenar en cad2[0] '\n' y en cad2[1] '\0'
Terminar
Si no:
Almacenar en cad2[0] el valor de cad1[0] + K (caracter cifrado)
llamar a cifrar(cad1[1:], cad[1:], K) para cifrar las subcadenas
En esta animación se muestra en funcionamiento:
La subrutina cifrar() debe estar en un fichero separado
Escribe un programa principal que calcule el valor máximo de una serie de bytes almacenados en memoria de forma consecutiva. En el programa principal se define esta serie en tiempo de compilación usando la directiva .byte. El último byte de la serie debe ser el 0, que indica el final. Por ejemplo:
num: 5,8,112,30,42,0
El programa principal llama a la función maximo(pdatos) a la que le pasa un puntero al primer byte de la serie. Esta función devuelve el máximo. El programa principal lo imprimirá en la consola
La función maximo(pdatos) se debe implementar usando el siguiente algoritmo RECURSIVO, descrito mediante este pseudocódigo en python:
def maximo(pdat):
if pdat[0]==0:
return 0
else:
m = maximo(pdat[1:])
if pdat[0] > m:
return pdat[0]
else:
return m
La subrutina maximo() debe estar en un fichero separado
Escribe un programa principal que pida al usuario el número del término de Fibonacci (n) a calcular y que imprima su valor. Para ello deberá llamar a la función Fibo(n), que tiene un argumento con el término a calcular y devuelve su valor. Esta función se debe implementar siguiente este algoritmo recursivo, descrito en pseudocódigo python:
def fibo(n):
if n <=2:
return 1
else:
return fibo(n-1) + fibo(n-2)
Así, por ejemplo, si se introduce n=10, el resultado deberá ser 55
La subrutina fibo() debe estar en un fichero separado
El objetivo es hacer un programa principal para evaluar expresiones que sean sumas de números de un único dígito (para hacerlo más sencillo), como por ejemplo esta:
1+3+4+5+6+7+2+3+2+0
Se detectarán errores sencillos: Si donde debería haber un dígito hay otra cosa: se notificará un error. Si donde debería haber un simbolo '+' hay otra cosa, también se notificará el error
Primero crearemos la función is_digit(car), que comprueba si car es un número decimal de un dígito: '0'-'9'. La función devuelve 2 parámetros: el primer es el valor entero (car - '0') y el segundo será 0 si car es un dígito y 1 en caso de error
def is_digit(car):
if car < '0' or car > '9':
return (0, 1)
else:
return (car - '0', 0)
Para evaluar la expresión usaremos la función evaluar(pcad), que tiene como parámetro un puntero a la expresión y devuelve dos parámetros: El primero con el valor de la expresión, si no ha habido errores, y el segundo indicando si ha habido error: 1 indica error, y 0 indica que todo ok
La función evaluar(pcad) se deberá implementar usando un algoritmo recursivo, siguiendo este pseudocódigo:
def evaluar(cad):
#-- Leer el primer digito
val1, err = is_digit(cad[0])
#-- Comprobar si hay error
if err:
return 0,1
#-- Comprobar si es el ultimo numero
if cad[1] == '\n':
return val1, 0
#-- Comprobar si tras el numero hay un +
if cad[1] != '+':
return 0,1
#-- Calcular la subexpresion
val, err = evaluar(cad[2:])
#-- Si hay error retornar
if err:
return 0,1
#-- Devolver la suma del primer digito + la subexpresion
return val1 + val, 0
Asi, por ejemplo, si se prueba con la siguiente expresión:
expr: .string "1+5+3+4+5+8+9+0+1+0+0+0+0+0+0+0\n"
El programa principal imprimirá en la pantalla el valor: 36. Todas las funciones se harán en ficheros separados
Queremos programar un contador ascendente, que vaya desde 1 hasta 9 en el display de 7 segmentos derecho (que se encuentra mapeado en la dirección 0xFFFF0010). Para ello necesitamos crear la función print_display(n) a la que se le pasa el número entero a mostrar en el display (0 - 9). No devuelve nada
Para implementar esta función usar una tabla de bytes en memoria, con los valores a enviar para encender los segmentos correspondientes a los dígitos 0 - 9. El primer byte contiene el valor para el dígito 0, el siguiente para el 1, etc. Si n es el número a mostrar, y base es la dirección del primer elemento de esta tabla, en base + n se encuentra el valor para representar el dígito n. Basta con hacer la suma y leer el valor de esa dirección
La función contar_up(n) realiza la cuenta hasta el número n. Tiene un parámetro de entrada, n, que indica el número al que llegar y no devuelve nada. La cuenta actual se imprimirá tanto en la consola como en el display de 7 segmentos. Se implementará usando un algoritmo recursivo, cuyo pseudocódigo es:
def contar_up(n):
if n > 1:
#-- Realizar la cuenta hasta n-1
contar_up(n-1)
#-- Mostrar la cuenta actual
print(n)
print_display(n)
El programa principal inicializa el display a 0 llamando a la función display_print(0), luego llamará a contar_up(9) y terminará. Todas las funciones se implementarán en ficheros separados
Queremos hacer ahora una cuenta descendente, desde 9 hasta 1, modificando el programa del ejercicio 9. La función para realizar la cuenta descendente la hace ahora la función count_down(n), donde n indica el número desde el que comienza la cuenta. Esta función se debe implementar también de forma recursiva.
Su algoritmo recursivo es muy parecido al ascendente, pero con algún cambio. Piensa primero cómo sería el pseudo-código. Una vez que tengas claro cómo sería, programa la función en ensamblador
La función count_down(n) muestra la cuenta actual en el display de 7 segmentos llamando a la misma función print_display(n) del ejercicio anterior
El programa principal llama a count_down(9) y termina. Todas las funciones se implementarán en ficheros separados
- Katia Leal Algara
- Juan González-Gómez (Obijuan)
L1: Práctica 1-1. RARs
L2: Práctica 1-2. Ensamblador
L3: Práctica 1-3. Variables
L4: Pract 2-1. E/S mapeada
L5: Práctica 2-2: Inst. ecall
L6: Prác 2-3: Cadenas
L7: Práct 3-1: Bucles y saltos
L8: Práct 3-2: Cadenas II
L9: Pract 4-1: Subrut. Nivel-1
L10: Pract 4-2: La pila
L11: Pract 4-3: Recursividad
L12: Pract 5-1. Heap. Listas
Simulacro examen 1
GISAM. Ordinario. 2019-Dic-11
GISAM. Extra. 2020-Jul-03
GISAM. Ordinario. 2021-Ene-21
GISAM. Ordinario. 2022-Ene-10
GISAM. Extra. 2022-Jun-29
GISAM. Parcial 1. 2022-Oct-26
GISAM. Parcial 2. 2022-Nov-30
GISAM. Parcial 3. 2022-Dic-21
GISAM. Parcial 1. 2023-Oct-09
GISAM. Parcial 2. 2023-Nov-11
GISAM. Parcial 3. 2023-Dic-20
GISAM. Extra. 2024-Jun-17
GISAM. Parcial 1. 2024-Oct-14
GISAM. Parcial 2. 2024-Nov-13
GISAM. Parcial 3. 2024-Dic-16
TELECO. Ordinario. 2019-Dic-13
TELECO. Extra. 2020-Jul-07
TELECO. Ordinario. 2021-Ene-21
TELECO. Extra. 2021-Jul-02
TELECO. Ordinario. 2022-Ene-10
TELECO. Extra. 2022-Jun-29
TELECO. Ordinario. 2023-Ene-10
TELECO. Extra. 2023-Jun-29
TELECO. Parcial 1. 2023-Oct-20
TELECO. Parcial 2. 2023-Nov-17
TELECO. Parcial 3. 2023-Dic-22
TELECO. Extra. 2024-Jun-17
TELECO. Parcial 1. 2024-Oct-10
TELECO. Parcial 2. 2024-Nov-21
TELECO. Parcial 3. 2024-Dic-19
Robótica. Ordinario. 2020-Jun-1
Robótica. Extra. 2020-Jul-13
Robótica. Ordinario. 2021-Mayo-20
Robótica. Extra. 2021-Junio-16
Robótica. Parcial 1. 2022-Feb-25
Robótica. Parcial 2. 2022-Abril-1
Robótica. Parcial 3. 2022-Mayo-6
Robótica. Parcial 1. 2023-Feb-27
Robótica. Parcial 2. 2023-Mar-27
Robótica. Parcial 3. 2023-May-08
Robótica. Parcial 1. 2024-Feb-26
Robótica. Parcial 2. 2024-Mar-20
Robótica. Parcial 3. 2024-May-06
Robótica. Extra. 2024-Junio-24
Datos. Parcial 1. 2023-Oct-09
Datos. Parcial 2. 2023-Nov-15
Datos. Parcial 3. 2023-Dic-20
Datos. Parcial 1. 2024-Oct-09
Datos. Parcial 2. 2024-Nov-13
Práctica 1: Sesiones 1,2 y 3
Práctica 2: Sesiones 4, 5 y 6
Práctica 3: Sesiones 7 y 8
Práctica 4: Sesiones 9, 10 y 11
Práctica 5: Sesión 12