sábado, 17 de septiembre de 2016

Mostrar un menú de sistema

Después de unas largas "vacaciones" vuelvo recargada de energía para empezar un nuevo curso, estos últimos meses he podido empezar mi web de docente que aún está en construcción, la voy avanzando poco a poco y ya os daré el enlace cuando esté terminada.

Mientras tanto quiero compartir que hoy empecé con la primera alumna en este curso 2016-2017 y vimos un problema que se repite mucho, sobretodo en la facultad de Gestión Aeronáutica de la UAB (que últimamente son con los que más trabajo), se trata de mostrar un menú de sistema con distintas opciones:

1. Empezar reserva
2. Modificar reserva
3. Facturar reserva
4. Cancelar reserva
5. Salir

El menú se debe mostrar, realizar las acciones que el usuario seleccione y al terminar de ejecutar cada acción volver a mostrar el menú hasta que el usuario marque salir.

El objetivo de este ejercicio es:

- Reconocer que es necesario el uso de un bloque de instrucciones iterativas (while/for/do-while) para mostrar el menú hasta que el usuario decida salir
- Reconocer que es más práctico utilizar el bloque de instrucciones switch en lugar de if's anidados

A continuación muestro el código comentado en C++

int main(int argc, char** argv) {
//define una variable que guarde la opción seleccionada por el usuario
int opcion =0;
//escogemos do-while para asegurar que el menú se muestre al menos la primera vez
do 
{
                //imprimimos las opciones disponibles del menú
printf (" 1. Hacer reserva\n 2.Modificar reserva\n 3.Facturar reserva\n 4.Cancelar reserva\n 5.Salir\n");

                //leer de teclado la opción seleccionada por el usuario
printf ("Seleccione una opcion\n");
scanf ("%d",&opcion);

                //utiliza el bloque switch para decidir que acción realiza según la opción seleccionada
switch (opcion) 
{
case 1: printf ("Haciendo reserva\n");
break;
case 2: printf ("Modificando reserva\n");
break;
case 3: printf ("Facturando reserva\n");
break;
case 4: printf ("Cancelar reserva\n");
break;
case 5: printf ("Salir\n");
break;

default: printf ("Opcion incorrecta\n");
break;
}
}
while (opcion !=5);//en caso que seleccione la opción 5 termina de ejecutar la iteración

return 0;
}

A continuación muestro unas capturas de pantalla de la ejecución:









domingo, 3 de julio de 2016

Cursores Programación SQL

Para variar un poco, veremos algo de SQL, en este caso serán los cursores.

¿Qué es un cursor?
De forma sencilla podemos decir que es una especie de tipo de variable que se genera a partir de una select y cuyos datos son dinámicos, dependerán de los parámetros que se le pase.
Los cursores son similares a las estructuras, son tipos de datos que podemos crearlos, pero si no los usamos no servirán de nada.

Un ejemplo de declaración de un cursor es:
DECLARE MiCursor CURSOR  
    FOR SELECT campo2 FROM miTabla WHERE campo1 = @valorCampo1 

MiCursor: Es el nombre del nuevo tipo de datos
miTabla: es el nombre de la tabla que estamos consultando
campo1 y campo 2: son campos de la tabla miTabla
@valorCampo1: es el parámetro que se le pasa al cursos para poder obtener la información de forma dinámica

Si ningún programa utiliza el cursor MiCursor que hemos creado entonces será un tipo de datos inservible.
Para utilizarlo debemos recordar la lectura de ficheros, era como la lectura de un vinilo, el cursor es de la misma forma, el resultado de la select es un conjunto de registros en donde cada registro será similar a una linea leída en el fichero. De la misma forma forma como hacemos con un fichero, un cursor debe abrirse y cerrarse, para leer el registro primero nos posicionamos en él mediante la instrucción FETCH, luego de posicionarnos podemos leerlo.
El algoritmo sería el siguiente:
1. Inicializamos los parametros que necesite el cursor
2. Abrimos el cursor
3. Nos posicionamos en el primer registro
4. Leemos registro a registro hasta que lleguemos al último, en este caso el FETCH devolverá un estado de finalización.
5. Cerramos el cursor

Aplicamos el algoritmo al ejemplo:
@valorCampo1 = 1 --inicializamos el parámetro 
OPEN MiCursor --abrimos el cursor
FETCH NEXT FROM MiCursor INTO @registro --se posiciona en el primer registro, la variable @registro debe tener el mismo formato que campo2
WHILE @@FETCH_STATUS = 0  --lee hasta encontrar el estado de finalización
    BEGIN
        FETCH NEXT FROM MiCursor INTO @registro --lectura de siguiente registro
    END
CLOSE MiCursor --cierre de cursor

Para tener claro el funcionamiento de cursores es necesario tener claro el concepto de estructuras, de lectura de ficheros, y por supuesto de sentencias SQL y procedimientos.

martes, 24 de mayo de 2016

Ordenamiento de una lista simplemente enlazada

El ordenamiento de una lista simplemente enlazada es similar al ordenamiento de vectores con la gran diferencia de que en una lista simplemente enlazada no disponemos del concepto del “índice” que nos ayuda a acceder directamente a un elemento de la lista, en este caso para acceder a un elemento debemos recorrer desde el inicio hasta hallar el elemento que nos interese.

Un algoritmo sencillo es el del Intercambio, consiste en lo siguiente:

1. Situar un puntero al inicio de la lista que nos irá indicando a medida que vayamos ordenando, cuánto de la lista ya está ordenada, es la variable más importante del algoritmo porque nos servirá para saber que hemos acabado de ordenar la lista.

Elemento * elemBase = elemInicio;
while(elemBase != NULL)
{
//en este proceso vamos ordenando la lista a partir del nodo elemBase hasta el final
elemBase = elemBase->next;
}

2. En el caso que el ordenamiento sea de menor a mayor, necesitamos encontrar el menor de los elementos a partir del elemento base hasta el final de la lista.
En el caso del ordenamiento de Vectores obteníamos la posición, en este caso obtendremos un puntero al menor elemento.

Elemento* buscarMenor(Elemento* ptrInicial)

Esta función tendría que utilizarse en cada iteración

Elemento * elemBase = elemInicio;
Elemento* elemMenor; //puntero que nos sirve para apuntar al menor elemento encontrado
while(elemBase != NULL)
{
elemMenor = buscarMenor(elemBase);
elemBase = elemBase->next;
}

3. Intercambiar el elemento menor con el elemento que se encuentra apuntando el puntero base. Para hacer este intercambio es necesario que el elemento anterior al elemento base apunte al elemento menor, para eso necesitamos un puntero que apunte al anterior del elemento base.

Como es una lista simplemente enlazada, no podemos retroceder para hallar al puntero anterior al puntero base, por tanto, crearemos una función que nos sirva para ubicarnos en el elemento anterior que queramos. Esta función es necesaria para luego poder hacer el intercambio de nodos.

Elemento * getElemAnterior(Elemento* elemInicio, Elemento* elem);

Utilizaremos esta función para obtener el elemento anterior al base y al menor y de esta forma poder realizar el intercambio

Elemento * elemAnteBase = NULL;//puntero que apunta al anterior de elemento base
Elemento * elemBase = elemInicio;
Elemento* elemAnteMenor; //puntero que apunta al anterior del elemento menor
Elemento* elemMenor; //puntero que nos sirve para apuntar al menor elemento encontrado
while(elemBase != NULL)
{
elemMenor = buscarMenor(elemBase);
elemAnteBase = getElemAnterior(elemInicio, elemBase);
elemAnteMenor = getElemAnterior(elemInicio, elemMenor);
elemBase = elemBase->next;
}

Teniendo el puntero anterior al base ya podemos hacer el intercambio:

void intercambio(Elemento* elemAnteBase, Elemento* elemBase,
Elemento* elemAnteMenor, Elemento* elemMenor);

Incorporamos el intercambio en cada iteración y asignamos el nodo menor como el nuevo elemento base:

Elemento * elemAnteBase = NULL;//puntero que apunta al anterior de elemento base
Elemento * elemBase = elemInicio;
Elemento* elemAnteMenor; //puntero que apunta al anterior del elemento menor
Elemento* elemMenor; //puntero que nos sirve para apuntar al menor elemento encontrado
while(elemBase != NULL)
{
elemMenor = buscarMenor(elemBase);
elemAnteBase = getElemAnterior(elemInicio, elemBase);
elemAnteMenor = getElemAnterior(elemInicio, elemMenor);
intercambio(elemAnteBase, elemBase, elemAnteMenor, elemMenor);
elemBase = elemMenor;
elemBase = elemBase->next;
}

4. Finalmente desarrollamos las funciones:

Elemento* buscarMenor(Elemento* ptrInicial)
{
Elemento* elemMenor = ptrInicial;
Elemento* ptr = ptrInicial;//puntero que sirve para recorrer la lista
int menorValor = elemMenor.valor;//inicializamos el primer valor menor
//recorremos toda la lista y dejamos el puntero elemMenor en el de menor valor
while(ptr!=NULL)
{
if(ptr.valor < menorValor)
{
elemMenor = ptr;
menorValor = ptr.valor;
}
ptr = ptr->next;
}
return elemMenor;
}

void intercambio(Elemento* elemAnteBase, Elemento* elemBase,
Elemento* elemAnteMenor, Elemento* elemMenor)
{
Elemento* aux = elemBase->next; //nos sirve para hacer el intercambio sin perder punteros
elemBase->next = elemMenor->next;
elemMenor->next = aux;
elemAnteBase->next = elemMenor;
elemAnteMenor->next = elemBase;
}

Elemento * getElemAnterior(Elemento* elemInicio, Elemento* elem)
{
Elemento*anterior = NULL;
Elemento*ptr = elemInicio;
int finBusqueda = 0;
while(ptr!=NULL && ptr->next!=NULL && !finBusqueda)
{
if(ptr->next.valor == elem.valor)
{
finBusqueda = 1;
}
anterior = ptr;
ptr = ptr->next;
}
return anterior;


}




lunes, 28 de marzo de 2016

Lectura de fichero de texto

En este post veremos los pasos para realizar la lectura de un fichero de texto, para esto es necesario saber que es similar a escuchar música en un vinilo, se deben realizar  pasos:

1- Poner el disco y la aguja al inicio del disco.
2- Escuchar la música.
3- Levantar la aguja cuando ya no se quiera escuchar o esperar a que termine de reproducirlo todo.

La aguja en el disco viene a representar un puntero de tipo FILE* que se prepara al inicio del fichero, avanza con cada caracter que lee y finalmente debemos cerrarlo.

Fichero que leeremos:


Primer Paso: Abrir Fichero
FILE* abrirFichero () {
      FILE* fptr = NULL; //puntero que abre al inicio del fichero y sirve para recorrerlo
      fptr = fopen ("fichero_prueba.txt", "rt"); //apertura de fichero con permiso de lectura
      return fptr;
}

Invocación de la función abrirFichero:
FILE* ptrFichero; //declaramos una variable de tipo puntero
ptrFichero = abrirFichero (); //invocamos a la función y guardamos el resultado en la variable puntero

Segundo Paso: Leer Fichero
Esta función leerá el fichero hasta el final

void leerFichero(FILE* fptr )
{
  char palabra1 [BUFSIZ];
  char palabra2 [BUFSIZ];
  int numero;
  //La función eof = End Of File devuelve true cuando el puntero llega al final del fichero
  while(!feof(fptr))
  {
      //leemos palabra a palabra (se sabe previamente el formato del fichero)
      //sabemos que leemos 2 palabras y un número por registro
      //cada iteración del while leerá un registro
      fscanf (fptr, "%s", palabra1);
      fscanf (fptr, "%s", palabra2);
      fscanf (fptr, "%i",&numero);
      //imprimimos por pantalla lo que hemos leído
      printf("palabra 1: %s, palabra 2: %s, numero: %i \n", palabra1, palabra2, numero);
   }
}

Invocación de la función leerFichero:
Sólo puede realizarse si el fichero se llego a abrir (ptrFichero != NULL)
FILE* ptrFichero;
ptrFichero = abrirFichero ();
if(ptrFichero != NULL)
{
  leerFichero(ptrFichero);
}

Tercer Paso: Cerrar Fichero
void cerrarFichero (FILE* fptr) {
     fclose(fptr);
}

Invocación de la función leerFichero:
Sólo puede realizarse si el fichero se llego a abrir (ptrFichero != NULL)
FILE* ptrFichero;
ptrFichero = abrirFichero ();
if(ptrFichero != NULL)
{
  leerFichero(ptrFichero);
  cerrarFichero(ptrFichero);
}

El resultado de la ejecución es el siguiente:


domingo, 7 de febrero de 2016

Definir e Inicializar una variable

En esta entrada explicaré la diferencia entre Definir e Inicializar una variable, mucha veces se confunden los conceptos porque ambos se pueden realizar en una misma instrucción:

Ejemplo:
int a = 0; 

Aprendemos a crear variables de forma repetitiva y a veces es necesario saber la diferencia entre ambos conceptos para lograr una programación más limpia (no crear variables en exceso pudiendo reutilizarlas).

Definir una variable
Esto es reservar un espacio en la memoria para almacenar valores de un determinado tipo. Para definir una variable sólo es necesario saber el tipo de la variable y el nombre que queremos asignar.

Ejemplo:
int a; //int es el tipo y "a" es el nombre que he asignado a la variable.

Cuando realizamos esta instrucción, lo que hace el programa es reservar un espacio de memoria para la variable "a" del tamaño que marca el tipo (el tipo int puede ser de 2 bytes o 4 bytes dependiendo del procesador).

La variable "a" tiene un espacio para guardar valores, pero aún no hemos guardado nada en ese espacio.
Podría ser que un anterior programa que utilizara ese mismo espacio de memoria, almacenara algún dato y luego al terminar de ejecutar dejara en ese espacio un valor, es importante que cada programe limpie los espacios reservados para sus variables.

Inicializar una variable
Esto es limpiar los espacios asignados a las variables en la definición para que queden preparados para trabajar con ellos. Para inicializar una variable utilizamos la instrucción de asignación, asignando un valor fijo o el valor de otra variable.

Ejemplo:
a = 0; //inicializamos la variable "a" con un valor fijo
int b; //definimos la variable "b"
b = a; //inicializamos la variable "b" con el valor de la variable "a"

Sabiendo la diferencia entre Definir e Inicializar una variable, ahora si podemos simplificar ambas instrucciones en una sola:

Ejemplo:
int a; //Definir variable "a"
a = 0; //Inicializar variable "a"
int b = a; //Definir e Inicializar la variable "b" en una sola instrucción

¿Cómo podemos reutilizar una variable sabiendo la diferencia entre Definir e Inicializar?
Un ejemplo sencillo es imprimir un sucesión de números de forma creciente y de forma decreciente:
//Definimos una variable
int numero;
//inicializamos el valor de la variable leyendo el valor desde consola
scanf("%d",&numero);
//impresión decreciente
for(int i=numero; i>0;i--) printf("%d",i);
//impresión creciente
for(int i=0; i<=numero;i++) printf("%d",i);

La variable "i" definida en el primer for no es la misma variable definida en el segundo for, cada una existe sólo en el ámbito de cada for, pero podríamos hacer que sea una única variable y reutilizar en ambos bloques de instrucciones:
//Definimos una variable
int numero;
//inicializamos el valor de la variable leyendo el valor desde consola
scanf("%d",&numero);
//definimos e inicializamos la variable "i"
int i = numero;
//impresión decreciente: no hace falta ni definir ni inicializar nuevamente la variable "i"
for(; i>0;i--) printf("%d",i);
//impresión creciente: no hace falta ni definir ni inicializar nuevamente, el for anterior dejó la 
//variable "i" en 0
for(; i<=numero;i++) printf("%d",i);