Subido por leonardo20.nm

Laboratorios

Anuncio
Programación Avanzada:
7. Laboratorios
Nicolas Thériault
Departamento de Matemática y Ciencia de la Computación,
Universidad de Santiago de Chile.
N. Thériault (USACH)
Presentación
1 / 28
Laboratorios (fechas tentativas)
1
Resolver sistemas lineales grandes, 23/10
2
Multiplicar matrices, 06/11
3
Calcular matrices inversas y resolver sistemas grandes, 20/11
4
Ordenar listados de puntos según sus coordenadas, 18/12
5
Resolver un problema de geometrı́a computacional, 15/01
6
Resolver un problema de programación dinamica, 22/01
N. Thériault (USACH)
Presentación
2 / 28
Para los laboratorios 1, 2 y 3
Para evitar problemas de redondeo en la aritmética de matrices:
se trabaja módulo p = 3781996138433268199 (número primo de 62 bits)
aritmética de enteros (exacta, no hay problemas de redondeo)
permite dividir entre cualquier número 6= 0
suma/restas con resultados en {0, 1, . . . , p − 1}
producto con resta modular
programados para long de 32 bits, long long de 64 bits
deberı́a ser compatible con compiladores C99, C11 y C17
inverso (b −1 ): utiliza el algoritmo Euclidiano extendido
división a/b: calcular como a · (b −1 )
operaciones especiales:
long long SumaP( long long a , long long b ) ;
long long RestaP( long long a , long long b ) ;
long long MultP( long long a , long long b) ;
long long InvP( long long a ) ;
N. Thériault (USACH)
Presentación
3 / 28
Para los laboratorios 4 y 5
Para evitar problemas de redondeo en la evaluación de distancias:
se trabaja con enteros long
aritmética de enteros para d 2 (exacta, no hay problemas de redondeo)
I
I
I
I
I
d1 > d2 si y solo si d1 2 > d2 2
Distancias al cuadrado en formato long long
Calcular la raı́z cuadrada introduce un error, complica para comparar
Riesgo: d + 1 y d − 2 parecen distintos aunque deberı́an ser iguales
Riesgo: d1 + 1 > d2 − 2 aunque d1 < d2
programados para long de 32 bits, long long de 64 bits
deberı́a ser compatible con compiladores C99, C11 y C17
I
I
para C11 y C17, se puede trabajar con listados más grandes
para C99, empieza a producir repeticiones de puntos más temprano
N. Thériault (USACH)
Presentación
4 / 28
Objetivos
Trabajar en equipos
Seleccionar técnicas de programación adecuadas según el problema a resolver
Aplicar las técnicas vistas en clases en situaciones prácticas
Controlar la utilización de memoria dentro de los programas
Producir código limpio y eficiente
Hacer experimentos para estimar el comportamiento del algoritmo a grande escala
N. Thériault (USACH)
Presentación
5 / 28
Aspectos generales
Utilizar un sistema de menu en la ejecución del algoritmo.
Si debe leer un archivo (para trabajar los datos que contiene), debe pedir a los
usuarios de escribir su nombre dentro de la ejecución del programa
Testear todas sus funciones.
Si suponen algunos limitantes en las entradas, documentarlo
Siempre deben liberar la memoria utilizada antes de cerrar el programa (antes de
hacer el “salir” final)
I
Si necesario, hacer una pausa del tipo:
printf(“Listo para cerrar, entrar cualquier entero: ”);
scanf(“%d”,&selecion);
N. Thériault (USACH)
Presentación
6 / 28
C vs C++
Ventajas del C++:
Programación orientada a objetos
I
Manejo más flexible de los tipos de variables
I
Modelos (templates)
I
Métodos/funciones virtuales
Manejo de excepciones (exception handling)
Librerı́as estandares más amplias
N. Thériault (USACH)
Presentación
7 / 28
desventajas del C++
Algunas de las funcionalidades del C++ pueden afectar la eficiencia de los programas:
Modelos (templates)
I
I
Requiere cuidado para asegurar que no tiene mucho costo
Hacer el trabajo especifico al caso permite tomar ventaja de algunas propiedades
Métodos/funciones virtuales
I
Afecta la eficiencia
Manejo de excepciones (exception handling)
I
I
I
Afecta la eficiencia
Da seguridad para evitar errores, pero las verificaciones se pagan
Mejor verificar solamente cuando es necesario
Librerı́as estandares más amplias
I
I
Requiere cuidado para asegurar que no tiene mucho costo
Si se utiliza algo sin entenderlo, puede no ser lo más optimal para el caso
N. Thériault (USACH)
Presentación
8 / 28
Ventajas de C
Lenguaje más pequeño, pero completo:
Se puede conocer todo el lenguaje estandar (sin tener que utilizar referencias).
Sencillo de leer.
Hay pocas manejas de hacer las cosas, por lo que es más sencillo entender el código
escrito por otros/otras.
Facilita incorporar nuevas personas en un grupo de trabajo.
Obliga a ser más cuidadoso
Los programas en C++ pueden ser igual de eficientes de los en C, pero es más fácil
de “descuidarse” en C++, y los descuidos se pueden acumular unos sobre otros.
Para trabajos conjuntos: TODO el equipo debe mantener el cuidado
N. Thériault (USACH)
Presentación
9 / 28
Entradas y salidas en C
formato básico:
printf (equivalente de cout)
printf(“Listado de %d entradas.\n”,valor);
scanf (equivalente de cin)
scanf(“%d”,&valor);
Debe incluir los tipos de variables en la parte de “texto”:
c (char), s (string)
d (int), u (unsigned), ld (long), lld (long long)
f (float - decimal), e (float - exponencial), lf (double), llf (long double)
En printf, se puede inticar el número de posiciones (no utilizar en scanf):
%5d → entero de ≥ 5 espacios decimales
%6.3f → número decimal de 6 posiciones (con el “.”), hasta los milésimos.
Con archivos:
fprintf, fscanf (formato parecido, pero incluye el nombre del archivo)
Empezar con fopen, terminar con fclose
Tipo de variable del archivo: FILE (en mayusculas)
N. Thériault (USACH)
Presentación
10 / 28
Punteros
Los punteros corresponden a la posición (bloque de memoria) donde se encuentran
unos datos
Se definen como las estructuras a las cuales apuntan, con una ∗ antes del nombre
tipo *nombre de la variable;
float *puntoflotante;
long *enterogrande;
Una vez definido, no se escribe el ∗
puntoflotante = NULL;
Para conocer el puntero asociado a una variable, se pone & frente al nombre
puntoflotante = &abc;
Necesarios para trabajar las entradas y salidas de muchas funciones
I
Por ejemplo si una función requiere cambiar el valor de una de sus entradas
N. Thériault (USACH)
Presentación
11 / 28
Arreglos
Bloques continuos de memoria de un mismo tipo de estructura.
Se debe conocer el tamaño (cantidad de entradas) al momento de definir
Se definen como sus estructuras, con un [tamaño] después del nombre
I
El tamaño puede ser un valor fijo o una variable (int/long) con un valor dado
tipo nombre[tamaño];
float puntosflotantes[7];
long enterosgrandes[n];
La j-ésima entrada (empezando a contar posiciones en 0) se obtiene como:
nombre[j];
puntosflotantes[4];
En efecto, un arreglo es un puntero a la primera entrada
I
I
I
Para pasar un arreglo como entrada de una función, se utiliza solo el puntero
El procesador calcula la posición de cada entrada sumando su indice al puntero inicial
Las funciones que reciben arreglos, no conocen su tamaño
N. Thériault (USACH)
Presentación
12 / 28
Memoria dinámica en C
Permiten reservar espacios de memoria que seguirán disponibles fuera de la función
donde fueron definidos, en general como arreglos, sin restricción de tamaño total (dentro
de lo que permite el procesador).
malloc: reserva un bloque de espacios de memoria, devolviendo la dirrección inicial
int *arreglo = (int *) malloc( 20 * sizeof(int) ) ;
calloc: reserva un bloque de espacios de memoria, inicializando en 0
int *arreglo = (int *) calloc( 20 , sizeof(int) ) ;
realloc: ajusta el espacio de memoria asociado a una variable (puede cambiar la
dirección inicial). Libera el espacio ya no utilizado y copia los valores si la dirección
cambia.
arreglo = (int *) realloc( arreglo , 20 * sizeof(int) ) ;
free: libera (todo) el espacio asociado a la variable
free( arreglo ) ;
I
I
No libera los espacios apuntados dentro la variable
Necesario para evitar que la memoria queda reservada mientrás funciona el programa
N. Thériault (USACH)
Presentación
13 / 28
Estructuras en C
Definición (ejemplo: nodo de árbol binario):
typedef struct nodoarbol {
long valor ;
nodoarbol *ramaizquierda ;
nodoarbol *ramaderecha ;
} nodo ;
I
I
I
podemos poner cuantos elementos como queremos, de cualquier tipos
el nombre inicial (temporario) sirve para poner punteros a la estructura dentro de ella
el nombre final será el oficial de la estructura, puede ser distinto o repetir el inicial
Definir una variable/puntero:
nodo bloque = { 3 , izq , der } ;
nodo *dondebloque ;
Llamado a un elemento:
bloque.valor ;
dondebloque->ramaizquierda ;
(*dondebloque).ramaizquierda ;
En malloc, calloc y realloc:
sizeof( nodo ) ;
N. Thériault (USACH)
Presentación
14 / 28
Matrices y arreglos dobles
El lenguaje C permite definir arreglos dobles (matrices), pero su uso es limitado:
Se define como
tipo nombre[tamaño1][tamaño2];
En realidad es un arreglo simple de largo tamaño1 × tamaño2, donde los dados
están organizados en “filas” de largo tamaño2.
La posición de del bloque nombre[i][j] es i × tamaño2 + j
Para pasar a una función se hace como para un arreglo simple, con el puntero del
bloque inicial
Se pierde el formato “arreglo doble” para la función, se ve como un arreglo simple
La función no puede utilizar directamente la organización interna
Como consecuencia, los arreglos dobles son poco prácticos para ser utilizados en
sub-funciones:
Riesgos de trabajo fuera del arreglo (simple o doble), que puede producir un
segmentation fault o corromper los valores de otras variables
Se deben enviar todos los tamaños de los arreglos como entradas de la función
Se debe calcular la posición como i × tamaño2 + j para todas las utilizaciones
N. Thériault (USACH)
Presentación
15 / 28
Matrices, alternativa 1
una forma manera de reducir los cálculos consiste en utilizar punteros dobles:
Se define como
tipo **nombre;
Es un arreglo de punteros, cada entrada corresponde a la posición inicial de una fila
(arreglo de variables tipo)
En vez de hacer el cálculo “i × tamaño2 + j”, se sigue el i-ésimo puntero y se toma
la j-ésima entrada de este
Si se utilizan varias entradas de una misma fila, se puede copiar el puntero del
arreglo para reducir el trabajo
Trabajar con memoria dinámica:
I
I
Se crea el espacio para el arreglo de punteros primero, luego se crea el espacio para
cada fila y se guarda su ubicación en el arreglo de punteros.
Para liberar el espacio de memoria, primero liberar el espacio de cada filas, y luego el
espacio del arreglo de punteros
Más flexible para la memoria: no requiere un espacio continuo para toda la matriz,
solamente un espacio continuo para cada filas (que pueden ser separadas una de la
otra) y un espacio continuo para el listado de filas.
Todavı́a hay que entregar las dimensiones como entradas de funciones que utilizan la
matriz.
N. Thériault (USACH)
Presentación
16 / 28
Matrices, alternativa 2
Definir estructuras especiales para contener la matriz:
Una estructura que contiene las dimensiones (tamaños) de la matriz y un puntero
para el arreglo (o puntero doble)
Ejemplo:
typedef struct matriz {
long filas ;
long columnas ;
double **listado ;
} matriz ;
Para enviar a funciones, se necesita solamente la estructura (o su puntero) como
entrada en vez de tener las dimensiones como entradas a parte
I
I
I
Código más compacto
Más facil de seguir y verificar
Ofrece más seguridad (menos riesgo de inconsistencias de tamaños por errores de
redacción o confusiones)
Se puede considerar estructuras especiales para las filas también, que contienen el
tamaño y el puntero para el arreglo
I
Puede facilitar operaciones de filas
Desventaja: hay que manejar la estructuras para seguir el código
N. Thériault (USACH)
Presentación
17 / 28
Funciones en C
Incluyen los tipos (estructura) de cada variable
void Invertir Matriz( matriz A , matriz *B ); // B = A∧(-1)
I
void: sin entrada o salida (según donde se escribe)
Dos funciones no pueden tener el mismo nombre, aun si el tipo de variables cambia
int maximo int( int a , int b );
float maximo float( float a , float b );
El compilador puede permitir algunos ajustes de tipos, pero no es recomendable
I
Ejemplo: si se manda un int de entrada cuando se pide un long
Se debe incluir las declaraciones de funciones antes de utilizarlas
I
Esencial para funciones recursivas
Funciones con resultado único
I
I
Utilizar return(variable), lo que termina la ejecución de la función.
Utilizar puntero como entrada (escribir el resultado en la dirección asociada).
Funciones con varios resultados
I
I
I
Utilizar punteros como entradas (escribir los resultados en las direcciones asociadas).
Utilizar un arreglo (si todos los resultados son del mismo tipo).
Utilizar una estructura especial.
N. Thériault (USACH)
Presentación
18 / 28
Algunas funciones y constantes comunes en C
clock(): tiempo del procesador asociado al programa, en tipo de variable “clock t”
CLOCKS PER SEC: constante (de tipo clock t) para convertir a segundos
rand(): generador aleatorio, devuelve enteros entre 0 y RAND MAX
srand(semilla): inicializa la “semilla” del generador aleatorio
arc4random(): generador aleatorio con mejores propiedades, devuelve enteros entre
0 y UINT32 MAX. No disponible en versiones más antiguas de C
abs(int): valor absoluto; labs(long), fabs(float), fabsf(double), fabsl(long double)
log2(float): logaritmo en base 2; log2f(double), log2l(long double)
ceil(valor): parte entera (redondeada hacı́a arriba)
floor(valor): parte entera (redondeada hacı́a abajo)
NULL: puntero “vacio”
N. Thériault (USACH)
Presentación
19 / 28
Algunos aspectos a considerar al programar
Orden de las operaciones
Operadores lógicos y acciones condicionales
Bucles while y for
Matrices y arreglos dobles
Sub-funciones e inlining
Funciones recursivas
Testeo de programas
N. Thériault (USACH)
Presentación
20 / 28
Orden de las operaciones
Siempre verificar si el orden de las operaciones importan:
I
Para sumas y productos de enteros: conmutativos y asociativos (el orden no importa)
I
Para sumas de matrices: conmutativas y asociativas
I
Para productos de matrices: asociativos pero no conmutativos
I
Para productos de floats: conmutativos y asociativos
I
Para sumas de floats: conmutativas pero no asociativos
A · (B · C ) = (A · B) · C
F
A · B 6= B · A
Problemas de redondeos (estudio de análisis númerico, etc)
−70
20 + 3 · 10
−70
3 · 10
−70
+ . . . + 3 · 10
−70
+ . . . + 3 · 10
= 20
+ 20 = 20.0000000006
Algunas funciones conmutativas pueden no considerar ambas entradas como
equivalentes:
I
“acumular” (suma que pone el resultado en un espacio de entrada)
podrı́a ser permitido para a ← a + b pero no para b ← a + b
I
Se recomienda utilizar const en la definición de la función si se considera que el bloque
asociado al puntero de entrada no puede ser afectado (seguridad)
N. Thériault (USACH)
Presentación
21 / 28
Operadores lógicos y acciones condicionales
Comparaciones:
I
I
I
a == b (¿a es igual a b?),
a < b (¿a es menor que b?),
a > b (¿a es mayor que b?),
a! = b (¿a es distinto de b?),
a <= b (¿a es menor o igual que b?),
a >= b (¿a es mayor o igual que b?),
Operadores lógicos:
I
I
I
!P (negación lógica)
P&&Q (P y Q)
PkQ (P o Q)
Acciones condicionales: if e if-else
I
Formato básico:
if (condicones)
{
acciones A;
}
else
{
acciones B;
}
I
Cuidado: un “;” después de la condición (antes de los “{” y“}”) es equivalente a
poner una secuencia de acciones vacı́a.
N. Thériault (USACH)
Presentación
22 / 28
Bucles while y for
Bucles while:
I
Formato básico:
inicialización;
while (condicones)
{
acciones;
}
I
I
Más flexibles, no es necesario saber cuantas iteraciones se harán
Si hay incrementos, se pueden hacer antes o después de las acciones
Bucles for:
I
Formato básico:
for ( inicialización ; condiciones ; incremento )
{
acciones;
}
I
I
I
Más facı́l de seguir para entender el programa y verificar su validez
Se sabe cuantas iteraciones hay
Siempre hay un incremento, después de las acciones
N. Thériault (USACH)
Presentación
23 / 28
Sub-funciones e inlining
En programas más complejos (de escribir), se puede utilizar sub-funciones o inlining:
Crear sub-funciones:
I
I
I
Más sencillo de seguir el código
Más sencillo para la verificación y el testeo
El llamado a funciones aumenta un poco el tiempo de ejecución
Inlining manual:
I
I
I
I
Las operaciones están detalladas a cada utilización
Evita llamados a funciones (más eficiente) y “saltos” dentro del programa
Aumenta en tamaño del código redactado
Requiere cuidado con los nombres de variables, pero puede reutilizar las mismas
variables temporarias
Inlining automatico:
I
I
I
I
Recomienda al compilador de hacer el reemplazo automáticamente
Se agrega “inline” o “static inline” antes dedefinir/declarar la función
Más seguro (evita errores de nombres de variables)
Poco control sobre el aumento de tamaño del programa
Lo ideal es llegar a un equilibrio entre sub-funciones y inlining: crear funciones
cuando se necesitan, sin sub-dividir demasiado.
I
Se recomienda empezar con sub-funciones para el testeo inicial
N. Thériault (USACH)
Presentación
24 / 28
Funciones recursivas
Una función recursiva es una función que se llama a si misma (una o más veces).
Se debe declarar la función primero (sin excepción)
No se puede utilizar “inline”
Puede ser una herramienta esencial para obtener mejor eficiencia
I
I
Formato natural para algoritmos Reducir-y-Conquistar y Dividir-y-Conquistar
Común en algoritmos de Progración dinámica
Se deberı́a pensar como si se trataba de una demostración por inducción
Debe incluir unas condiciones para evitar recursiones infinitas
I
I
I
I
Una función recursive siempre tendrá a lo menos un “if” para elegir entre el caso base
(sin recursión) y el caso recursivo
Los emphcasos recursivos corresponden al paso inductivo en la demostración por
inducción
Los emphcasos bases corresponden a la eituación bases en la demostración por
inducción (donde se inicia la inducción)
Se llama a la función misma solamente si se cumple la hipótesis de inducción
N. Thériault (USACH)
Presentación
25 / 28
Testeo de programas
Aunque un algoritmo puede ser bien desarrollado (preparado), con un pseudo-código
claro y verificado, varias cosas pueden salir mal al programarlo:
Errores de escritura
Errores de signos
Problemas con el manejo de memorı́a dinámica
Conflictos con sub-funciones y/o funciones provenientes de otras librerı́as
Interpretación equivocada de de los indices, etc
Detalles no considerados en el pseudo-código
En algunos casos, la complejidad conceptual (dificultad de entender todos los detalles del
programa y tenerlos todos en mente al mismo tiempo), especialmente con programas
desarrollados en equipos de trabajo grandes, puede ser muy dificil de asegurar que todo
está bien con el programa.
Por eso, es importante testear un programa antes de hacerlo oficial.
N. Thériault (USACH)
Presentación
26 / 28
Consejos para el testeo
Empezar con ejemplos pequeños primero:
I
I
Más rápido
Idealmente comparar con valores calculados a mano o obtenidos por otros algoritmos
(previamente testeados)
Testear las (sub-)funciones una por una antes de testearlas juntas
I
I
I
Idealmente testear cada función justo después de programarla y antes de empezar a
trabajar la próxima
Juntar las funciones de a poco
Si se detectan errores pero no se sabe donde, mostrar resultados parciales
Antes de testear una función recursiva de forma general, se recomiendo testear una
versión que hace a lo más un paso de recursión.
I
I
I
I
I
Utilizar una versión de la función que parra después de aplicar el proceso de inducci’on
una sola vez
Comparar con un algoritmo más básico (e.g. fuerza bruta)
Puede ayudar a optimizar la función también (determinar cual es el caso base práctico)
Evita entrar en bucles infinitos por errores en en proceso de los pasos
Si hay un(os) error(es), es más sencillo identificar donde antes de que las recursiones
hagan una difusión del error
N. Thériault (USACH)
Presentación
27 / 28
Mediciones de tiempo
Utilizar clock para medir tiempo (tiempo cpu), no time (tiempo reloj).
Según la instalación de C, podrı́an haber otras funciones de medición del tiempo cpu
(más precisas), pero no siempre están disponibles. Solo utilizar clock.
Hay un nivel mı́nimo de precisión de la medición, no considerar debajo de µs.
Para cálculos debajo del minuto, en general el proceso puede compartir el
procesador, lo que produce ruido en la medición del tiempo cpu.
Para mediciones de mayor precisión, se recomienda repetir el proceso hasta que
requiere a lo mı́nimo un minuto, medir el tiempo total y dividir por la cantidad de
repeticiones.
I
Ejemplo: 10, 100, 1000, 104 , ... repeticiones.
Idealmente repetir con entradas (aleatorias) distintas, pero generar las entradas
aleatorias ANTES de empezar a medir el tiempo.
I
I
Si el algoritmo requiere dos (o más) entradas, preparar un conjunto de valores para
cada una y ejecutar para las diferentes combinaciones (requiere menos tiempo y
memoria de preparación).
Asegurar que las entradas preparadas no agotan el RAM.
N. Thériault (USACH)
Presentación
28 / 28
Descargar