Mostrando entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta programación. Mostrar todas las entradas

lunes, 14 de diciembre de 2020

Problema de los rombos

El siguiente problema lo he venido encontrado a menudo en diferentes clases, el objetivo es el correcto manejo de la sentencia FOR y el printf.

Lo que busca es ingresar el valor de un número que representa el lado de un rombo e imprimir en pantalla el rombo. un ejemplo de ejecución:


Lo he resuelto utilizando un función para imprimir cada línea, de esta forma el programa me parece más ordenado.

Os dejo el código:

#include <iostream>

void imprimirFila(int fila, int lado)

{

//calculo de fila en el caso de estar en el triangulo inferior

if(fila>lado) fila = (lado * 2) - fila;

//calcular el número de blancos a imprimir

int blancos = (lado * 2) - fila;

//calcular el número de caracteres a imprimir

int impreso = (fila * 2) - 1;

//impresion de blancos

for(int i=1; i<=blancos; i++) printf(" ");

//impresion de caracteres

for(int i=1; i<=impreso; i++) printf("X");

//imprime salto de linea

printf("\n");

}


int main(int argc, char** argv) {

//ingresa el valor del rombo

int lado = 0;

printf("ingrese el lado del rombo:");

scanf("%d", &lado);

printf("el valor del lado ingresado es: %d\n", lado);

//calculamos el número de lineas a imprimir

int lineas = (lado * 2) - 1;

//un for para imprimir cada linea

for(int fila = 1; fila<=lineas; fila++)

{

imprimirFila(fila, lado);

}

return 0;

}



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.

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);










domingo, 25 de octubre de 2015

Operación Residuo (MOD)

El set de instrucciones de cada lenguaje de programación puede llegar a ser tan amplio que no llegamos a aprenderlo todo y mucho menos saber en qué casos podemos sacar ventaja de utilizar una instrucción determinada.
En este post quiero hablar de la instrucción de Residuo, conocida como % en C, C++, php, java o MOD en COBOL, PLSQL, Pascal.

Recordando un poco de matemáticas, sabemos que:
Dividendo = Cociente * Divisor + Residuo
Ejm. 7 = 3 * 2 + 1
Si despejamos el residuo sería:
Residuo = Dividendo - (Cociente * Divisor)

La operación Residuo se calcula utilizando el Dividendo y el cociente o el dividendo y el divisor. La sintaxis sería:
Residuo = Dividendo % Cociente (1 = 7 % 3)
Residuo = Dividendo % Divisor (1 = 7 % 2)

¿En qué casos podemos utilizar esta operación?
Sólo os daré dos casos típicos:

Realizar una acción cuando un número es múltiplo de otro
Imprimir los 30 primeros múltiplos de 3
int numerosImpresos = 0 //controla el número de números impresos
int contador = 1; //Es el número que hace de Dividendo en la operación
while(numerosImpresos < 30)
{
  if (contador % 3 == 0) //si el residuo es 0, es una división exacta por tanto es múltiplo
  {
    Console.write(contador);  
  }
  contador ++;
}

Realizar un cambio de base
Cambiar el número 5 de base decimal a binario.
Como se cambia a sistema binario de utiliza el divisor = 2 (Base N, implica divisor N)

int dividendo = 5;
int divisor = 2;
string cambioBase = “”;

while(dividendo / divisor >1)
{
  residuo = dividendo % divisor;
  dividendo = dividendo / divisor;
  cambioBase = residuo + cambioBase; //se va concatenando a la izquierda
}
cambioBase = dividendo + cambioBase; //en esta variable está el resultado "101"



¿Se os ocurre otro ejemplo?






jueves, 2 de octubre de 2014

Árboles binarios, un ejemplo práctico

Muchas veces nos es difícil entender para qué nos enseñan algo sin explicarnos la aplicación. El tiempo que llevo dando clases particulares he podido ver apuntes con mucho contenido y con pocos ejemplos prácticos, esto desmotiva mucho porque el estudiante piensa ¿Para qué me enseñan esto?.

Esto pasa generalmente con el tema de árboles, te explican los tipos de árboles existen (binarios, múltiples, balanceados, etc) y cómo recorrerlos (pre-orden, post-orden, etc), sin embargo son muy pocos los ejemplos prácticos que he podido encontrar.

El objetivo de este post es mostrar un ejemplo práctico en que se podría hacer uso de los árboles, aunque también se puede hacer uso de otras soluciones.

Tenemos un fichero de texto que contiene en cada linea la edad de personas encuestadas para un estudio de mercado, lo que nos interesa es leer el fichero y mostrar en pantalla la cantidad de repeticiones por cada edad, es decir la edad que más se repiten en la muestra.

Partimos de la premisa que la información no está ordenada y es una muestra de 1000 personas.

Utilizaremos para cada número una estructura (o clase) que almacene el valor de la edad y el valor de la repetición:

public struct elemMuestra
{
public int edad;
public int frecuencia;
}

El algoritmo que podríamos plantear es el siguiente:

1. Leer número del fichero
2. Buscar el número en la estructura ordenada
2.1. Si el número existe en la estructura, incrementar el número de repeticiones
2.1. Si el número NO existe en la estructura, inserto el número en la estructura
3. Cuando termino de leer todos los números del fichero, imprimo cada número con su frecuencia de repetición guardada en la estructura.

En el punto 2 vemos que utilizamos una estructura ordenada, aquí podríamos utilizar un vector, una lista de punteros, un árbol binario,etc. lo importante es que la estructura que definamos nos sea útil tanto para buscar si el número ya existe en la estructura, como para insertar un nuevo número en la estructura.

Si utilizamos un vector, podríamos realizar una búsqueda binaria, debido a que los índices del vector ayudan a este tipo de búsqueda, sin embargo si utilizamos una lista de punteros o un árbol binario tendríamos que usar una búsqueda secuencial, pero sin duda el recorrido en un árbol podría ser mucho más eficiente que el recorrido en una lista de punteros, debido a que la forma del árbol no requiere recorrer a todos sus elementos sino que al tener un orden binario es como si realmente se hiciera una búsqueda binaria, a la búsqueda en un árbol binario se le llama búsqueda Silaina.

El otro punto a evaluar es el tema de la inserción, cuando ya sabemos que el número que hemos leído de fichero aún no existe en la estructura que hemos escogido, utilizar un vector podría ser lo menos eficiente, puesto que si tenemos ordenados 900 números en 20 edades distintas, y llega una nueva edad que es la menor de todas, debemos desplazar todas las edades a una casilla posterior para insertar la nueva edad de forma ordenada, ese desplazamiento puede ser muy costoso. En una lista de punteros la inserción es relativamente sencilla y en un árbol también.

A nivel de programación los árboles son más fáciles de trabajar con funciones recursivas, por tanto si tenéis poca experiencia en funciones recursivas y la cantidad de elementos de la muestra no es tan grande, puede ser más sencillo de programar con vectores + búsqueda binaria o secuencial, sin embargo si veis que la muestra es muy grande y luego vais a realizar muchas búsquedas sobre la estructura cargada y ordenada es mejor trabajarla con árboles binarios + búsqueda sobre árboles binarios.

Ahora bien, para que la búsqueda en un árbol binario sea aun más eficiente deberíamos lograr que el árbol tenga el menor numero de niveles, para esto se realiza lo que se llama el balanceo de árboles:



domingo, 14 de septiembre de 2014

La importancia de pensar

Hace un tiempo hice un ejercicio de memoria para recordar exactamente qué fue lo que me motivó a ser Ing. Informática, la respuesta está en mi colegio, cuando tenia 8 años se introdujo una nueva asignatura que se llamaba "Computación", no era la primera vez que veía un ordenador (ya tenía uno en casa que sólo usaba para jugar), pero era la primera vez que aprendía a usar un ordenador para resolver problemas simples de matemáticas.

El lenguaje que utilizábamos era el Pascal, la clase consistía en leer atentamente un problema y luego dibujar el diagrama de flujo para resolverlo, el profesor pasaba por cada uno de nuestros sitios según íbamos terminando y revisado el diagrama podíamos empezar a programar, nunca antes!! porque lo importante era que se validara que hubiéramos entendido lo que se pedía en el problema y que tengamos claro cómo lo íbamos a enfrentar. Lo más importante de esta clase era la metodología para solucionar los problemas, pensar y luego programar! tuve la suerte de caer en esa clase, fue la base fundamental para decantarme por la rama tecnológica.

Ayer encontré un artículo muy interesante del Blog Tiching en el que plantea los beneficios de que los niños aprendan a programar no solo para fomentar una orientación científica-tecnológica sino para que se acostumbren a estructurar el planteamiento de cualquier tipo de problema. Os recomiendo mucho leer el post, los beneficios de programar con metodología no sólo sirven en un campo puramente técnico sino que sirven para ejercitar y acostumbrarnos a entender los objetivos, plantear estrategias y ejecutar acciones para lograr a cumplir nuestros objetivos.

Cada curso conozco alumnos diferentes, y a pesar de ser de distintos años de carrera, distintas edades e incluso diferentes carreras, todos creen que terminaremos la clase con un programa hecho, en realidad prácticamente no utilizamos el ordenador, lo que se llevan a casa son muchos papeles llenos de pseudocódigo y con las ideas muy claras: Objetivo y Estrategia, esa es la finalidad de la clase, teniendo estas dos cosas, la resolución sale sola.

El próximo post será más técnico sólo quería hacer esta "pausa" por que es parte del aprendizaje entender que antes de programar se debe pensar.


domingo, 23 de marzo de 2014

Recursividad

Se dice que una función es recursiva cuando su ecuación de solución se utiliza a si misma para resolverse.

Un ejemplo típico de recursividad es la función Factorial, sabemos que el Factorial de un numero es igual al producto de dicho número multiplicado por el factorial del número anterior:

Factorial(4) = 4 * Factorial(3)

Vemos que está definiendo la solución del factorial empleando la misma fórmula del factorial, puesto que:

Factorial(3) = 3 * Factorial(2)
Factorial(2) = 2 * Factorial(1)
....

Pero, ¿Cuándo termina la recursividad? Es absolutamente necesario definir lo que se llama un TOPE, que es cuando la recursividad termina, sino seguiríamos hasta el infinito.

Para el caso del ejemplo típico del factorial, se sabe que esta función se aplica a un conjunto de números enteros mayores o iguales a 0, del cual Factorial(0) = 1, siendo esta fórmula la única que no utiliza la recursividad para su definición, por tanto:

Toda función recursiva necesita un TOPE, dicho tope es una de la soluciones de la función que no se define por si misma.

Si generalizamos podemos decir que, Para toda N tal que N>=0, Factorial(N) se resuelve con las siguientes ecuaciones:

Si N=0, Factorial(N) = 1
Si N>0, Factorial(N) = N * Factorial(N-1)
Para cualquier otro valor de N, No existe conjunto solución para Factorial(N)

Otra característica importante de una función recursiva es que al llamarse a si misma los parámetros deben variar, sino entrará en bucle infinito, por ejemplo esto sería un error:

Factorial(N) = N * Factorial(N)     ---> En este caso caerá en bucle infinito porque NUNCA llegará al TOPE

Por tanto:

En toda función recursiva es necesario que la llamada si misma garantice que en algún punto llegará al TOPE. 

//ejemplo de función recursiva
//asumimos que el valor N que ingresa es >=0
int factorial (int N)
{
    //lo primero que definimos en una función recursiva el el TOPE
    if(N == 0) return 1;
    //La llamada recursiva con N-1 asegura que en algún momento llegará a ejecutar factorial(0)
    return N * Factorial(N-1);
}


¿Por qué es importante definir el tope al principio?
¿Podríamos utilizar la operación unaria -- para llamar de forma recursiva: Factorial(N--)?










domingo, 9 de febrero de 2014

Inserción de valores en un vector ordenado

En esta ocasión trabajaremos una estructura fija (vector) como si fuese una estructura variable, esta solución se puede aplicar en escenarios en los cuales sabemos que:

- Como máximo utilizaremos un número determinado de elementos.
- La inicialización (inserción) se realiza una vez o con muy poca frecuencia,
- Y se necesita acceder continuamente a elementos en concreto.

Por ejemplo, sabemos que en un aula no se puede exceder de los 100 alumnos, la matrícula se realiza al principio de curso y durante el curso se puede acceder en cualquier momento al expediente de cualquiera de los alumnos matriculados.
En un aula podrían inscribirse 25 alumnos y en otra 82 alumnos, si se quisiera imprimir un listado de los alumnos matriculados en cada aula no es necesario recorrer el vector de 100 elementos, sino que en el primero se recorrerá 25 veces y en el segundo 82.

Para hacer uso de los vectores de esta forma utilizaremos una variable auxiliar que representará el número de elementos utilizados, así cada vez que necesitemos hacer una operación sobre todos los elementos no recorreremos todos los elementos del vector sino todos los utilizados.

int listaOrdenada[N];
int numelem = 0; //cantidad de elementos ocupados de la lista

Ahora complicaremos un poco el problema y suponemos que los alumnos cuando se matricula no vienen de forma ordenada, pero se requiere almacenarlos de forma ordenada por apellido, esto implica que cada vez que se agrega un alumno a la lista de matriculados se debe buscar la posición del vector en la cual se debe insertar, no siempre será el último.

Este tipo de problema utiliza varios de los conceptos vistos en entradas anteriores, tales como:

- El uso de vectores para almacenar información del mismo tipo.
- El uso de vectores como estructura que ocupa un espacio de memoria fija de la cuál sólo utilizaremos una parte de forma dinámica.
- El uso de los iteradores for y while para realizar una acción sobre cada elemento de un vector.
- Recorridos de vectores en forma ascendente y en forma descendente
- El uso de comprador if para determinar la posición en la cual se debe insertar el nuevo elemento.

Para simplificar el problema supondremos que los alumnos son números enteros y sus apellidos son números mayores que cero y os invito a que modifiquéis el código para adaptarlo a un vector de una estructura de alumnos que incluya los nombres y los apellidos.

Os dejo el código a continuación en C:

#include <stdio.h>
#include <stdlib.h>
#define N 100


int main(int argc, char *argv[])
{
  //definición de variables
  int listaOrdenada[N];
  int numelem = 0; //cantidad de elementos ocupados de la lista
  int numero;
  int i, j;
  //inicializa vector
  for(i=0; i<N; i++) listaOrdenada[i] = 0;

  //proceso
  //mensajes iniciales
  printf("Ingrese los numeros de la lista \n\nLos numeros deben ser mayores que 0 \nPara introducir pulse <ENTER> \n");
  printf("como maximo puede ingresar %d numeros \n", N);
  printf("Para finalizar pulse 0 y <ENTER>\n");

  //Lee valor a ingresar en el vector
  printf("Numero: ");
  scanf("%d", &numero);
  printf("Numero ingresado %d \n", numero);

  while(numero != 0 && numelem < N)
  {
    i = 0;
    //ubica la posición para insertar el número
    while(i<numelem && listaOrdenada[i] < numero) i++;
    //en el caso que se inserte en la última posición: i == numelem
    if(i==numelem) {
       listaOrdenada[i]=numero;
       numelem++;
    }
    else{
       //tiene que desplazar a todos los elementos posteriores
       j = numelem;
       numelem++;
       //recorre la lista de forma inversa para desplazar los valores
       while(j>i){
         listaOrdenada[j] = listaOrdenada[j-1];
         j--;
       }
       listaOrdenada[i] = numero;
    }
 
    //imprime la lista de elementos 
    for(i=0; i<numelem; i++) printf("%d \t",listaOrdenada[i]);

    printf("\n\nNumero: ");
    scanf("%d", &numero);
    printf("Numero ingresado %d \n", numero);

  }

  printf("Elementos utilizados: %d \n", numelem);

  system("PAUSE");
  return 0;
}



sábado, 30 de noviembre de 2013

Representación de un puntero en memoria

La representación de la memoria para una variable de tipo int se puede entender de forma muy simple, en tiempo de ejecución cuando las variable se crean se les asigna un espacio físico en memoria, así cuando vemos:

//se define la variable a de tipo int
int a;

La variable a tiene reservado un espacio para almacenar su contenido (el valor de la variable) y se sabe que ese contenido tiene que ser de tipo int porque así ha sido definida.

Sin embargo, cuando utilizamos variables de tipo punteros la comprensión de la asignación de la memoria es un poco más complicada, una variable de tipo puntero tiene en su definición "al menos un *", aquí unos ejemplos:

int * b;
int **c;

Cuando se reserva memoria para un puntero, se separa un espacio de memoria pero el contenido debe tener el formato de una dirección de memoria. si tomamos como ejemplo:

int * b;

El primer paso es identificar el nombre de la variable y el tipo de la variable:

nombre de variable: b
tipo de la variable: int *

como el tipo de la variable contiene "al menos un *" entonces el contenido (el valor) tiene el formato de una dirección, esa dirección debe apuntar al tipo de variable al cual apunta.

Para saber el tipo de variable al cual apunta, seguimos el segundo paso, que consiste en leer la misma definición de la variable pero ahora el * pasa a ser parte del nombre de la variable:

nombre de variable: *b
tipo de la variable: int

Ahora vemos que la dirección a la que apunta el contenido de b (el valor de b) apunta a una dirección de memoria cuyo nombre se puede interpretar como *b y cuyo tipo es int.

Para verlo de una forma más práctica representaremos las siguientes líneas de código:

int a = 5:
//reserva memoria para el puntero, esta asignación varía según el lenguaje de programación
int* b = new int(); 
     *b = 3;

En una tabla:

Podemos ver que para la variable b el contenido es la dirección 0003, y en la dirección 3 se encuentra la variable *b de tipo int en la que se guarda el contenido 3.

Podríamos aprovechar el espacio de memoria de a para guardar el contenido de b:

int a = 5;
int* b;
     *b = a; //*b y a son del mismo tipo por eso se puede realizar la instrucción de asignación.

La representación en la tabla de memoria sería:


Podemos ver que en este caso: a y *b están en la misma dirección de memoria.





viernes, 17 de mayo de 2013

Búsqueda del valor máximo

Uno de los problemas académicos más comunes es el de la búsqueda del valor máximo o mínimo dentro de una lista. Una aplicación que podríamos darle a este problema sería por ejemplo con fines estadísticos, calcular para una muestra de datos: el máximo, el mínimo y el valor medio.

Antes de plantear el problema vamos a intentar "pensar en cámara lenta" y preguntarnos qué haría nuestra cabeza si tuviésemos que encontrar el valor máximo de una muestra de datos. Suponemos la siguiente muestra de datos:

2, 5, 10, 1, 7

A simple vista diríamos que el máximo es 10, pero ¿Cómo es que lo hemos determinado? si lo vemos en cámara lenta nos daríamos cuenta que:

1. Comparamos el 2 y el 5, como el 5 es mayor, nos quedamos con el 5 y descartamos el 2.
2. Comparamos el 5 con el 10, como el 10 es mayor, nos quedamos con el 10 y descartamos el 5.
3. Comparamos el 10 con el 1, como el 10 es mayor, nos quedamos con el 10 y descartamos el 1.
4. Comparamos el 10 con el 7, como el 10 es mayor, nos quedamos con el 10 y descartamos el 7.

Finalmente el último con el que nos quedamos fue el 10, por tanto, es el 10 el valor máximo.

Si analizamos cada uno de los pasos, vemos que en cada uno de ellos se compara un elemento de la lista con el valor con el que nos hemos quedado en el paso anterior. Si llevamos esto a lenguaje de programación  se puede plantear como un recorrido por todos los elementos de una lista en la que cada iteración realza una comparación entre el valor actual con el máximo de la iteración anterior, por tanto sabemos que debemos tener una estructura así:

//creamos la muestra de datos
int listaNumeros [5]; 
listaNumeros[0] = 2;
listaNumeros[1] = 5;
listaNumeros[2] = 10;
listaNumeros[3] = 1;
listaNumeros[4] = 7;

//tenemos un recorrido de la muestra
for(int i=0; i<5;i++)
{
  //comparamos el valor actual con el resultado de la iteración anterior
  if (listaNumeros[i] > valor_maximo)
  {
    //esta linea representa el "nos quedamos con" de cada paso
    valor_maximo = listaNumeros[i]; 
  }
}

Hasta aquí tenemos la estructura básica del algoritmo, ahora le hacemos algunos ajustes, teniendo en cuenta que la variable valor_maximo no la hemos inicializado.
Si la inicializaramos con 0, nos corremos el riesgo de que para otra muestra tengamos valores negativos y en ese caso el máximo siempre sería 0, lo mejor es tomar como máximo algún valor de la muestra, por ejemplo el primero.

//inicializamos el valor máximo con el primer elemento de la muestra
int valor_maximo = listaNumeros[0];

//tenemos un recorrido de la muestra
for(int i=0; i<5;i++)
{
  //comparamos el valor actual con el resultado de la iteración anterior
  if (listaNumeros[i] > valor_maximo)
  {
    //esta linea representa el "nos quedamos con" de cada paso
    valor_maximo = listaNumeros[i]; 
  }
}

Ahora que nos aseguramos que el valor_maximo de la muestra sea uno de los elementos de la muestra, vemos que la primera iteración comparará listaNumeros[0] y valor_maximo y que siempre serán iguales en la primera iteración, por tanto el código que está dentro del if núnca se ejecutará en la primera iteración, así que podemos aplicar una optimización haciendo que empiece la iteración a partir del segundo elemento, de la siguiente forma.


//inicializamos el valor máximo con el primer elemento de la muestra
int valor_maximo = listaNumeros[0];

//el recorrido se inicia en el segundo elemento
for(int i=1; i<5;i++)
{
  //comparamos el valor actual con el resultado de la iteración anterior
  if (listaNumeros[i] > valor_maximo)
  {
    //esta linea representa el "nos quedamos con" de cada paso
    valor_maximo = listaNumeros[i]; 
  }
}

Finalmente, aprovecharemos el mismo recorrido para calcular el máximo, el mínimo y la media:

//creamos la muestra de datos
int listaNumeros [5]; 
listaNumeros[0] = 2;
listaNumeros[1] = 5;
listaNumeros[2] = 10;
listaNumeros[3] = 1;
listaNumeros[4] = 7;

int valor_maximo = listaNumeros[0];
int valor_minimo = listaNumeros[0];
int media = listaNumeros[0];

for(int i=1; i<5;i++)
{
  //Búsqueda del máximo
  if (listaNumeros[i] > valor_maximo)
  {
    valor_maximo = listaNumeros[i];
  }

  
  //Búsqueda del mínimo
  if (listaNumeros[i] < valor_minimo)
  {
    valor_minimo = listaNumeros[i] ;
  }

  //cálculo de media
  media +=  listaNumeros[i] ;

}

//al terminar el recorrido la variable media contiene la suma de todos los elementos
//dividimos entre el número de elementos para calcular la media
media = media/5;

//En este punto tenemos calculado los tres valores estadísticos de la muestra, los imprimimos en pantalla
printf("valor_maximo: ", valor_maximo);
printf("valor_minimo: ", valor_minimo);
printf("media: ", media);



lunes, 28 de enero de 2013

Variables

Las variables son elementos imprescindibles en la programación, son las encargadas de almacenar la información que se va calculando en un programa en tiempo de ejecución.

Las variables se definen con un tipo específico según la información que se quiera almacenar, por ejemplo si se quiere almacenar un precio definiremos una variable de tipo decimal (float, double, etc), si se quiere almacenar una edad definiremos una variable de tipo entero (int), si se quiere almacenar un nombre definiremos una variable de tipo cadena de caracteres (string, char[], char*).


Cuando se define una variable se reserva el espacio de memoria que la variable necesita según su tipo, sólo reserva el espacio pero no se coloca ningún valor en ella. 


Para definir una variable es necesario:

- Tener claro el tipo de información que se quiere almacenar en la variable para escoger el tipo.
- Escoger un nombre de variable, se recomienda que el nombre describa la información que se almacenará.

Ejemplos de definición de variables


//int: tipo de datos entero porque almacenará la cantidad de alumnos

//numAlumnos: nombre de la variable, que representa el número de alumnos
int numAlumnos; 

//float: tipo de datos decimal porque almacenará un precio

//precio: nombre de variable que representa el precio de un producto
float precio;

Hasta aquí hemos visto qué es una variable, cómo se define y que debemos tener en cuenta para definir una variable. 
En la próxima entrada veremos cómo asignar valores a las variables.