viernes, 9 de diciembre de 2011

Cómo aprender un nuevo idioma gratis y ayudar a traducir la web en el proceso: Duolingo

¿Alguna vez te has encontrado con algo similar a esto?

Seguramente lo habrás visto en alguna parte. Esto es un reCAPTCHA, es decir, se trata de un programa que puede reconocer si el usuario es humano o es una computadora. Está basado en nuestra habilidad visual para reconocer palabras a partir de imágenes distorsionadas o incompletas de éstas. Actualmente las computadoras no son tan hábiles como nosotros para reconocer palabras o patrones a partir de imágenes que han sufrido ligeras modificaciones, así que muchos sitios de internet utilizan los reCAPTCHA para asegurarse de que es una persona la que está tratando de llenar una solicitud o realizar una compra, como por ejemplo, en el caso de Ticket Master y así evitar que una computadora pueda introducir información falsa y saturar con ella la red.

reCAPTCHA también es el nombre de la compañía que está detrás del servicio gratuito para proveer de reCAPTCHAS a un sitio web y sus fundadores son los responsables de la creación de los CAPTCHAS (los cuales a diferencia de los reCAPTCHAS, solamente contienen una palabra que debe ser reconocida) y por supuesto de los reCAPTCHAS. Sin embargo, lo interesante es que están utilizando este servicio para que millones de personas colaboren en la digitalización de libros o documentos antiguos o dañados que son muy difíciles de reconocer correctamente mediante programas de computadora.

¿Cómo es esto posible? Es simple, cada vez que se genera un reCAPTCHA, que contiene dos palabras que deben ser reconocidas por un usuario, estas palabras provienen de libros o documentos que han sido escaneados pero que no han podido ser reconocidos exitosamente mediante programas de computadora. Cuando el usuario teclea correctamente ambas palabras, sin saberlo, está contribuyendo con la digitalización de dichos documentos. Pero ¿cómo saber si el usuario tecleó las palabras correcamente? Pues bien, se conoce correctamente una de estas palabras y se desconoce a la otra, si el usuario teclea correctamente la palabra conocida, es altamente probable que haya tecleado ambas correctamente, el truco aquí es que no se sabe cuál de las dos palabras es la conocida y cuál es la desconocida.

Ahora bien, reCAPTCHA tiene un nuevo proyecto llamado Duolingo, que es una herramienta gratuita que permitirá el aprendizaje de una nueva lengua de manera gratuita a través de internet. Este proyecto está basado en la experiencia positiva que se ha tenido con las reCAPTCHAS, pues están convencidos de que mediante la colaboración de un número muy grande de usuarios es posible traducir páginas que contienen un volumen considerable de información a otros idiomas y al mismo tiempo, permitir que quienes colaboran, aprendan otro idioma.

La idea es presentar sentencias cuya complejidad varía de acuerdo con el nivel de conocimiento del usuario con respecto a un idioma para que éste las comience a traducir. En Duolingo se otorgan herramientas de apoyo para lograr este objetivo, como por ejemplo la lectura de la oración de manera que la persona pueda escuchar la pronunciación del nuevo idioma, así como la traducción de palabras que sean totalmente desconocidas o nuevas.

Este proyecto realmente es una idea muy interesante y valdria la pena aventurarse a formar parte de él. Las expectativas son tan altas que, de acuerdo con su sitio, sería posible mediante la colaboración de una multitud de usuarios traducir Wikipedia en su totalidad del inglés al español en aproximadamente 80 horas de manera gratuita.

¿Les gustaría aprender un nuevo idioma y colaborar a que la información en internet sea más accesible? Los invito a participar, sin duda es increíble lo que como civilización podemos lograr si realizamos un esfuerzo colectivo por el bien de todos.

Para saber más acerca de este proyecto, pueden consultar las siguientes ligas:

Página de reCAPTCHA

Página de Duolingo

Y también, pueden observar el siguiente video, donde se explica más a fondo acerca de Duolingo y reCAPTCHA:

jueves, 8 de diciembre de 2011

Cómo evaluar transformadas de Laplace utilizando Wolfram Alpha

La transformada de Laplace es una herramienta muy útil para resolver problemas que en muchas ocasiones generan ecuaciones diferenciales muy complejas y por consiguiente, difíciles de resolver. Este tipo de ecuaciones se originan por ejemplo, durante el análisis de circuitos electrónicos.

Transformar significa convertir una cosa en otra distinta. Precisamente la Transformada de Laplace convierte una función en otra a través de un proceso de integración. Sin embargo, a pesar de ser una herramienta diseñada para facilitar la resolución de ecuaciones diferenciales, el proceso de encontrar la transformada de Laplace puede resultar complicado en algunos casos. Para auxiliarnos en la verificación de nuestros resultados cuando estamos calculando esta transformada, podemos utilizar Wolfram Alpha.

Supongamos que queremos calcular la siguiente transformada de Laplace:

Para obtener el resultado, introducimos en el recuadro de búsqueda de Wolfram Alpha (este cuadro de texto es un recuadro de borde color naranja que tiene un signo de "igual" del lado derecho) la expresión de la transformada como a continuación se muestra:

Laplace transform ((1 - e^t + 3e^(-4t)) * cos(5t))

El resultado que calcula Wolfram Alpha es el siguiente:

Además de generar el resultado, Wolfram Alpha también muestra la gráfica de la función para varios rangos de valores así como formas alternas del resultado y los pasos para obtenerlas.

En otros casos, es necesario calcular la transformada inversa de Laplace. Esta operación también puede hacerse mediante Wolfram Alpha.

Por ejemplo, ahora supongamos que queremos encontrar la transformada inversa de Laplace de la expresión:

En el mismo recuadro de búsqueda en Wolfram Alpha, tendríamos que introducir a la expresión de esta forma:

inverse Laplace transform (2s - 4)/((s^2 + s)(s^2 + 1))

El resultado de Wolfram Alpha es:

Esta expresión contiene un producto en el denominador, por lo que también podríamos haberla escrito indicando el producto mediante un asterisco (*) y obtendríamos el mismo resultado:

inverse Laplace transform (2s - 4)/((s^2 + s)*(s^2 + 1))

lunes, 28 de noviembre de 2011

Ordenar usando la interface Comparable

En este post revisaremos una duda que recibimos. El objetivo es leer un archivo que contiene en cada línea datos que representan una instancia. Por ejemplo el siguiente archivo (productos.txt) contiene:

555 Producto1 200 3
333 Producto2 300 1
222 Producto3 400 2
111 Producto4 150 5

Para representar un producto, creamos una clase Producto que contiene un id, nombre, precio y cantidad en inventario. Y deseamos ordenarlo por id del producto. El tip es que la clase Producto debe implementar la interface Comparable para que podamos comparar entre productos y de esta forma puedan ser ordenados.

El siguiente código muestra cómo ordenar el archivo anterior (copiar el código en un archivo con nombre OrdenarArchivo.java), los pasos son muy sencillos:

  1. leemos cada línea del archivo y la dividimos en los campos de un producto (se asume que los campos están separados por un espacio en blanco);
  2. creamos una instancia de Producto y la agregamos a una lista de Productos;
  3. ordenamos la lista usando Collections.sort

Es importante notar que tras bambalinas Collections.sort está usando el método compareTo (de la interface Comparable) que implementamos en la clase Producto. Al ejecutar el código debe imprimir:

Elementos originales:
[555 Producto1 200.0 3, 333 Producto2 300.0 1, 
222 Producto3 400.0 2, 111 Producto4 150.0 5]
Elementos ordenados:
[111 Producto4 150.0 5, 222 Producto3 400.0 2, 
333 Producto2 300.0 1, 555 Producto1 200.0 3]

OrdenarArchivo.java

import java.io.*;
import java.util.*;

public class OrdenarArchivo{
  public static void main(String [] args){
    String filename = "productos.txt";
    String linea = null;
    String[] datos = null;
    List<Producto> productos = new ArrayList<Producto>();
    Producto producto = null;
    try{
      BufferedReader br = new BufferedReader(
                           new FileReader(filename)); 
      // leer todas la lineas del archivo
      while((linea=br.readLine())!=null){
        // cada linea tiene los datos para crear un producto
        datos = linea.split(" ");
        producto = new Producto(
                    Integer.parseInt(datos[0]),
                    datos[1],
                    Double.parseDouble(datos[2]),
                    Integer.parseInt(datos[3]));
        // agregamos el producto a la lista de productos
        productos.add(producto);
      }
      br.close();
      // imprimir archivo en orden original
      System.out.println("Elementos originales:");
      System.out.println(productos);
      Collections.sort(productos);
      // imprimir archivo ordenado
      System.out.println("Elementos ordenados:");
      System.out.println(productos);
    }catch(IOException e){
      System.out.println(e);   
    }
  }
}

// Clase que representa lo que queremos ordenar
// Notar que estamos implementado Comparable
class Producto implements Comparable<Producto> {
  int id;
  String nombre;
  double precio;
  int inventario;
  
  public Producto(int id, String nombre, 
                  double precio, int inventario){
    this.id = id;
    this.nombre = nombre;
    this.precio = precio;
    this.inventario = inventario;
  }
  
  public String toString(){
    return id + " " + nombre + " " + 
           precio + " " + inventario;
  }
  
  // Este metodo es el que nos permite comparar
  // entre productos y de esta forma puedan ser ordenados.
  public int compareTo(Producto p){
    return id - p.id;
  }
}

sábado, 26 de noviembre de 2011

Modificadores en Java

A continuación podrán encontrar una tabla que resume en dónde es válido usar varios modificadores en Java. Por ejemplo una clase normal (es decir no anidada en otra clase o método) sólo tiene dos modificadores de acceso válidos: public y default; una clase anidada en otra clase (ya sea estática o no estática) puede usar private, default, protected, public; y una clase definida dentro de un método no puede usar modificadores de acceso.

Modificadores en Java.

Si encuentran algún error, por favor repórtenlo en los comentarios.

lunes, 14 de noviembre de 2011

CyArk: preservación digital de nuestro pasado

CyArk (Cyber Archive o Ciber Archivo) es una organización sin fines de lucro cuyo objetivo es la preservación digital de sitios considerados como patrimonio histórico y cultural de la humanidad.

La preservación de estos lugares se lleva a cabo mediante recursos de alta tecnología entre los cuales se encuentra el uso del escaneo a través de láser en 3D, lo que produce una serie de puntos que son transformados a una imagen tridimensional del área escaneada y cuya precisión se encuentra entre los 2 y 3 mm. El escaneo se lleva a cabo en cuestión de minutos y los datos que son colectados se utilizan para producir representaciones espectacularmente precisas de los sitios escaneados.

Actualmente, CyArk está llevando a cabo proyectos de preservación en varios lugares alrededor del mundo y México no es la excepción. En nuestro país, se han llevado a cabo proyectos sumamente interesantes en las ciudades mayas de Chichén Itzá, Tikal, la capital zapoteca Monte Albán, así como un proyecto en progreso para preservar Teotihuacán, la Ciudad de los Dioses.

CyArk es un proyecto que merece la pena dar a conocer y más aún cuando se cuenta con hermosos sitios dignos de preservación para conocimiento y goce de las futuras generaciones. En México somos afortunados porque existe una gran variedad de estos lugares y vale la pena saber que se están realizando esfuerzos para preservarlos.

Visiten el sitio de CyArk, entérense de la maravillosa obra de esta organización y de alguna manera, comencemos a involucrarnos.

CyArk lanza página web en español

Página principal de CyArk

Sitios de Patrimonio: Monte Albán

Información para contribuir con el proyecto de CyArk

Preservación digital de Chichén Itzá

jueves, 10 de noviembre de 2011

Recursos Java Recomendados

Durante los últimos meses me estuve preparando para la certificación de Programador Java (Oracle Certified Professional Java Programmer). Sin lugar a dudas el libro recomendado es SCJP Sun Certified Programmer for Java 6 por Katherine Sierra y Bert Bates. Sin embargo, algunos tópicos requirieron materiales extra. Aún cuando existe una cantidad abrumadora de recursos y tutoriales acerca de Java, me gustaría compartir cuatro de los sitios que fueron de gran ayuda durante mi preparación.

1) Especificación de la API de Java

Fue de gran ayuda para estudiar métodos y parámetros, por ejemplo de las clases “Wrapper” (Integer, Double, Long, …). Además siempre que estemos desarrollando en Java debemos tener a la mano esta API.

2) Jdocs

El propósito de este sitio es presentar una versión aumentada de la tradicional API de Java. Lo que realmente me ayudó es que también permite visualizar el código fuente. Con ello entendí más detalles acerca del comportamiento de varias clases, en una ocasión lo usé para ver cómo estaban implementados los métodos equals, compareTo y hashCode de la clase Integer.

3) Vídeo Tutoriales por Clive Scott

Si son más del tipo de aprender a través de vídeo tutoriales, este sitio les fascinará. En youtube hay muchos tutoriales Java que generalmente cubren aspectos básicos o introductorios. Esta página presenta un compendio de vídeos que cubren a profundidad temas como overriding, overloading, clases anidadas, modificadores de acceso, entre otros. Si acabas de tomar alguna clase básica o intermedia de Java, estos tutoriales son de gran ayuda para repasar y profundizar varios temas. Altamente recomendable.

4) Tutoriales en mindprod.com

Este sitio contiene tutoriales que cubren tópicos con gran detalle. Por ejemplo, el tutorial acerca de la operación módulo menciona qué pasa si uno de los operandos es negativo (-10%4, 10%-4, -10%-4), presenta un par de tablas para poder comparar el módulo en java vs. el módulo de acuerdo a su definición matemática y menciona ejemplos de la utilidad de este operador. El tutorial de clases anidadas, además de conceptos básicos, tiene una tabla para mostrar los modificadores de acceso válidos para los diferentes tipos de clases y otra para mostrar las combinaciones de qué tipo de clases pueden heredar qué tipo de clases, por ejemplo una clase definida en un método sólo puede ser extendida por otra clase definida en el mismo método o una clase anónima en el mismo método. En general, un gran nivel de detalle con varios ejemplos.

5) Extra

Estos son algunos posts o tutoriales extras que también les recomiendo:

Por último, si alguien está interesado en certificarse les recomiendo los exámenes del sitio Whizlabs. Ofrece un examen de muestra. Y si deciden comprar el paquete de exámenes de práctica, les serán de gran utilidad ya que el nivel de dificultad es similar al examen de certificación. De hecho originalmente el examen tenía una duración de 3 horas, pero unos quince días antes de presentar mi examen, el tiempo límite cambió a 2.5 horas. Afortunadamente, gracias a los exámenes de práctica completé el examen de certificación en tiempo con 95% (57 de 60).

Espero que los recursos recomendados les sirvan y ayuden.

miércoles, 12 de octubre de 2011

Apuntes de la Unidad 2 de Matemáticas 5

En este apartado se encuentran los apuntes de la unidad 2 del curso de matemáticas 5 del Instituto Tecnológico de Toluca.

El examen de la segunda unidad abarca hasta donde aparece una nota dentro de los apuntes. Si existe alguna aclaración respecto a la legibilidad de éstos, no duden en comentarla.

Descargar apuntes dando click derecho y luego "Guardar como".

domingo, 2 de octubre de 2011

SQL Sakila: rentas por día

Nota: Para seguir mejor este ejemplo y ejecutar las consultas, les recomiendo seguir las instrucciones para configurar el ambiente de trabajo en la parte 1 del tutorial de SQL. Además, en el siguiente enlace pueden ver el modelo de Sakila versión 0.8.

En este ejemplo continuaremos usando la base de datos Sakila. El objetivo es obtener el promedio de rentas por día de la semana. Esto puede ser útil para lanzar una promoción en el día con menos rentas (por ejemplo: martes de 2x1).

Usaremos la tabla rental que tiene un registro por cada renta realizada. El campo que nos interesa es rental_date que es de tipo fecha (datetime). Primero, tenemos que obtener el día de la semana para esa fecha, es decir, a partir de “3 de Julio de 2011” debemos obtener “Domingo”. La función WEEKDAY es justo lo que necesitamos, retorna 0 para lunes, 1 para martes, ... 6 para domingo. La siguiente consulta retorna el día de la semana cuando se realizó la renta.

SELECT WEEKDAY(rental_date) AS wday FROM rental;

Ahora, todo lo que tenemos que hacer es agrupar y contar las ocurrencias para cada día de la semana.

mysql> select weekday(rental_date) as wday, count(*)
    -> from rental
    -> group by weekday(rental_date);

Noten que en la cláusula group by podemos usar el resultado de una función, en este caso weekday. La siguiente imagen muestra el resultado.

Podemos observar, que jueves es cuando se han realizado menos rentas en total con 2200. Noten que obtuvimos el total de rentas, pero ¿cómo podemos obtener el promedio por día? En otras palabras, hasta el momento sabemos que de todos los jueves en la tabla hay un total de 2200 rentas, pero no sabemos cuántas películas se rentan por jueves (por ejemplo, en un jueves se rentan normalmente 70 películas).

Recordemos que la tabla rental tiene una renta por registro. Lo primero que tenemos que hacer es agrupar las retas por día, es decir, una consulta que nos permita obtener: el 17 de Julio se realizaron 45 rentas. Sin embargo, un simple group by rental_date no funciona ya que rental_date contiene fecha y hora (dos rentas en el mismo día pero a diferente hora no serían agrupadas en el mismo día). Para ello utilizaremos la función DATE() que nos permite extraer sólo la fecha. La siguiente consulta agrupa por fecha.

mysql> select date(rental_date) as solo_fecha, count(*) as rentas 
    -> from rental 
    -> group by solo_fecha;

Ahora podemos agrupar por día de la semana y obtener un promedio. Además del promedio, también desplegamos el número de días y el total (es decir, el promedio por día x número de días, éste debe ser igual al total que obtuvimos en la primer consulta).

mysql> select weekday(solo_fecha) as wday, 
    ->        avg(rentas), count(*) as ndias, 
    ->        avg(rentas) * count(*) as total 
    -> from ( 
    -> select date(rental_date) as solo_fecha, count(*) as rentas 
    -> from rental 
    -> group by solo_fecha 
    -> ) agrupado_por_dia 
    -> group by wday;

La siguiente imagen muestra el resultado de la consulta.

En la primer consulta obtuvimos que el día con menos rentas en total era jueves; sin embargo, con esta consulta vemos que en promedio se rentan menos películas los martes. Esto se debe a que en la base de datos hay más días martes en comparación con el resto de los días.

Como pueden ver es posible manipular los datos de la base de diferentes formas para tener varias perspectivas de la misma información. Además existen muchas funciones predeterminadas que nos ayudan a transformar los datos, en este post vimos 2 funciones para la manipulación de fechas (DATE y WEEKDAY).

Compartan sus consultas favoritas de la BD Sakila o describan alguna consulta que les gustaría que explicáramos en un post.

sábado, 24 de septiembre de 2011

Ejercicios de Matemáticas V (Parte 2)

En este post podrán descargar la segunda parte de los ejercicios de Matemáticas 5 del Instituto Tecnológico de Toluca, semestre Agosto-Diciembre de 2011.

Estos ejercicios son los 4 problemas de aplicación que la profesora dictó el viernes pasado. Simplemente da click sobre el enlace que se presenta a continuación.

Descargar Ejercicios: problemas de aplicación

domingo, 18 de septiembre de 2011

Videos y tutoriales para comenzar a programar el PIC 16F628A

A continuación se encuentra una serie de videos que encontré y que pueden ser muy útiles en la programación del PIC 16F628A, que es el que actualmente utilizamos en el curso de Arquitectura de Computadoras. Quizá alguno de ellos parezca demasiado sencillo e introductorio para quienes ya cuentan con un poco de experiencia, pero para los que aún somos neófitos en este tema, pueden ser de gran ayuda.

Estos son también tutoriales acerca de cómo utilizar MPLAB, la herramienta que manejaremos a la hora de programar el PIC. Por supuesto existen muchos más videos, así que si tienen alguna sugerencia será bienvenida.

Ejercicios Matemáticas V (Parte 1)

En el siguiente enlace pueden descargar la primera parte de los ejercicios de Matemáticas V (Ecuaciones Diferenciales). De acuerdo con la profesora, solamente faltan 2 temas por anexar pero podemos ir resolviendo los que se encuentran en esta primera parte.

Descargar ejercicios

domingo, 11 de septiembre de 2011

Diagrama Sakila DB

En septiembre del año pasado inicié una serie de SQL y posteriormente publiqué un par de posts describiendo cómo formular ciertas consultas (trivia sql y filtrar por columnas resultado de agrupado)

En el primer post de la serie de SQL, también proporcioné un enlace a un diagrama de la base de datos para tener mayor referencia al crear las consultas. Sin embargo, mientras preparaba un nuevo post, noté algo raro en el diagrama.

El archivo zip de sakila contiene tres archivos: uno con comandos para crear la base de datos, otro para poblarla y el último es un archivo del modelo de base de datos que se puede abrir con MySQL Workbench.

Hasta el momento no había usado el modelo de Workbench, pero al ver que las relaciones del diagrama parecían invertidas, decidí instalar MySQL Workbench y abrir el modelo. Esto me permitió corroborar que las relaciones del diagrama que se encuentra en la página principal de Sakila están invertidas.

Observen las siguientes dos imágenes. La primera es el diagrama en la página principal de Sakila: los símbolos de 'muchos' no están del lado de film_actor. La segunda es una imagen del modelo de Workbench: los símbolos de 'muchos' sí están del lado de film_actor.

Diagrama Página Sakila

Diagrama Workbench

Así que recomendaría mejor usar el modelo de Workbench (también subí la siguiente imagen del modelo de Sakila versión 0.8).

Ahora sí podemos hablar de la siguiente consulta en el próximo post.

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

martes, 5 de julio de 2011

ANSI C tarea: Uso de condicionales

Dentro de la programación existen estructuras que nos auxilian en caso de tener que tomar una decisión con respecto al flujo de nuestro programa. Es decir, puede existir el caso en el que debamos realizar ciertas operaciones si se cumple una condición o si ésta no se cumple.

Puedes manejar una decisión dentro de tu programa, de cuatro formas en C:

1. if...


   if(condición){
      realiza algo;
   }

2. if...else


   if(condición){
      realiza algo;
   }
   else{
      realiza otra acción distinta;
   }

3. ...?...:... (es como un if...else abreviado)


   (condicion) ? realiza algo : realiza otra acción distinta;

4. switch


   switch(valor obtenido despues de tomar una decision){
      case 1:
         Realiza una primera acción;
         break; (break es opcional: si solamente quieres que realice solo esta acción)
      case 2:
         Realiza una segunda acción;
         break;
      .
      .
      .
      default:
         Accion a realizar si ninguno de los otros casos se cumplió
         break; (buena práctica de programación colocar un break al final aunque no es necesario)
   }

Para ilustrar su uso, se encuentra el ejemplo siguiente de un programa que calcula la edad aproximada en días, horas, minutos y segundos de un usuario cuando éste introduce su fecha de nacimiento:


#include <stdio.h>

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

  //Variables
  int dia;
  int mes;
  int anio;
  int edadEnAnios;
  unsigned long edadEnDias;
  unsigned long edadEnHoras;
  unsigned long edadEnMinutos;
  unsigned long edadEnSegundos;
  int opcion;

  //Instrucciones para un usuario
  printf("Introduce tu fecha de nacimiento\n");
  printf("Dia (numero entre 1 y 31): \n");
  scanf("%d", &dia);
  printf("Mes (numero entre 1  y 12): \n");
  scanf("%d", &mes);
  printf("Anio (que no pase de 2011): \n");
  scanf("%d", &anio);

  /*
    Estas son solo algunas condiciones para verificar la fecha escrita por el 
    usuario pero podrian existir otras
   */
  //Condicion para verificar que el numero del dia sea valido
  if(dia < 1 || dia > 31){
    printf("Fecha no valida, el dia debe ser un numero entre 1 y 31\n");
  }
  //Condicion para verificar que el numero de mes sea valido
  else if(mes < 1 || mes > 12){
    printf("Fecha no valida, el mes debe ser un numero entre 1 y 12\n");
  }
  //Condicion para verificar que el anio sea valido
  else if(anio > 2010){
    printf("Para fines de este programa, el anio no puede ser mayor a 2010\n");
  }
  //Si el mes se encuentra entre Enero y Julio, los meses pares en este rango
  //solamente tienen 30 dias, excepto Febrero, que tiene menos
  else if(mes < 8 && (mes % 2 == 0) && (dia > 30)){
    printf("Fecha no valida, el mes que escribiste solo tiene 30 dias\n");
  }
  //Si el mes se encuentra entre Agosto y Diciembre, los meses impares en este
  //rango, tienen 30 dias
  else if(mes >= 8 && (mes % 2 != 0) && (dia > 30)){
    printf("Fecha no valida, el mes que escribiste solo tiene 30 dias\n");
  }
  //Si el mes es Febrero, es un anio que marca fin de siglo, es bisiesto y 
  //el dia escrito es mayor que 29, entonces no es una fecha valida
  else if((mes == 2) && (anio % 100 == 0) && (anio % 400 == 0) && (dia > 29)){
    printf("Fecha no valida, Febrero solo tiene 29 dias en el anio %d\n", anio);
  }
  //Si el mes es febrero, es un anio que marca fin de siglo, no es bisiesto y
  //el dia escrito es mayor que 28, entonces no es una fecha valida
  else if((mes == 2) && (anio % 100 == 0) && (anio % 400 != 0) && (dia > 28)){
    printf("Fecha no valida, Febrero solo tiene 28 dias en el anio %d\n", anio);
  }
  //Si el mes es febrero, no es un anio que marca fin de siglo, es anio 
  //bisiesto y el dia escrito es mayor que 29, no es una fecha valida
  else if((mes == 2) && (anio % 100 != 0) && (anio % 4 == 0) && (dia > 29)){
    printf("Fecha no valida, Febrero solo tiene 29 dias en el anio %d\n", anio);
  }
  //Si el mes es febrero, no es un anio que marca fin de siglo, no es 
  //bisiesto y el dia escrito es mayor que 28, no es una fecha valida
  else if((mes == 2) && (anio % 100 != 0) && (anio % 4 != 0) && (dia > 28) ){
    printf("Fecha no valida, Febrero solo tiene 28 dias en el anio %d\n", anio);
  }
  /*
    Si todas las condiciones anteriores verifican la validez de la fecha
    se procede a realizar calculos sencillos para poder estimar el numero
    de dias vividos por la persona cuya fecha de nacimiento se introdujo.
    Este programa no funciona cuando la persona tiene menos de un anio de 
    vida. Despues se despliega un menu de opciones.
  */
 
  else{
    
    edadEnAnios = 2011 - anio;
    edadEnDias = (edadEnAnios * 365);
    edadEnHoras = edadEnDias * 24;
    edadEnMinutos = edadEnHoras * 60;
    edadEnSegundos = edadEnMinutos * 60;

    //Menu
    printf("Selecciona una opcion:\n");
    printf("1. Conocer tu edad aproximada en dias\n");
    printf("2. Conocer tu edad aproximada en horas\n");
    printf("3. Conocer tu edad aproximada en minutos\n");
    printf("4. Conocer tu edad aproximada en segundos\n");

    scanf("%d", &opcion);
    
    //Acciones que realiza el programa dependiendo de la eleccion del
    //usuario
    switch(opcion){
    case 1:
      printf("Has vivido aproximadamente %lu dias\n", edadEnDias);
      break;
    case 2:
      printf("Has vivido aproximadamente %lu horas\n", edadEnHoras);
      break;
    case 3:
      printf("Has vivido aproximadamente %lu minutos\n", edadEnMinutos);
      break;
    case 4:
      printf("Has vivido aproximadamente %lu segundos\n", edadEnSegundos);
      break;
    default:
      printf("Opcion no valida.\n");
      break;
    }
  }
}

Algunos ejemplos de la salida de este programa son los siguientes:

Este programa solamente aproxima el número de días que podría haber vivido una persona. No toma en cuenta años bisiestos y además se basa en el número de años transcurridos desde la fecha de nacimiento de la persona, por lo que si alguien introduce una fecha cuyo año sea el año en curso (2011), no se realizan los cálculos adecuados y se obtendrá que la persona ha vivido 0 días cuando probablemente haya vivido 100 días. Sin embargo, sí realiza algunas validaciones en cuanto a la fecha de nacimiento introducida por el usuario pero su objetivo principal no es el cálculo de los días, horas, minutos y segundos exactos, sino la demostración del uso de los condicionales y cómo éstos pueden anidarse o combinarse de distintas maneras.

Referencias

"Leap year". Wikipedia. The Free Encyclopedia. 2011

Decisions. The GNU C Programming Tutorial, Edition 4.1

ANSI C extra: while, do while y for pueden ser equivalentes

Las estructuras de tipo cíclo en la lógica de programación como lo son el While, el Do While y el For, pueden ser utilizadas indistintamente en muchas ocasiones. En el ejemplo siguiente, podemos observar que sin importar cuál de estas estructuras elijamos, el resultado será el mismo.

El ejemplo imprime los múltiplos de 3 menores que 30 comenzando por el número 3. Las tres estructuras realizan un proceso similar: existe una inicialización de la variable a un cierto valor (en este caso a 3), se realiza un proceso que involucra a esta variable (en nuestro ejemplo se imprime su valor) y además, hay una verificación de la validez de la condición que nos permitirá seguir imprimiendo el valor de la variable o nos sacará del ciclo (que para nuestro ejemplo será el momento en el que la variable supere el valor de 30).

Veamos el código fuente del ejemplo:


#include <stdio.h>


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

  //Programa para demostrar que los ciclos for, while y do while 
  //pueden ser utilizados para obtener los mismos resultados

  int variable = 3;

  //Imprimir los multiplos de 3 menores que 30 utilizando un while
  printf("Multiplos de 3 menores que 30 generados utilizando un while:\n");
  while(variable < 30){
    printf("%2d\n", variable);
    variable += 3;
  }

  //Limpiar la variable para poder reutilizarla
  variable = 3;

  //Imprimir los multiplos de 3 menores que 30 utilizando un do while
  printf("Multiplos de 3 menores que 30 generados utilizando un do while:\n");
  do{
    printf("%2d\n", variable);
    variable += 3;
  }while((variable + 3) < 30);

  //Imprimir los multiplos de 3 menores que 30 utilizando un for
  printf("Multiplos de 3 menores que 30 generados utilizando un for:\n");
  //El ciclo for inicializa la variable a 3, por lo que no es necesario
  //limpiarla
  for(variable = 3; variable < 30; variable += 3){
    printf("%2d\n", variable);
  }
}

Como puedes observar, el While, Do While y el For guardan muchas similitudes. Este es el resultado de correr el programa:

Podemos entonces afirmar, que el uso de estas estructuras depende de la elección del programador: se puede llegar al mismo resultado con cualquiera de las 3.

domingo, 3 de julio de 2011

ANSI C extra: Construye tu propio sistema numérico

De acuerdo con lo que hemos aprendido acerca del sistema binario y las reglas para representar un número utilizando una base de dos dígitos, es fácil pensar que quizá entonces podemos utilizar cualquier otro número para organizar nuestro propio sistema numérico.

Si recordamos, podemos representar un número en nuestro sistema decimal de la siguiente manera: 145 = 1 x (10^2) + 4 x (10^1) + 5 x (10^0). Entonces, siguiendo esta misma lógica, un número binario, como el 101 es equivalente a: 1 x (2^2) + 0 x (2^1) + 1 x (2^0) y aquí podemos ver que en realidad no importa el número que sea tomado como base, ya sea el 2 o el 10 o cualquier otro, podemos construir un sistema numérico fácilmente.

Tomemos por ejemplo el número 5. En primer lugar nuestro sistema numérico contará con los dígitos: 0, 1, 2, 3, y 4 (cinco en total, como lo indica nuestra base) ahora, un número en expresado en el sistema con base 5, representa una multiplicación entre los dígitos con los que cuenta y una potencia de la base, por ejemplo, el número 332 en base cinco, representa lo siguiente: 3 x (5^2) + 3 x (5^1) + 2 x (5^0). Y algunos de los números que podemos formar con este sistema de base 5 son: 0, 1, 2, 3, 4, 10, 11, 12, 13, 14, 20, que equivalen en el sistema decimal a 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. En el caso de bases que sean mayores que 10, como por ejemplo en el sistema hexadecimal, cuya base es 16, por convención se utilizan letras mayúsculas. Así en el sistema hexadecimal la base está formada por los dígitos del 0 al 9 y por las letras de la A a la F, donde A representaría el 10 en nuestro sistema decimal y F el 15.

Podemos divertirnos utilizando un sistema numérico que contenga la base de nuestra elección, por ejemplo, la fecha de hoy es 3/07/2011, si la expresamos en sistema de base 5, podríamos decir que hoy es el 03/12/31021.

O quizá si utilizamos la base 3 y alguien tiene 20 años de edad, podemos expresar esta cantidad en base 3 y decir que esa persona tiene ¡202 años!.

Te invito a que elijas un número y te diviertas utilizándolo como base para tu sistema numérico personal.

viernes, 1 de julio de 2011

ANSI C Extra: Emacs cheat sheet

Durante este curso de verano de ANSI C impartido en la Universidad Autónoma de Nuevo León he mencionado que utilizamos un editor llamado Emacs para codificar nuestros ejemplos. En esta ocasión me gustaría presentar un poco de información acerca de este editor así como una hoja donde se encuentran algunos comandos básicos que pueden ser de utilidad cuando apenas se está aprendiendo a utilizarlo.

Emacs es un editor de texto que se caracteriza por su extensibilidad. Esto significa que está desarrollado de manera que facilita su desarrollo futuro, y debido a su naturaleza abierta, como usuarios podemos tener acceso a su código fuente y realizar contribuciones que ayuden a mejorar la versión existente así como modificaciones que nos permitan un manejo más fácil o el acceso y personalización a características que consideramos importantes.Cuenta con más de 1000 comandos que permiten al usuario combinarlos para generar macros con los cuales poder automatizar tareas. El desarrollo de esta herramienta comenzó a mediados de los años 70 y aún hoy, en el 2011 continúa en evolución.

La versión más popular de Emacs en la actualidad es GNU Emacs. Como dato curioso, GNU tiene una definición recursiva, ya que significa: GNU no es Unix. La segunda versión más popular de Emacs es XEmacs, desarrollada a finales de los años 80s.

Por la cantidad enorme de comandos con los que cuenta Emacs, es muy útil contar con alguna ficha donde se muestren los comandos más esenciales y conservarla a nuestro alcance para poder utilizarla mientras realizamos alguna tarea. A la hoja donde concentré estos comandos es a la que llamé Emacs cheat sheet.

Una vez que descarguen la hoja, espero sus comentarios o sugerencias con el fin de mejorarla.

Descargar Emacs cheat sheet

Referencias

"Emacs". Wikipedia. The Free Enciclopedia. 2011

Emacs Cheat Sheet. Original version by David Cohen, revised by Bob Rogers.

jueves, 30 de junio de 2011

ANSI C: Sistema binario y operaciones binarias

La mejor manera de demostrar el entendimiento sobre un tema, es explicándolo en términos sencillos. Debido a esto, para abordar el tema del Sistema Binario y algunas de sus operaciones más importantes, decidí realizar el siguiente mini tutorial dividido en 5 lecciones. Este tutorial, aunque contiene en su mayoría texto, decidí realizarlo como si fuera un programa, con el objetivo de practicar el uso del editor emacs en Linux, que es el editor utilizado durante el curso de verano de ANSI C y practicar la sintaxis y estructura del lenguaje.

Una vez compilado el código, obtendremos la siguiente salida en la terminal:

******************************************
Aprendiendo el idioma de las computadoras
******************************************
Bienvenido!! Aqui podras aprender acerca del
idioma que utilizan las computadoras para interpretar
el mundo que nos rodea.


LECCION 1: QUE ES EL SISTEMA BINARIO

Podria decirse que el sistema binario es el idioma
que hablan las computadoras. A traves de este idioma
las computadoras interpretan el mundo que nos rodea.

En que consiste este idioma?
Una computadora contiene muchos circuitos electronicos
en su interior. Estos circuitos solamente pueden hacer
dos cosas: apagarse o encenderse, por lo tanto, 
a la computadora le basta saber dos simbolos: uno que
represente a "apagado"y otro para representar
a "encendido".

Los dos simbolos que forman el idioma de las computadoras
son el cero y el uno. Asi de sencillo!!!


Sabias que...
El primer registro de la palabra "computador" data de 1613
y se referia a la persona que realizaba calculos.


LECCION 2: COMO VE LOS NUMEROS LA COMPUTADORA

Si solamente pudieras utilizar unos y ceros para contar,
como le harias? Este es un dilema al que se enfrentan
nuestras computadoras todos los dias de sus vidas.
Y aunque parezca algo muy dificil, escribir numeros con
unos y ceros es casi lo mismo que escribir los numeros
como estamos acostumbrados.

Los numeros que utilizamos de manera cotidiana estan formados
por diez digitos: 0,1,2,3,4,5,6,7,8 y 9.
El sistema binario solamente tiene dos digitos: 0 y 1.
Cuando contamos con nuestros diez digitos, despues del 9
escribimos un 1 y colocamos un 0 a la derecha para formar
el numero que le sigue a 9.
Con el sistema binario ocurre lo mismo: cuando llegamos al 1
y como no existe el 2, volvemos a escribir 1 seguido
 de un 0 a la derecha. Asi se representan el 0, 1, 2 y 3 que conocemos
en el sistema binario:
Decimal Binario
0 0
1 1
2 10
3 11

Como te imaginas que se representa entonces el 4?
En este punto ya volvimos a usar nuestras dos cifras,
por lo tanto, volvemos a colocar el 1, pero esta vez
seguido por dos ceros para representar al 4: 100.
Asi se representan el 4, 5, 6 y 7 en binario:
Decimal Binario
4 100
5 101
6 110
7 111

Puedes adivinar como se escribe entonces el 8?
Pues el 8 se escribe asi: 1000. Los numeros que le siguen
se escriben como si comenzara a entrar un 1 por la derecha
y se detuviera hasta encontrarse con el 1 original.

Pero que pasaria si solamente pudieras ver el numero
cuando esta escrito en binario? Como lo cambiarias a decimal?
Cuando aprendemos a contar olvidamos que todos los numeros
se pueden representar como sumas de numeros mas pequenios,
por ejemplo: 123 = 100 + 20 + 3
Ahora bien, 100 se puede ver tambien de la siguiente manera: 
100 = 1 x 10 x 10. La multiplicacion de 10 x 10 es lo mismo que
elevar el 10 a la potencia 2. Igualmente, 20 tambien se escribe
como una multiplicacion de 10: 20 = 2 x 10. El 10 solito es
lo mismo que elevar el 10 a la potencia 1. Finalmente, el 3
es: 3 x el 10 elevado a la potencia 0, pero como todo numero
elevado a la potencia 0 es siempre 1, entonces nos queda 3 x 1.
La razon por la que utilizamos 10 es porque nuestro sistema decimal
esta formado por 10 digitos. Entonces, en el sistema binario, en vez
de utilizar el 10, utilizamos el 2 de la misma manera. Asi, si
tuvieramos este numero binario: 1001, podemos saber cual es su
representacion decimal a traves de sumas y multiplicaciones con 2:
1001 = 1 x 2 elevado a la potencia 3 + 
       0 x 2 elevado a la potencia 2 + 
       0 x 2 elevado a la potencia 1 + 
       1 x 2 elevado a la potencia 0

Es decir: 1001 = 1 x 8  +  0 x 4  +  0 x 2  +  1 x 1 = 8 + 1 = 9

Ahora te toca a ti:
Ejercicio 1: Como se representa el 1256 en binario?
Ejercicio 2: Como se representa el 1110001 en decimal?


LECCION 3: COMO VE LAS LETRAS LA COMPUTADORA

En la leccion anterior observamos la forma en que la
computadora ve los numeros que ocupamos en nuestro sistema
decimal. Ahora sabremos como puede interpretar nuestras letras.
Si recordamos que la computadora solamente "conoce"
ceros y unos, entonces, como puede entender una letra?
Pues bien, para que una computadora pueda entender una letra
es necesario que esta se convierta en un numero decimal,
y como ya sabemos transformar decimales a binarios,
al llegar a este punto podemos decir que entonces conocemos
la representacion de una letra en la computadora.
Los numeros decimales que representan a una letra, estan definidos
dentro de un estandar llamado codigo ASCII. En internet se puede
encontrar la tabla de correspondencias para averiguar cuales son
los valores decimales que se utilizan para las letras. Por ejemplo:
consultando la tabla ASCII, observamos que la letra A (mayuscula)
se puede representar con el 65. Por lo tanto, cuando escribes
texto, y tecleas esta letra, lo que en realidad ve la computadora
es: 1000001, que es la representacion binaria de 65.

Ahora te toca a ti:
Ejercicio 3: Si tu fueras una computadora,
Como verias este grupo de letras?: H o L a
Consulta la tabla ASCII para obtener los valores necesarios.


LECCION 4: COMO MULTIPLICAR SIN MULTIPLICACION Y COMO DIVIDIR
SIN DIVISION

Observa que mientras mas grande sea un numero, su representacion
binaria es cada vez mas larga. Y mientras mas hacia la izquierda
se encuentra un digito, mayor sera su valor. Entonces, que pasa
cuando recorremos un numero binario hacia la izquierda en una
posicion y agregamos un cero en el extremo derecho para que ocupe
ese lugar que se recorrio? Observa con atencion:
Supongamos que tenemos el numero 4 en binario: 100 y ahora
recorremos todos sus digitos una posicion a la izquierda y ademas
agregamos un cero en el extremo derecho: 1000. Este nuevo numero
es el 8 en decimal que equivale a multiplicar 4 por 2. Entonces
al empujar un numero binario hacia la izquierda, lo que estamos
haciendo es duplicar ese numero tantas veces como hayamos recorrido
sus cifras a la izquierda, si por ejemplo recorremos el numero 1
(1 en binario) en dos posiciones a la izquierda obtendremos: 100
(4 en decimal) que equivale a duplicar el 1 dos veces, es decir:
1 x 2 x 2.

Haciendo corrimientos hacia la izquierda se puede duplicar un numero
binario. Te imaginas lo que pasa si los corrimientos los hacemos
hacia la derecha?. Como la derecha es el opuesto a la izquierda,
asi tambien ocurre lo opuesto al recorrer un numero binario a la
derecha: en lugar de aumentarle digitos, los eliminamos y en lugar
de multiplicar el resultado tantas veces sea necesario por 2, ahora
tendremos que dividir entre 2 el numero de veces que hayamos
recorrido nuestras cifras. Por ejemplo: el numero 8 en binario
es 1000, si recorremos estas cifras 2 veces hacia la derecha,
obtenemos 10, que es el 2 en decimal y que se obtiene al dividir
al 8 dos veces entre 2: 8/2 y el resultado otra vez entre 2.
Estas operaciones de recorrer cifras se conocen como Empujes y
tal como se vio, los empujes sirven para multiplicar o dividir
un numero de una manera poco convencional.

Ahora te toca a ti:
Ejercicio 4: Cual es el resultado de empujar al numero 14
3 posiciones hacia la izquierda?
Ejercicio 5: Cual es el resultado de empujar al numero 27
4 posiciones a la derecha?


LECCION 5: EXPLORANDO LA LOGICA DE LAS COMPUTADORAS

Existen otro tipo de operaciones que las computadoras pueden
realizar con los digitos de un numero binario. Estas operaciones
se llaman Operaciones Logicas Binarias. Se les llama logicas
porque trabajan con valores de verdadero y falso. Estos valores
tambien pueden ser representados mediante el 1 (para verdadero)
y el 0 (para falso).
La primera de ellas es el Y. Esta operacion tiene la caracteristica
de que solamente arroja como resultado el valor de verdadero cuando
trabaja con dos valores que tambien son verdaderos. En binario,
entonces el resultado solamente es 1 cuando los dos valores que
utiliza la operacion Y son 1 y 1. Como se realiza esta operacion?
Supongamos que tenemos el numero 110 y el numero 101, la operacion
Y compara digito a digito y produce un resultado dependiendo de sus
valores y comenzando de derecha a izquierda, asi: 110 & 101 arroja
como resultado: 100. El simbolo de esta operacion es el & (ampersand).

La segunda operacion es el O binario, que se representa con el simbolo
|, esta operacion arroja falso (es decir 0) solamente cuando ambos
valores son 0, en los demas casos, basta con que uno de ellos sea 1
para que obtengamos verdadero (es decir 1). Por ejemplo, si tenemos
los numeros 1001 y 1100 y realizamos la operacion de O entre ellos:
1001 | 1100 el resultado es: 1101.

La tercera operacion que analizaremos es la negacion o el NO.
Se representa con el simbolo: ~.
Esta operacion significa colocar el valor contrario
al valor que se tenga. Por ejemplo, para el numero 101, al
aplicar la negacion, el resultado es: 010. Observa que en esta
operacion no es necesario tener dos numeros para realizarla y que
es muy sencilla: cambia los 1s por 0s y los 0s por 1s.

Otra operacion es el llamado O exclusivo, representado por ^.
Con el O exclusivo, podemos obtener verdadero siempre y cuando
los valores a comparar sean diferentes, es decir sean 0 y 1.
Por ejemplo, teniendo los numeros 1010 y 0101, al aplicar el O
exclusivo obtendremos: 1010 ^ 0101 = 1111 puesto que
todas sus cifras son diferentes

Ahora te toca a ti:
Ejercicio 6: Realiza las cuatro operaciones logicas vistas
en esta leccion con los siguientes numeros y escribe su valor
en decimal: 11001101011, 1101110111


RESPUESTAS A LOS EJERCICIOS

Ejercicio 1: El numero 1256 en binario es 10011101000.
Ejercicio 2: El numero 1110001 en decimal es 113.
Ejercicio 3: Las letras H o L a se verian de la siguiente
manera: 1001000 1101111 1001100 1100001.
Ejercicio 4: 112
Ejercicio 5: 1
Ejercicio 6:
11001101011 = 1643
1101110111 = 887
1643 & 887 = 611
1643 | 887 = 1919
~1643 = 4294965652
~887 = 4294966408
1643 ^ 887 = 1308

El código fuente es el siguiente:


#include <stdio.h>


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

  printf("******************************************\n");
  printf("Aprendiendo el idioma de las computadoras\n");
  printf("******************************************\n");
  
  printf("Bienvenido!! Aqui podras aprender acerca del\n");
  printf("idioma que utilizan las computadoras para interpretar\n");
  printf("el mundo que nos rodea.\n\n\n");

  printf("LECCION 1: QUE ES EL SISTEMA BINARIO\n\n");
  printf("Podria decirse que el sistema binario es el idioma\n");
  printf("que hablan las computadoras. A traves de este idioma\n");
  printf("las computadoras interpretan el mundo que nos rodea.\n\n");
  printf("En que consiste este idioma?\n");
  printf("Una computadora contiene muchos circuitos electronicos\n");
  printf("en su interior. Estos circuitos solamente pueden hacer\n");
  printf("dos cosas: apagarse o encenderse, por lo tanto, \n");
  printf("a la computadora le basta saber dos simbolos: uno que\n");
  printf("represente a \"apagado\"y otro para representar\n");
  printf("a \"encendido\".\n\n");
  printf("Los dos simbolos que forman el idioma de las computadoras\n");
  printf("son el cero y el uno. Asi de sencillo!!!\n\n\n");

  printf("Sabias que...\n");
  printf("El primer registro de la palabra \"computador\" data de 1613\n");
  printf("y se referia a la persona que realizaba calculos.\n\n\n");

  printf("LECCION 2: COMO VE LOS NUMEROS LA COMPUTADORA\n\n");
  printf("Si solamente pudieras utilizar unos y ceros para contar,\n");
  printf("como le harias? Este es un dilema al que se enfrentan\n");
  printf("nuestras computadoras todos los dias de sus vidas.\n");
  printf("Y aunque parezca algo muy dificil, escribir numeros con\n");
  printf("unos y ceros es casi lo mismo que escribir los numeros\n");
  printf("como estamos acostumbrados.\n\n");

  printf("Los numeros que utilizamos de manera cotidiana estan formados\n");
  printf("por diez digitos: 0,1,2,3,4,5,6,7,8 y 9.\n");
  printf("El sistema binario solamente tiene dos digitos: 0 y 1.\n");
  printf("Cuando contamos con nuestros diez digitos, despues del 9\n");
  printf("escribimos un 1 y colocamos un 0 a la derecha para formar\n");
  printf("el numero que le sigue a 9.\n");
  printf("Con el sistema binario ocurre lo mismo: cuando llegamos al 1\n");
  printf("y como no existe el 2, volvemos a escribir 1 seguido\n ");
  printf("de un 0 a la derecha. Asi se representan el 0, 1, 2 y 3 que conocemos\n");
  printf("en el sistema binario:\n");
  printf("Decimal\tBinario\n");
  printf("0\t0\n");
  printf("1\t1\n");
  printf("2\t10\n");
  printf("3\t11\n\n");
  
  printf("Como te imaginas que se representa entonces el 4?\n");
  printf("En este punto ya volvimos a usar nuestras dos cifras,\n");
  printf("por lo tanto, volvemos a colocar el 1, pero esta vez\n");
  printf("seguido por dos ceros para representar al 4: 100.\n");
  printf("Asi se representan el 4, 5, 6 y 7 en binario:\n");

  printf("Decimal\tBinario\n");
  printf("4\t100\n");
  printf("5\t101\n");
  printf("6\t110\n");
  printf("7\t111\n\n");

  printf("Puedes adivinar como se escribe entonces el 8?\n");
  printf("Pues el 8 se escribe asi: 1000. Los numeros que le siguen\n");
  printf("se escriben como si comenzara a entrar un 1 por la derecha\n");
  printf("y se detuviera hasta encontrarse con el 1 original.\n\n");

  printf("Pero que pasaria si solamente pudieras ver el numero\n");
  printf("cuando esta escrito en binario? Como lo cambiarias a decimal?\n");
  printf("Cuando aprendemos a contar olvidamos que todos los numeros\n");
  printf("se pueden representar como sumas de numeros mas pequenios,\n");
  printf("por ejemplo: ");
  printf("123 = 100 + 20 + 3\n");
  printf("Ahora bien, 100 se puede ver tambien de la siguiente manera: \n");
  printf("100 = 1 x 10 x 10. La multiplicacion de 10 x 10 es lo mismo que\n");
  printf("elevar el 10 a la potencia 2. Igualmente, 20 tambien se escribe\n");
  printf("como una multiplicacion de 10: 20 = 2 x 10. El 10 solito es\n");
  printf("lo mismo que elevar el 10 a la potencia 1. Finalmente, el 3\n");
  printf("es: 3 x el 10 elevado a la potencia 0, pero como todo numero\n");
  printf("elevado a la potencia 0 es siempre 1, entonces nos queda 3 x 1.\n");
  printf("La razon por la que utilizamos 10 es porque nuestro sistema decimal\n");
  printf("esta formado por 10 digitos. Entonces, en el sistema binario, en vez\n");
  printf("de utilizar el 10, utilizamos el 2 de la misma manera. Asi, si\n");
  printf("tuvieramos este numero binario: 1001, podemos saber cual es su\n");
  printf("representacion decimal a traves de sumas y multiplicaciones con 2:\n");
  printf("1001 = 1 x 2 elevado a la potencia 3 + \n");
  printf("       0 x 2 elevado a la potencia 2 + \n");
  printf("       0 x 2 elevado a la potencia 1 + \n");
  printf("       1 x 2 elevado a la potencia 0\n\n");

  printf("Es decir: 1001 = 1 x 8  +  0 x 4  +  0 x 2  +  1 x 1 = 8 + 1 = 9\n\n");
  printf("Ahora te toca a ti:\n");
  printf("Ejercicio 1: Como se representa el 1256 en binario?\n");
  printf("Ejercicio 2: Como se representa el 1110001 en decimal?\n\n\n");

  printf("LECCION 3: COMO VE LAS LETRAS LA COMPUTADORA\n\n");
  printf("En la leccion anterior observamos la forma en que la\n");
  printf("computadora ve los numeros que ocupamos en nuestro sistema\n");
  printf("decimal. Ahora sabremos como puede interpretar nuestras letras.\n");
  printf("Si recordamos que la computadora solamente \"conoce\"\n");
  printf("ceros y unos, entonces, como puede entender una letra?\n");
  printf("Pues bien, para que una computadora pueda entender una letra\n");
  printf("es necesario que esta se convierta en un numero decimal,\n");
  printf("y como ya sabemos transformar decimales a binarios,\n");
  printf("al llegar a este punto podemos decir que entonces conocemos\n");
  printf("la representacion de una letra en la computadora.\n");
  printf("Los numeros decimales que representan a una letra, estan definidos\n");
  printf("dentro de un estandar llamado codigo ASCII. En internet se puede\n");
  printf("encontrar la tabla de correspondencias para averiguar cuales son\n");
  printf("los valores decimales que se utilizan para las letras. Por ejemplo:\n");
  printf("consultando la tabla ASCII, observamos que la letra A (mayuscula)\n");
  printf("se puede representar con el 65. Por lo tanto, cuando escribes\n");
  printf("texto, y tecleas esta letra, lo que en realidad ve la computadora\n");
  printf("es: 1000001, que es la representacion binaria de 65.\n\n");

  printf("Ahora te toca a ti:\n");
  printf("Ejercicio 3: Si tu fueras una computadora,\n");
  printf("Como verias este grupo de letras?: H o L a\n");
  printf("Consulta la tabla ASCII para obtener los valores necesarios.\n\n\n");

  printf("LECCION 4: COMO MULTIPLICAR SIN MULTIPLICACION Y COMO DIVIDIR\n");
  printf("SIN DIVISION\n\n");

  printf("Observa que mientras mas grande sea un numero, su representacion\n");
  printf("binaria es cada vez mas larga. Y mientras mas hacia la izquierda\n");
  printf("se encuentra un digito, mayor sera su valor. Entonces, que pasa\n");
  printf("cuando recorremos un numero binario hacia la izquierda en una\n");
  printf("posicion y agregamos un cero en el extremo derecho para que ocupe\n");
  printf("ese lugar que se recorrio? Observa con atencion:\n");
  printf("Supongamos que tenemos el numero 4 en binario: 100 y ahora\n");
  printf("recorremos todos sus digitos una posicion a la izquierda y ademas\n");
  printf("agregamos un cero en el extremo derecho: 1000. Este nuevo numero\n");
  printf("es el 8 en decimal que equivale a multiplicar 4 por 2. Entonces\n");
  printf("al empujar un numero binario hacia la izquierda, lo que estamos\n");
  printf("haciendo es duplicar ese numero tantas veces como hayamos recorrido\n");
  printf("sus cifras a la izquierda, si por ejemplo recorremos el numero 1\n");
  printf("(1 en binario) en dos posiciones a la izquierda obtendremos: 100\n");
  printf("(4 en decimal) que equivale a duplicar el 1 dos veces, es decir:\n");
  printf("1 x 2 x 2.\n\n");
  printf("Haciendo corrimientos hacia la izquierda se puede duplicar un numero\n");
  printf("binario. Te imaginas lo que pasa si los corrimientos los hacemos\n");
  printf("hacia la derecha?. Como la derecha es el opuesto a la izquierda,\n");
  printf("asi tambien ocurre lo opuesto al recorrer un numero binario a la\n");
  printf("derecha: en lugar de aumentarle digitos, los eliminamos y en lugar\n");
  printf("de multiplicar el resultado tantas veces sea necesario por 2, ahora\n");
  printf("tendremos que dividir entre 2 el numero de veces que hayamos\n");
  printf("recorrido nuestras cifras. Por ejemplo: el numero 8 en binario\n");
  printf("es 1000, si recorremos estas cifras 2 veces hacia la derecha,\n");
  printf("obtenemos 10, que es el 2 en decimal y que se obtiene al dividir\n");
  printf("al 8 dos veces entre 2: 8/2 y el resultado otra vez entre 2.\n");
  printf("Estas operaciones de recorrer cifras se conocen como Empujes y\n");
  printf("tal como se vio, los empujes sirven para multiplicar o dividir\n");
  printf("un numero de una manera poco convencional.\n\n");

  printf("Ahora te toca a ti:\n");
  printf("Ejercicio 4: Cual es el resultado de empujar al numero 14\n");
  printf("3 posiciones hacia la izquierda?\n");
  printf("Ejercicio 5: Cual es el resultado de empujar al numero 27\n");
  printf("4 posiciones a la derecha?\n\n\n");

  printf("LECCION 5: EXPLORANDO LA LOGICA DE LAS COMPUTADORAS\n\n");
  printf("Existen otro tipo de operaciones que las computadoras pueden\n");
  printf("realizar con los digitos de un numero binario. Estas operaciones\n");
  printf("se llaman Operaciones Logicas Binarias. Se les llama logicas\n");
  printf("porque trabajan con valores de verdadero y falso. Estos valores\n");
  printf("tambien pueden ser representados mediante el 1 (para verdadero)\n");
  printf("y el 0 (para falso).\n");
  printf("La primera de ellas es el Y. Esta operacion tiene la caracteristica\n");
  printf("de que solamente arroja como resultado el valor de verdadero cuando\n");
  printf("trabaja con dos valores que tambien son verdaderos. En binario,\n");
  printf("entonces el resultado solamente es 1 cuando los dos valores que\n");
  printf("utiliza la operacion Y son 1 y 1. Como se realiza esta operacion?\n");
  printf("Supongamos que tenemos el numero 110 y el numero 101, la operacion\n");
  printf("Y compara digito a digito y produce un resultado dependiendo de sus\n");
  printf("valores y comenzando de derecha a izquierda, asi: 110 & 101 arroja/n");
  printf("como resultado: 100. El simbolo de esta operacion es el & (ampersand).\n\n");

  printf("La segunda operacion es el O binario, que se representa con el simbolo\n");
  printf("|, esta operacion arroja falso (es decir 0) solamente cuando ambos\n");
  printf("valores son 0, en los demas casos, basta con que uno de ellos sea 1\n");
  printf("para que obtengamos verdadero (es decir 1). Por ejemplo, si tenemos\n");
  printf("los numeros 1001 y 1100 y realizamos la operacion de O entre ellos:\n");
  printf("1001 | 1100 el resultado es: 1101.\n\n");

  printf("La tercera operacion que analizaremos es la negacion o el NO.\n");
  printf("Se representa con el simbolo: ~.\n");
  printf("Esta operacion significa colocar el valor contrario\n");
  printf("al valor que se tenga. Por ejemplo, para el numero 101, al\n");
  printf("aplicar la negacion, el resultado es: 010. Observa que en esta\n");
  printf("operacion no es necesario tener dos numeros para realizarla y que\n");
  printf("es muy sencilla: cambia los 1s por 0s y los 0s por 1s.\n\n");

  printf("Otra operacion es el llamado O exclusivo, representado por ^.\n");
  printf("Con el O exclusivo, podemos obtener verdadero siempre y cuando\n");
  printf("los valores a comparar sean diferentes, es decir sean 0 y 1.\n");
  printf("Por ejemplo, teniendo los numeros 1010 y 0101, al aplicar el O\n");
  printf("exclusivo obtendremos: 1010 ^ 0101 = 1111 puesto que\n");
  printf("todas sus cifras son diferentes\n\n");

  printf("Ahora te toca a ti:\n");
  printf("Ejercicio 6: Realiza las cuatro operaciones logicas vistas\n");
  printf("en esta leccion con los siguientes numeros y escribe su valor\n");
  printf("en decimal: 11001101011, 1101110111\n\n\n");

  printf("RESPUESTAS A LOS EJERCICIOS\n\n");
  printf("Ejercicio 1: El numero 1256 en binario es 10011101000.\n");
  printf("Ejercicio 2: El numero 1110001 en decimal es 113.\n");
  printf("Ejercicio 3: Las letras H o L a se verian de la siguiente\n");
  printf("manera: 1001000 1101111 1001100 1100001.\n");
  printf("Ejercicio 4: %d\n", (14 << 3));
  printf("Ejercicio 5: %d\n", (27 >> 4));
  printf("Ejercicio 6:\n");
  printf("11001101011 = 1643\n");
  printf("1101110111 = 887\n");

  int a, b;
  a = 1643;
  b = 887;

  printf("%u & %u = %u\n", a, b, (a & b));
  printf("%u | %u = %u\n", a, b, (a | b));
  printf("~%u = %u\n", a, (~a));
  printf("~%u = %u\n", b, (~b));
  printf("%u ^ %u = %u\n", a, b, (a ^ b));

}

Referencias

"Computer." Wikipedia. The Free Encyclopedia. 2011

ASCII Table

Binary Number System.[Online] Available http://www.mathsisfun.com/binary-number-system.html, 2011.

Presentation 9: Binary System. [Online] Available https://sites.google.com/site/childrenandtechnology/presentation-9-binary-system

"Binary numeral system." Wikipedia. The Free Encyclopedia. 2011