sábado, 27 de agosto de 2011

LibreOffice corrección ortográfica

En enero hablamos sobre LibreOffice como una alternativa a OpenOffice. Con el lanzamiento de Ubuntu 11.04, LibreOffice se convirtió en la suite de productividad oficial de Ubuntu. De hecho cuando actualicé a 11.04, OpenOffice fue desinstalado y LibreOffice ocupó su lugar.

Sin embargo, noté que no tenía instalada la revisión ortográfica en español. Afortunadamente es muy fácil agregar esta funcionalidad. Primero debemos abrir el administrador de paquetes (system > administration > synaptic package manager) y en el recuadro de búsqueda teclear myspell-es. Asegúrate de tener cerrada cualquier instancia de LibreOffice e instala el paquete myspell-es.

Ahora abre alguna instancia de LibreOffice como Writer, pon el lenguaje en Spanish (México) y teclea una sentencia en español para verificar si funciona el paquete. En caso de que no aparezca el lenguaje deseado, selecciona la opción more para tener más opciones.

Como podemos observar en la siguiente imagen, la revisión y corrección ortográfica está funcionando adecuadamente.

Si tienes más tips para mejorar la funcionalidad de LibreOffice, compártelos en los comentarios.

domingo, 7 de agosto de 2011

Respaldar en Ubuntu

Ahora que ha llegado el verano y ha terminado el semestre, es un buen momento para dar una buena limpieza a nuestra computadora y realizar un respaldo. De hecho, siempre debemos tener respaldada nuestra información en caso de que nuestra máquina principal falle.

La primera opción que viene a la mente es tener que comprar un disco externo para realizar respaldos. Sin embargo, en nuestro caso, tenemos dos computadoras y ambas tienen bastante espacio; además, vamos a respaldar sólo aquella información realmente valiosa. Por ello decidimos que cada computadora guardaría un respaldo de la otra. De esta forma aprovechamos el espacio libre y ahorramos al no tener que comprar un disco externo. A continuación mostramos los pasos que seguimos así como algunas recomendaciones. La siguiente imagen muestra la configuración que estamos usando.

1) Instalar Back In Time

Buscando en varios foros y sitios de tecnología todo apuntó a usar Back In Time. Sólo tenemos que agregar el repositorio e instalarlo desde el Centro de Software, verifica que elijas la opción correcta: Gnome o KDE.

  sudo add-apt-repository ppa:bit-team/stable 
  sudo apt-get update

2) Configurar la red

Back In Time no soporta respaldos remotos. Pero usando SSH es posible simular que Back In Time está escribiendo localmente cuando en realidad es una carpeta remota. El siguiente post describe de manera excelente los pasos a seguir. Antes de seguir los pasos del post, sólo realizamos tres pasos adicionales:

  1. Asignamos ip's fijas a cada computadora, esto lo realizamos directamente en el ruteador.
  2. En cada máquina instalamos un servidor de SSH.
      sudo apt-get install openssh-server
  3. Creamos cuentas en cada máquina para poder conectarnos.

3) Cifrar datos sensibles

Es posible que tengamos información que deseemos respaldar pero que no queramos que sea vista por otras personas. Para ello usamos TrueCrypt, que permite crear discos o particiones virtuales cifradas. Debido a que estamos copiando información sensible sin cifrar a un volumen cifrado, es importante que después de copiarla, borremos la información sin cifrar de una manera segura usando Secure-Delete o shred.

  sudo aptitude install secure-delete

Por el momento Secure-Delete nos está dando algunos problemas, que describiremos en otro post, así que decidimos usar shred. Para borrar un archivo:

  shred -u -z -n 32 archivo.txt

Para borrar todos los archivos de un directorio y sub-directorios:

  find -type f -execdir shred -u -z -n 32 '{}' \;

4) Realizar el respaldo.

Ahora sí ya podemos iniciar Back In Time, si es la primera vez, nos presenta un diálogo donde podemos seleccionar dónde guardar el respaldo (en nuestro caso es la carpeta configurada en el paso 2) y qué carpetas deseamos respaldar, entre otras opciones. El siguiente post describe las opciones más comunes.

El primer respaldo fue de unos 5 Gb y duró aproximadamente unas dos horas; sin embargo para el siguiente respaldo, sólo se copiarán aquellas cosas que cambiaron, por lo que será más rápido.

Esperamos haberlos motivado a respaldar sus datos y a que compartan sus experiencias al respecto.

jueves, 14 de julio de 2011

ANSI C extra: Control de versiones con Mercurial

Durante la sesión anterior, se comentó lo peligroso que puede ser el mal uso de algunos comandos en la terminal que pueden ocasionar la pérdida de la información que contienen archivos que tal vez para nosotros son muy importantes y representan quizá el trabajo de varios días y varias noches.

Y aunque es inevitable equivocarse, afortunadamente existen herramientas que pueden ayudarnos a reducir el impacto de un error que se cometió bajo la presión de alguna fecha de entrega de proyectos y una alternativa para no perder completamente nuestra información es el uso de Control de Versiones.

Existen varias herramientas de Control de Versiones y una de las que he explorado es Mercurial. Para conocer más acerca de Mercurial, puedes echar un vistazo a los posts Mercurial y Bitbucket (parte 1) y Mercurial y Bitbucket (parte 2).

ANSI C extra: implementación de una cola circular

En el post ANSI C tarea: Pilas y Colas se mencionó la existencia de una estructura llamada Pila Circular. En esta ocasión quiero presentar un ejemplo de la implementación de la Cola Circular Estática.

Que sea estática significa que toma como base un arreglo, por lo que el número de elementos que contiene y la memoria que utiliza, están limitados por el tamaño del arreglo con el que se construye.

Para este ejemplo, construí una cola de 10 elementos, pero puede extenderse para que se utilice con cualquier número de elementos.

El código para la Cola Circular Estática es el siguiente:

#include 
#include 
#include "bool.h"

bool estaVacia(int* arreglo){
  
  int i;
  for(i=0; i < 10; i++ ){
    if(arreglo[i] > -1){
      return FALSE;
      break;
    }
  }
  return TRUE;
}

void insertar(int n, int**a, int* fr, int* fn){
  printf("%d, %d\n", *fr, *fn);
  int* esteArreglo = *a;
  if(estaVacia(esteArreglo)){
    *fr = *fn = 0;
  }
  else if(*fn == 9){
    (*fn) = 0;
  }
  else{
    (*fn)++;
  }
  esteArreglo[*fn] = n;
}

int remover(int**a, int*fr, int*fn){
  printf("%d, %d\n", *fr, *fn);
  int* esteArreglo = *a;
  int x = esteArreglo[*fr];
  if((*fr) == (*fn)){
    (*fr) = (*fn) = -1;
  }
  else if((*fr) == 9){
    (*fr) = 0;
  }
  else{
    (*fr)++;
  }
  return x;
}

void imprimir(int* arreglo, int principio){
  
  int i;
  for(i = principio; i < 10; i++){
    printf("%d ", arreglo[i]);
  }
  printf("\n");
}

int main(){
  //indices para controlar el final y el principio de la cola
  int frente = -1;
  int fin = -1;
  //arreglo que contendra los elementos de la cola (10 elementos)
  int* arreglo = NULL;
  arreglo = (int*)malloc(sizeof(int)*10);

  if(arreglo == NULL){
    printf("No se pudo reservar memoria para el arreglo\n");
  }
  else{
    int index;
    for(index = 0; index < 10; index ++){
      arreglo[index] = -1;
    }  
  }

  insertar(2, &arreglo, &frente, &fin);
  imprimir(arreglo, frente);

  insertar(4, &arreglo, &frente, &fin);
  imprimir(arreglo, frente);

  insertar(3, &arreglo, &frente, &fin);
  imprimir(arreglo, frente);

  insertar(10, &arreglo, &frente, &fin);

  imprimir(arreglo, frente);

  remover(&arreglo, &frente, &fin);
  imprimir(arreglo, frente);

  remover(&arreglo, &frente, &fin);

  imprimir(arreglo, frente);
  free(arreglo);
}

La salida de este ejemplo es la siguiente:

ANSI C tarea: Depth First Search

DFS (en inglés Depth First Search) es un algoritmo que nos sirve para movernos a través de la estructura de un grafo. Básicamente permite visitar los vértices de un grafo, pero existen muchos algoritmos que están basados en DFS para realizar sus distintas funcionalidades. Su principio de funcionamiento es bastante sencillo: hay que avanzar en profundidad visitando cada nodo mientras sea posible visitarlo, si no es posible visitar a un nodo, entonces se realiza un procedimiento llamado "backtracking", en el cual comenzamos a regresar por el camino donde llegamos.

Para saber si un vértice es "visitable", contiene tres colores que funcionan como identificadores de su estado:

1. Blanco: Significa que el vértice no ha sido visitado.

2. Gris: La visita a ese vértice está en progreso.

3. Negro: Ya se terminó la exploración de ese nodo y se dice que está visitado.

También se pueden tomar sólo dos colores o los valores verdadero y falso para determinar el estado de un nodo.

Inicialmente, todos los vértices son coloreados de blanco y conforme avanza la búsqueda o visita, sus colores van cambiando para representar su estado.

En esta ocasión deseaba mostrar un ejemplo de esta implementación, sin embargo, no logré mi objetivo pero de todas formas quiero dejar el código visto en clase para representar un grafo junto con las modificaciones que comencé a hacerle para intentar producir un DFS.

Este es el código del archivo grafo.c en donde se encuentran las funciones esArista y DFS que comencé a construir:

#include <stdio.h>
#include <stdlib.h>
#include "bool.h"

typedef struct algo {
  char caracter;
  int id;
  struct algo* sig;
  //Estado del nodo: 'b' = blanco, no visitado,
  //'g' = gris, visitado, 'n' = negro, ya no tiene aristas por visitar
  char estado;
} lista;

int etiqueta(lista** nombres, char a, 
      int* sigId) {
  lista* l = *nombres;
  lista* nuevo = NULL;
  int n = *sigId;
  if (l == NULL) { // no hay nada
    nuevo = (lista*)malloc(sizeof(lista));
    nuevo->caracter = a;
    nuevo->id = n++;
    *sigId = n;
    nuevo->estado = 'b';
    nuevo->sig = NULL;
    *nombres = nuevo;
    return nuevo->id;
  }
  while (1) { // mientras siempre
    if (l->caracter == a) {
      return l->id; // ya esta
    }
    if (l->sig != NULL) {
      l = l->sig; // avanzar en la lista
    } else { // lo ponemos al final
      nuevo = (lista*)malloc(sizeof(lista));
      nuevo->caracter = a;
      nuevo->id = n++;
      *sigId = n;
      nuevo->estado = 'b';
      nuevo->sig = NULL;
      l->sig = nuevo; // ligar en la lista
      return nuevo->id;
    }
  }
}

char recuperar(lista* l, int a) {
  while (l != NULL) { // mientras siempre
    if (l->id == a) {
      return l->caracter;
    }
    if (l->sig != NULL) {
      l = l->sig; // avanzar en la lista
    }
  }
  return '?';
}

//este es un metodo que intente construir para verificar que 
//hay una arista entre dos nodos dados
bool esArista(int** matriz, int a, int b){
  return(matriz[a][b] == 1);
}

//este es el metodo que intente construir para realizar un DFS
void DFS(lista** lista, int** matriz){
  lista* listaDFS = *lista;
  lista* aux = *lista;
  listaDFS -> estado = 'g';
  while(aux != NULL){
    if(esArista(matriz, listaDFS->id, aux->id) && 
       aux->estado == 'b'){
      print("%d\n", listaDFS->id);
      DFS(aux->siguiente, matriz);
    }
  }
  listaDFS->estado = 'n';
}

int main(int argc, char** args) {
  char* archivo = NULL;
  char* segundo = NULL;
  FILE* entrada = NULL;
  FILE* salida = NULL;
  lista* nombres = NULL;
  lista* aux = NULL;
  char a, b;
  int na, nb, i, j, n = 0;
  int** matriz = NULL;
  bool sinSalida = FALSE;

  if (argc < 3) {
    printf("Ponme los archivos guey.\n");
    return 0;
  }
  archivo = args[1];
  segundo = args[2];
  entrada = fopen(archivo, "r"); // r = lectura
  if (entrada == NULL) {
    printf("No se pudo con %s.\n", archivo);
    return 0;
  }
  salida = fopen(segundo, "w+"); // escribe o crea
  if (salida == NULL) {
    sinSalida = TRUE;
    printf("No habra salida.\n");
  }
  while (!feof(entrada)) {
    fscanf(entrada, "%c %c\n", &a, &b);
    na = etiqueta(&nombres, a, &n);
    nb = etiqueta(&nombres, b, &n);
    printf("Recuperado: (%c=%d, %c=%d).\n", 
    a, na, b, nb);
  }
  printf("Fueron en total %d nodos.\n", n);
  matriz = (int**)malloc(sizeof(int*)*n);
  for (i = 0; i < n; i++) {
    matriz[i] = (int*)malloc(sizeof(int)*n);
    for (j = 0; j < n; j++) {
      matriz[i][j] = 0; // no hay conexion
    }
  }
  rewind(entrada);
  while (!feof(entrada)) {
    fscanf(entrada, "%c %c\n", &a, &b);
    na = etiqueta(&nombres, a, &n);
    nb = etiqueta(&nombres, b, &n);
    // hay arista (na, nb) = (nb, na)
    matriz[na][nb] = 1;
    matriz[nb][na] = 1;
  }
  fclose(entrada);
  if (salida == NULL) {
    salida = stdout;
  }
  fprintf(salida, "    ");
  for (j = 0; j < n; j++) {
    fprintf(salida, "%c ", recuperar(nombres, j));
  }
  fprintf(salida, "\n   ");
  for (j = 0; j < n; j++) {
    fprintf(salida, "--", recuperar(nombres, j));
  }
  fprintf(salida, "\n");
  for (i = 0; i < n; i++) {
    fprintf(salida, 
     "%c | ", recuperar(nombres, i));
    for (j = 0; j < n; j++) {
      fprintf(salida, "%d ", matriz[i][j]);
    }
    fprintf(salida, "\n");
  }
  if (!sinSalida) {
    fclose(salida); // cerrar archivo
  }

  DFS(&nombres, matriz);

  // borrar la lista (una vez que ya no se ocupa)
  while (nombres != NULL) {
    aux = nombres->sig;
    free(nombres);
    nombres = aux;
  }

  for (i = 0; i < n; i++) {
    free(matriz[i]);
  }
  free(matriz);
  return 1;
}

Al compilar lo codificado, estos son los errores que aparecen:

Referencias

Algorithms and Data Structures with implementations in Java and C++. En http://www.algolist.net/Algorithms/Graph/Undirected/Depth-first_search. Visitado el 14 de Julio de 2011.

martes, 12 de julio de 2011

ANSI C tarea: Pilas y Colas

Pilas (Stacks)

Las pilas son estructuras de datos tipo LIFO (Last In First Out). Esto significa que funcionan de la siguiente manera: cuando se le agrega un nuevo elemento, éste siempre aparece al principio de la lista de elementos y cuando es cuestión de remover un elemento, siempre se remueve el último elemento agregado (que se encuentra al principio de la lista de elementos, en la cabecera de la pila).

Es de tipo lineal, es decir, sus elementos están acomodados en línea recta y además, puede generarse a partir de arreglos (de manera estática) o con listas enlazadas (de manera dinámica). La mayoría de los lenguajes de programación disponen de un dato tipo Pila, sin embargo, es necesario conocer y comprender su funcionamiento para poder hacer un uso adecuado de éste.

Se consideran 4 operaciones primitivas que pueden realizarse sobre una pila:

  1. Inserción. Consiste en agregar datos o elementos a una pila. Se le conoce como push
  2. Eliminación. Es la supresión de datos y se le conoce como pop
  3. Obtener elemento en el tope (es decir, el elemento primero). A esta operación se le conoce como stacktop y permite obtener el primer elemento de la pila, sin eliminarlo.
  4. Pila vacía. Es un método que regresa verdadero si la pila está vacía o falso de lo contrario. Se le conoce como empty.

Colas (Queues)

Son estructuras de datos tipo FIFO (First In First Out). Me agrada relacionarlas con la fila para comprar las tortillas: el primero en ser atendido e irse a casa es el primero que llega y el último en llegar se forma hasta atrás. Con las colas sucede lo mismo: cuando se saca un elemento de la cola, el elemento que sale es el primero que se colocó y que está al frente y cuando se agrega un elemento, éste se agrega hasta el final. Al igual que las pilas, las colas pueden generarse a partir de arreglos (de forma estática) o de listas enlazadas (de forma dinámica).

Existen diversos tipos de cola:

1. La cola circular. Representa a esta estructura de datos como un círculo y gracias a ello, no desaprovecha espacio como ocurre con una cola lineal estática.

2. La bicola o cola doble. Las inserciones y eliminaciones se pueden realizar tanto al inicio como al final. De ésta existen 2 tipos:

  • Bicola de entrada restringida: Acepta eliminaciones tanto al inicio como al final, pero solamente acepta inserciones al final.
  • Bicola de salida restringida: Acepta eliminaciones solo al inicio, pero las inserciones se pueden realizar tanto al inicio como al final.

3. Cola de prioridades. Es aquella en la que sus elementos tienen un cierto orden de acuerdo con el criterio que les asigna el programador. Hay dos tipos de cola de prioridades:

  • Cola de prioridad ascendente. Sus elementos se pueden insertar de manera arbitraria, pero se acomodan dentro de la cola de manera que lleven un orden ascendente.
  • Cola de prioridad descendente. Sus elementos se pueden insertar de forma arbitraria, pero se acomodan de manera descendente.

Estas son las operaciones primitivas o básicas que se pueden llevar a cabo con las colas:

  1. Insertar un elemento.El nombre puede variar, pero en general a esta operación se le llama insert
  2. Eliminación de un elemento. Operación remove. Cabe aclarar que con este nombre en particular tuve algunos problemas pues ya se encuentra definido en la libreria estándar stdio.h, así que usé la versión en español (remover) en el ejemplo que se encuentra al final de este post.
  3. Cola vacía. Se encarga de verificar si una cola está vacía. Operación empty.

Ejemplo de implementación

A continuación se encuentra un ejemplo de cómo se modificó el código de las listas enlazadas visto en clase para que se comporte tanto como una pila como una cola.

Empecemos por la pila. Así quedó declarada una librería para pilas (al que llamé pilas.h):

#ifndef PILAS_H

#define PILAS_H

#include "bool.h"

// estructura de un elemento de la lista
struct elemento_de_lista {
  int dato; // donde la info va
  // doblemente enlazada
  struct elemento_de_lista* siguiente; // puntero
  struct elemento_de_lista* anterior; // puntero
}; // <= ojo con el punto y coma

// redefinición por brevedad
typedef struct elemento_de_lista elem;

// eliminar todos los elementos de la lista
elem* borrar_pila(elem* esto);

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar_en_pila(int valor, elem* aqui);

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento_pila(elem* aqui, 
         elem** inicio);

// interface para llamadas mas bonitas
bool pop(elem** inicio);

void imprime_elemento(elem* esto);

// interface que agrega [ ... ] y el \n
void imprimir_pila(elem* lista);

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* push(int valor, elem* aqui);

#define MAX 30
#define MIN 1

// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio();

#endif

Esta es la implementación de los métodos declarados en la librería (pilas.c):

#include <stdio.h> // imprimir (printf)
#include <stdlib.h> // reservar memoria
#include "pilas.h"

// eliminar todos los elementos de la lista
// opcion vaciar
elem* borrar_pila(elem* esto) {
  elem* temp; // auxiliar 
  // iterativa
  while (esto != NULL) {
    temp = esto->siguiente;
    free(esto); // liberar
    esto = temp; // avanza al siguinte
  }
  return NULL; // que ya no hay lista
}

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar_en_pila(int valor, elem* aqui) {
  if (aqui != NULL) {

    printf("Buscando por %d en %d.\n", 
    valor, aqui->dato);

    // si el valor buscado esta en este elemento
    if (aqui->dato == valor) {

      printf("Son iguales.\n");

      return TRUE; // busqueda exitosa

    }
    // pasar la pelota al siguiente elemento
    return buscar_en_pila(valor, aqui->siguiente);
  }
  else { // aqui es null
    // este elemento actual ya es null, o sea,
    // no en realidad es un elemento

    printf("Ya se acabo. No estuvo.\n");
    return FALSE; // busqueda fallida
  }
}

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento_pila(elem* aqui, 
         elem** inicio) {
  if (aqui != NULL) { // si hay algo
    *inicio = aqui->siguiente;
    free(aqui); // borrame      return TRUE; // eliminacion exitosa
    return TRUE;
    } 
  return FALSE;
}

// interface para llamadas mas bonitas
bool pop(elem** inicio) {
  return 
    eliminar_elemento_pila(*inicio, inicio);
}

void imprime_elemento(elem* esto) {
  // iterativa
  while (esto != NULL) {
    printf("%d ", esto->dato);
    esto = esto->siguiente;
  }
  return;
}

// interface que agrega [ ... ] y el \n
void imprimir_pila(elem* lista) {
  printf("[ ");
  imprime_elemento(lista);
  printf("]\n");
  return;
}

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* push(int valor, elem* aqui) {
  elem* nuevo = NULL; // auxiliar
  // para crear el nuevo elemento
 
  if (aqui != NULL) {
    printf("Estoy en %d, insertando un %d.\n",
    aqui->dato, valor);
  } else {
    printf("No hay nada.\n");
  }
 
  if (aqui == NULL) { // no hay nadie
    nuevo = (elem*)malloc(sizeof(elem));
    nuevo->dato = valor; // asignar dato
    nuevo->siguiente = NULL; // el unico
    nuevo->anterior = NULL; // el unico
    return nuevo;
  } 
  else {
    nuevo = (elem*)malloc(sizeof(elem));
    nuevo->dato = valor; // pon el valor
    nuevo->siguiente = aqui;
    aqui->anterior = nuevo;
    nuevo->anterior = NULL; 
  }
  return nuevo;
}

// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio() {
  return ((rand() % (MAX - MIN + 1)) + MIN);
}

En el archivo prueba_pilas.c se encuentra el método principal que manda a llamar a las rutinas propias de la pila (contenidas en pilas.c):

#include "pilas.h"
#include "entrada.h"
#include  // printf
#include  // srand

// rutina principal
int main(int argc, char** args) {
  elem* lista = NULL; // vacia al inicio
  int valor; // auxiliares
  char op;
  bool basta = FALSE;

  while (!basta) {
    printf("Que quieres hacer?\n");
    printf("Agregar = a\nBuscar = b\n");
    printf("Eliminar = e\nVaciar = v\n");
    printf("Imprimir = i\nSalir = s\n> ");
    op = pide_opcion("abevis");
    switch (op) {
    case 'a':
      valor = pide_numero(MIN, MAX);
      lista = push(valor, lista);      
      break;
    case 'b':
      valor = pide_numero(MIN, MAX);
      printf("%d %s esta en la lista.\n",
      valor, (buscar_en_pila(valor, lista) ? 
       "SI" : "NO"));
      break;
    case 'e':
      if (pop(&lista)) {
 printf("%d eliminado.\n", valor);
      } else {
 printf("Pila vacia.No pudo eliminarse informacion de la pila");
      }
      break;
    case 'v':
      lista = borrar_pila(lista);
      break;
    case 'i':
      imprimir_pila(lista);
      break;
    case 's':
      basta = TRUE;
      break;
    default:
      printf("Esto no deberia pasar nunca.\n");
      break;
    }
  }
  return 1; // ya no hacemos nada
}

En la siguiente imagen se aprecia la salida del programa para las pilas:

A continuación se encuentra el código para las colas. Esta es la libreria que contiene la funcionalidad de las colas (colas.h):

#ifndef COLAS_H

#define COLAS_H

#include "bool.h"

// estructura de un elemento de la lista
struct elemento_de_lista {
  int dato; // donde la info va
  // doblemente enlazada
  struct elemento_de_lista* siguiente; // puntero
  struct elemento_de_lista* anterior; // puntero
}; // <= ojo con el punto y coma

// redefinicion por brevedad
typedef struct elemento_de_lista elem;

// eliminar todos los elementos de la lista
elem* borrar(elem* esto);

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar(int valor, elem* aqui);

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento_cola(elem* aqui, 
         elem** inicio);

// interface para llamadas mas bonitas
bool remover(elem** inicio);

void imprime_elemento(elem* esto);

// interfase que agrega [ ... ] y el \n
void imprimir_cola(elem* lista);

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* insert(int valor, elem* aqui);

#define MAX 30
#define MIN 1

// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio();

#endif

En este archivo (colas.c) se realizaron las modificaciones necesarias para obtener el comportamiento de una cola (colas.c)

#include  // imprimir (printf)
#include  // reservar memoria
#include "colas.h"

// eliminar todos los elementos de la lista
// opcion vaciar
elem* borrar(elem* esto) {
  elem* temp; // auxiliar 
  // iterativa
  while (esto != NULL) {
    temp = esto->siguiente;
    free(esto); // liberar
    esto = temp; // avanza al siguinte
  }
  return NULL; // que ya no hay lista
}

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar(int valor, elem* aqui) {
  if (aqui != NULL) {

    printf("Buscando por %d en %d.\n", 
    valor, aqui->dato);

    // si el valor buscado esta en este elemento
    if (aqui->dato == valor) {
      printf("Son iguales.\n");
      return TRUE; // busqueda exitosa
    } 
    // pasar la pelota al siguiente elemento
    return buscar(valor, aqui->siguiente);
  } else { // aqui es null
    // este elemento actual ya es null, o sea,
    // en realidad no es un elemento
    printf("Ya se acabo. No estuvo.\n");
    return FALSE; // busqueda fallida
  }
}

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento_cola(elem* aqui, 
         elem** inicio) {
  if (aqui != NULL) { // si hay algo
    *inicio = aqui->siguiente;
    free(aqui); // borrame
      return TRUE; // eliminacion exitosa
  }
  return FALSE;
}

// interface para llamadas mas bonitas
bool remover(elem** inicio) {
  return 
    eliminar_elemento_cola(*inicio, inicio);
}

void imprime_elemento(elem* esto) {
  // iterativa
  while (esto != NULL) {
    printf("%d ", esto->dato);
    esto = esto->siguiente;
  }
  return;
}

// interface que agrega [ ... ] y el \n
void imprimir_cola(elem* lista) {
  printf("[ ");
  imprime_elemento(lista);
  printf("]\n");
  return;
}

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* insert(int valor, elem* aqui) {
  elem* nuevo = NULL; // auxiliar
  // para crear el nuevo elemento
 
  if (aqui != NULL) {
    printf("Estoy en %d, insertando un %d.\n",
    aqui->dato, valor);
  } else {
    printf("No hay nada.\n");
  }

    nuevo = (elem*)malloc(sizeof(elem));
    nuevo->dato = valor; // asignar dato 

  if (aqui == NULL) { // no hay nadie
    nuevo->siguiente = NULL; // el unico
    nuevo->anterior = NULL; // el unico
    return nuevo;
  } 
  else {
    if(aqui->siguiente != NULL){
      insert(valor, aqui->siguiente);
    }
    else{
      printf("Anexando al final.\n");
     
      aqui->siguiente = nuevo;
      nuevo->anterior = aqui;
      nuevo->siguiente = NULL; // el ultimo
    }
  }
  return aqui;
}

// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio() {
  return ((rand() % (MAX - MIN + 1)) + MIN);
}

El siguiente código muestra el método principal y la llamada a los métodos propios de la cola (prueba_colas.c):

#include "pilas.h"
#include "entrada.h"
#include  // printf
#include  // srand

// rutina principal
int main(int argc, char** args) {
  elem* lista = NULL; // vacia al inicio
  int valor; // auxiliares
  char op;
  bool basta = FALSE;

  while (!basta) {
    printf("Que quieres hacer?\n");
    printf("Agregar = a\nBuscar = b\n");
    printf("Eliminar = e\nVaciar = v\n");
    printf("Imprimir = i\nSalir = s\n> ");
    op = pide_opcion("abevis");
    switch (op) {
    case 'a':
      valor = pide_numero(MIN, MAX);
      lista = push(valor, lista);      
      break;
    case 'b':
      valor = pide_numero(MIN, MAX);
      printf("%d %s esta en la lista.\n",
      valor, (buscar_en_pila(valor, lista) ? 
       "SI" : "NO"));
      break;
    case 'e':
      if (pop(&lista)) {
 printf("%d eliminado.\n", valor);
      } else {
 printf("Pila vacia.No pudo eliminarse informacion de la pila");
      }
      break;
    case 'v':
      lista = borrar_pila(lista);
      break;
    case 'i':
      imprimir_pila(lista);
      break;
    case 's':
      basta = TRUE;
      break;
    default:
      printf("Esto no deberia pasar nunca.\n");
      break;
    }
  }
  return 1; // ya no hacemos nada
}

Finalmente, este es el resultado de la compilación y ejecución de colas.c, prueba_colas.c y entrada.c. Éste último es necesario compilarlo junto con los otros dos pues se hace uso de sus métodos para requerir valores al hipotético usuario:

Referencias

"Queue (data structure)". Wikipedia. The Free Encyclopedia, 2011.

"Stack (data structure)." Wikipedia. The Free Encyclopedia, 2011.

Apuntes de Estructuras de Datos. Semestre Agosto-Diciembre de 2010.

jueves, 7 de julio de 2011

ANSI C tarea: Usando ciclos, apuntadores, arreglos y subrutinas

Existen tres tipos de ciclo en C: while, do..while y for. En el post ANSI C extra: while, do while y for pueden ser equivalentes se demostró su uso y además se compararon estos ciclos utilizando un pequeño ejemplo.

En esta ocasión, utilizaré otro ejemplo para ilustrar el uso que se puede da a apuntadores, arreglos y subrutinas en C.

En C, un apuntador es una variable "especial" que contiene la dirección de memoria de algún elemento que estamos utilizando en nuestro programa. Este elemento puede ser otra variable, un tipo definido por el programador e inclusive, otro apuntador.

Los arreglos utilizan apuntadores para navegar a través de los elementos que contienen. Así, cuando se crea un arreglo, el nombre del arreglo indica un apuntador al elemento 0 (primer elemento del arreglo) y a través de ciclos, podemos realizar alguna operación o simplemente conocer a los elementos que integran al arreglo. Un arreglo puede contener dentro de sí mismo a otros arreglos, lo que llamamos "Arreglo multidimensional", el cual es muy útil para representar por ejemplo, matrices en matemáticas.

Otro aspecto del lenguaje C que se ejemplifica en este post es el uso de subrutinas. Las subrutinas son métodos definidos cada uno en su propio archivo. De esta manera, a través del uso de subrutinas, el programa que creamos se vuelve más simple de manejar y adquiere un aspecto llamado "modularidad", esto quiere decir que dividimos las distintas funciones que queremos que realice en porciones o módulos que, debido a que se encuentran definidos cada uno en un archivo por separado, es posible reutilizarlos más adelante sin tener que volver a teclear el mismo código o pegarlo dentro de otro archivo.

El ejemplo que estoy presentando es la generación del Triángulo de Pascal. Este triángulo es una herramienta que nos permite conocer los coeficientes del polinomio que resulta al elevar un binomio a cierta potencia. El número de renglón corresponde a la potencia a la que se elevó el binomio. Por ejemplo, si elevamos el binomio (x + 1) al cuadrado obtendremos x² + 2x + 1. Luego, El triángulo de Pascal tiene la siguiente forma:

1

1 2 1

1 3 3 1

.

.

.

La segunda fila (1 2 1) corresponde a nuestro resultado de elevar el binomio al cuadrado: 1 es el coeficiente de x², 2 es el coeficiente de 2x y 1 es el coeficiente de 1².

Cada elemento después del primer 1 se obtiene sumando los dos elementos que se encuentran en la fila anterior y hacia la derecha del valor que deseamos obtener, así, en la fila 3 por ejemplo el 3 se forma de la suma de 1 y 2.

La implementación del ejemplo la dividí en varios módulos, cada uno en su propio archivo. Para poder compilarlos es necesario crear un encabezado donde se colocan las librerías a utilizar, las definiciones tanto de variables como de estructuras y los nombres y parámetros de los distintos métodos seguidos de punto y coma sin nada de su implementación.

El encabezado (al que llamé trianguloHeader.h) de este ejemplo es el siguiente:

#include 
#include 
#include 
#include 
#include 

#define TRUE 1
#define FALSE 0
typedef short boolean;

#define SIN_DEFINIR -1
#define LARGO_MAXIMO 12
#define MAX_DIMENSION 30

typedef struct una_matriz{
  int filas;
  int columnas;
  int** elementos;
}matriz;

char pide_opcion(char* permitidos);
int pide_numero(int minimo, int maximo);
void vacia(matriz* m);
void llena(matriz* m);
void imprime(matriz m);

Después, en otro archivo reutilicé el método para pedir un valor numérico al usuario. A este archivo lo llamé pedirNumero.c. Su contenido es el siguiente:

#include "trianguloHeader.h"

int pide_numero(int minimo, int maximo){
  int numero = SIN_DEFINIR;
  char* entrada = NULL;
  char actual;
  int posicion;
  double temporal;

  entrada = 
    (char*)malloc(sizeof(char)*LARGO_MAXIMO + 1);
  if(entrada == NULL){
    return SIN_DEFINIR;
  }
  do{
    printf("Dame un valor entre %d y %d: ", 
    minimo, maximo);
    posicion = 0;
    do{
      actual = getchar();
      if(posicion < LARGO_MAXIMO){
 if(isdigit(actual)){
   entrada[posicion++] = actual;
 }
      }
    }while(actual != '\n');
    entrada[posicion] = '\0';
    temporal = atof(entrada);
    if(temporal > INT_MAX - 1){
      continue;
    }
    numero = atoi(entrada);
  }while(numero < minimo || numero > maximo);
  free(entrada);
  return numero;
}

El siguiente archivo que creé contiene el método pedir_opcion, que también estoy reutilizando del material visto en clase. A este archivo lo nombré opcionSiNo.c. Este es su contenido:

#include "trianguloHeader.h"

char pide_opcion(char* permitidos){
  char actual;
  boolean listo = FALSE;

  while(TRUE){
    if(!listo){
      actual = tolower(getchar());
      if(strchr(permitidos, actual) != NULL){
 listo = TRUE;
      }
    } else{
      if(getchar() == '\n'){
 if(listo){
   break;
 }
      }
    }
  }
  return actual;
}

Después creé el archivo llenarTriangulo.c para almacenar la funcionalidad de llenado. La mayoría de este código también está basado en el material visto en clase:

#include "trianguloHeader.h"

void llena(matriz* m){
  int i, j, k;

  printf("Llenando el Triangulo de Pascal\n");
  printf("Cuantos renglones quieres que tenga?\n");
  m->filas = pide_numero(1, MAX_DIMENSION);
  
  m->elementos = (int**)malloc(sizeof(int*)*(m->filas));
  if(m->elementos == NULL){
    printf("No se pudo reservar memoria.\n");
    return;
  }
  for(i = 0; i < m->filas; i++){
    m->elementos[i] = 
      (int*)malloc(
     sizeof(int)*m->filas);
    for(j = 0; j < m->filas; j++){
      m->elementos[i][j] = 0;
    }
  }

  i = 0;
  j = 0;
  m->elementos[i][j] = 1;
   for(i = 1; i < m->filas; i++){
     j = 0;
     m->elementos[i][j] = 1;
     for(j = 1; j < m->filas;j++){
 m->elementos[i][j] = m->elementos[i-1][j-1]
     + m->elementos[i-1][j];
      }
   }
}

Otra funcionalidad que ocupé fue la de vaciar la matriz donde almacené los valores del triángulo de Pascal con el objetivo de liberar memoria. Este otro método también está basado en el material visto en clase. El archivo se llama vaciarTriangulo.c:

#include "trianguloHeader.h"

void vacia(matriz* m){
  int i;

  m->filas = SIN_DEFINIR;
  m->columnas = SIN_DEFINIR;

  for(i = 0; i < m->filas; i++){
    free(m->elementos[i]);
  }
  free(m->elementos);
  return;
}

La última funcionalidad que agregué es la de imprimir el triángulo reutilizando el método de imprimir una matriz bidimensional visto en la clase. El archivo se llama imprimeTriangulo.c:

#include "trianguloHeader.h"

void imprime(matriz m){
  int i, j, k;

  for(i = 0; i < m.filas; i++){
    for(j = 0; j <= i; j++){
      printf("%s%3d", (j == 0? "": "  "),
       m.elementos[i][j]);
    }
    printf("\n");
  }
  return;
}

Finalmente, el método principal, desde donde se mandan a llamar a casi todas mis funcionalidades se encuentra en el archivo trianguloPascal.c:

#include "trianguloHeader.h"

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

  boolean opcion = FALSE;
  do{
    matriz m;
    llena(&m);
    imprime(m);
    vacia(&m);

    printf("Deseas generar otro triangulo (s/n)?\n");
    opcion = (pide_opcion("sn") == 's');

  }while(opcion);
}

Es importante resaltar que todos los archivos, incluyendo el principal (trianguloPascal.c) deben incluir al encabezado (trianguloHeader.h) para poder compilarlos juntos. La forma de compilar y algunas salidas del programa se pueden observar en la siguiente imagen:

Referencias

"Pascal's Triangle". Wolfram MathWorld. 2011

Pointers.Te GNU C Programming Tutorial, Edition 4.1

Loops.Te GNU C Programming Tutorial, Edition 4.1