Archivo de la etiqueta: código

Códigos hash: ¿Qué son? ¿para qué sirven? ¿cómo se usan?

Hola a todos,

les comparto de nueva cuenta y como no he hecho en algún tiempo, notas de clase… Este semestre he estado impartiendo cursos que ya había dado en el pasado, por lo que en lugar de escribir nuevas notas he estado afinando las que ya tenía.

Pero, hubo un cambio en el material de estas fechas, por lo que preparé una clase sobre códigos hash; aquí presente.

En este resumen, se nos explica qué es un código hash, algunas de sus aplicaciones dentro del dominio de las estructuras de datos y cómo podemos usarlas en nuestros programas y aplicaciones.

A continuación, la liga a las notas.

Si desea conocer las aplicaciones que tiene el hashing en la seguridad informática, puedes consultar este material en seguridad y ruby; recomendaría también consultar el proyecto de ejemplo anexo.

Espero les sea útil y que el límite sea la imaginación.

Números y complejidad computacional: simple algoritmo para encontrar primos

Hola a todos!

Uno de los posts más exitosos en este blog, ha sido desde su publicación, es «un simple algoritmo para encontrar números primos«. El algoritmo no es tan simple cómo lo mencioné originalmente, aunque aún creo que la idea de almacenar persistentemente los resultados es bastante buena.

Los comentarios giraron mucho en torno a la complejidad del algoritmo. Muchos creen que un algoritmo al ser pequeño (de poco texto) es un algoritmo simple. Sin embargo, no esto no es del todo cierto.

Para detallar lo anterior, he escrito un post donde hablo de la complejidad computacional y porqué es importante. Un problema puede tener muchas soluciones, pero aplicar cada una de ellas puede ser costoso en distintas formas: algunas soluciones tendrán ventajas sobre otras desde distintos puntos de vista.

Por ahora vamos a casarnos con el tradicional punto de vista del tiempo. Queremos resolver un problema en la menor cantidad posible de pasos. En este caso, el problema de encontrar primos.

Empecemos por una solución «fácil»; al menos, fácil a la vista. Para esta parte, quiero estrenar esta forma mostrar código en junto con mis publicaciones en distintos lenguajes de programación. Así, si no estás acostumbrado a mi pseudo-código, a Java o a C; podrás elegir ver el código en el lenguaje que prefieras. Las implementaciones que ofrezco de inicio no son las únicas que podría presentar; se aceptan sugerencias para agregar más implementaciones en otros lenguajes de programación (aunque a mi juicio, ya son bastantes 😛 ).

Desgraciadamente, WordPress.com no permite insertar objetos HTML en los posts directamente, así que me limitaré a dejar el enlace al código.

http://www.nachintoch.mx/code

En fin. Tomando el pseudocódigo como modelo, el algoritmo realiza 11(n /2 -1) +3 operaciones para el peor caso, por lo que el algoritmo tiene una función de complejidad en tiempo lineal. Hasta aquí no se ve tan mal; de hecho el algoritmo «simple» ya incluye algunas optimizaciones: ningún número es divisible por otro más grande que su mitad y excluir al 1 del ciclo nos da otra pequeña ventaja al eliminar sus respectivas 11 operaciones elementales. Pero el algoritmo aún puede mejorarse aún más; ya que por ejemplo, más que la mitad, ningún número es divisible por un número más grande que su raíz.

Además, podemos usar propiedades de números y algunos resultados conocidos en la teoría de números, muestras numéricas o probabilidades para reducir el tiempo del algoritmo. También podríamos a recurrir a la paralelización: crear hilos cada un cierto intervalo de posibles divisores y hacer que todos los hilos concurrentemente («simultáneamente») verifiquen si el número es divisible por algún número en el intervalo que le ha tocado verificar.

Todo esto puede parecer demasiado; el algoritmo (en pseudo-código) es de apenas 6 líneas pequeñas de código ¿no es ya suficientemente bueno, pequeño y rápido? ¡Ni siquiera va a iterar tantas veces como el valor del número que se quiere saber es primo!

Aunque es cierto que se ve pequeño y su complejidad no es tan alta, veamos que pasa en los campos de aplicación del problema.

En la teoría y en como primeras prácticas de programación, este es un problema común que se puede resolver con cualquiera de los algoritmos ya mostrados. Sin embargo, en la práctica; una de las áreas donde determinar si un número es primo o no es crucial, es en la seguridad informática.

Una de las «piedras angulares» de la seguridad informática es la dificultad de encontrar números primos. Pero ¿qué dificultad si ya hemos resuelto el problema con un algoritmo simple? Por ejemplo, los certificados y las capas de encriptación que usa la web con HTTPS; intercambian  información cifrada por una llave de 256 bits (comúnmente); es decir, números que pueden ser tan grandes cómo 2²⁵⁶; para darnos una idea, puede ser un número decimal hasta de 78 dígitos. ¿cómo se ve un número de 71 dígitos?

10000000000000000000000000000000000000000000000000000000000000000000000

Y hay muchos más números como ese. ¿cuánto tardaría nuestro programa en determinar si uno de estos números de 71 dígitos es primo?

Considerando el número anterior cómo n, entonces la función de complejidad de nuestro algoritmo T(n) = 11(n /2 -1) +3 nos da 55×10⁶⁹.

Si cada operación elemental pudiera ser realizada en tan solo un milisegundo, entonces el número anterior es la cantidad de milisegundos necesarios para determinar si 1×10⁷⁰ es primo o no (no lo es, porqué es par. Pero seguro algún número cercano a él es primo; y no sólo los cercanos más pequeños, sino también números más grandes). Para ponerlo en perspectiva, los milisegundos anteriores son equivalentes a 1.8945×10⁶⁰ años. Si por el contrario, cada operación se realizara en un nanosegundo, el tiempo que requerimos para determinar si 1×10⁷⁰ es primo son 1.8945×10⁵⁴ años… El tiempo que se estima le queda de vida al son 5 mil millones de años; 5,000’000,000 = 5×10⁹. Nadie; ni si quiera el sol, tienen tiempo de esperar a que nuestro algoritmo determine si estos números son primos.

Usualmente, las nuevas versiones de este tipo de sistemas de seguridad, tienden a aumentar el tamaño de las llaves que usan; normalmente involucrando números primos en alguna forma importante; precisamente porque determinar si un número tan grande como estos es primo, es computacionalmente muy muy costoso. De allí la importancia de buscar nuevos algoritmos para este fin: más rápidos y eficientes.

Desde los días en los que «hablaba con mi hermano» he aprendido que no es trivial crear este tipo de soluciones para estos problemas. Requieren de un profundo estudio y si el lector encuentra curiosidad por esto, los invitaría a buscar y revisar publicaciones académicas donde aparecen propuestas de algoritmos para encontrar primos grandes de forma eficiente y cómo funcionan los algoritmos utilizados en la práctica para este mismo fin.

La moraleja que nos puede quedar, es que por muy simple e inofensivo que parezca un problema; su solución puede ser de grandes implicaciones y puede ser mucho más complicada de lo que aparenta… ¿nunca se ha preguntado cuan difícil es; computacionalmente hablando, resolver una torre de Hanoi de 64 discos? ¿podría el Sol esperar a ver esa solución?

Que el límite sea la imaginación.

Spoiler alert: no, ni el Sol puede esperar a resolver una torre de 64 discos si cada operación toma un milisegundo.

Cómputo concurrente: Ejemplos de solución al problema del consenso en Java y C (Uso de las herramientas de concurrencia)

Hola a todos!

Cerrando con broche de oro las clases de ayudatía que di este semestre, veremos a continuación algunos ejemplos para resolver problemas de concurrencia y en general de cómputo paralelo.

Les presento a continuación las notas, donde les explico las herramientas. Los ejemplos en C, pueden usarse en proyectos C++, y con algunas modificaciones, también puede usarse con varios sistemas basados en UNIX (incluyendo Mac OS X) y con la WinApi para su uso con Windows.

En el programa en C, se ilustra también cómo capturar una señal, por ejemplo la que se dispara cuando el usuario da la orden de interrumpir el comando actual usando ctrl +C (SIGINT).

En las notas encontrará más información al respecto:

Haz clic para acceder a 23_nov%20-%20Ejemplos%20en%20Java%20y%20C%20soluci%C3%B3n%20al%20problema%20del%20consenso.pdf

El código de los ejemplos, puede encontrarlo en el siguiente repositorio en GitHub:
https://github.com/Nachintoch/ejemplos-concurrencia


 

Antes de terminar, quiero comentar algunos detalles sobre los ejemplos.

Primero que nada, la sección del makefile para compilar la implementación en C carece de las banderas para GCC mencionadas en las notas -ffixed-reg. Esto es porque (y lo aprendí a la mala), es mala idea usar registros cómo «variables globales compartidas» de un programa en C.

¿Porqué?

Al declarar la variable compartida, lo que tenemos es básicamente acceso a algún registro que nosotros hayamos elegido. Luego, como es compartida, usamos mmap para solicitar dicho espacio de memoria compartida y alojamos el apuntador que refiere al valor almacenado en el registro seleccionado. Esto suena a trabalenguas y es el punto clave, así que lo explicaré con calma.

register int *var asm(«%eax»);

Lo anterior declara una variable de C llamada «var». Es de tipo apuntador a un entero de registro del procesador. Y además, solicitamos un registro estático para toda la vida del programa: el registro de procesador eax (en sintaxis para ensamblador de x86, x86_64 y amd64).

Entonces, tenemos un apuntador a un entero de registro. Queremos que este se convierta en una memoria compartida entre el proceso padre que ha hecho esta declaración y sus hijos que instancie de alguna manera más adelante en el flujo del programa.

Sería entonces lo esperado hacer algo como lo siguiente:

var = mmap(NULL, sizeof(*var), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);

mmap básicamente está solicitando al SO que nos de un espacio de memoria compartida y nos devuelve la dirección de memoria en la que el SO nos dió dicho espacio de memoria (o NULL (0) si el sistema no tiene suficiente memoria o por alguna otra razón rechaza la petición).

Cómo var es un apuntador, felizmente almacenamos la dirección de memoria retornada por mmap en var directamente y así tenemos un registro cuya dirección de memoria es compartida entre todos los procesos hijos y este padre. Genial ¿no?

Pues no mucho… ¿Qué pasa si el sistema interrumpe al proceso padre y lo envía a memoria? Uno esperaría que nada y que los hijos puedan seguir manipulando a donde apunta var felizmente, ya que es un espacio de memoria compartida ¿cierto?

Pero, var apunta a un registro de procesador, que además está en el contexto de ejecución del proceso padre que por el momento no está en ejecución, por lo que su contexto no está en el procesador, si no en memoria… Empieza a sonar feo ¿no?

Con el proceso padre sin tiempo de procesador, si un hijo intenta acceder a donde apunta var, accederá a una dirección del memoria -por el momento inválida- y normalmente el SO terminará matando al proceso hijo que hizo dicho intento.

Bajo nuestra aplicación anterior, esto es muy malo, ya que si justo ese era el proceso que debía hacer la llamada sem_post para permitir el progreso del otro proceso distinguido, esta condición habrá causado un deadlock.


 

Entonces, ¿usar variables registros o no usar variables registros? Esa es la cuestión.

La respuesta es la misma que si preguntamos cuando conviene usar un unsigned short int o un long long int: cuándo sea necesario y conveniente. La conveniencia de usar una variable registro, dependerá del problema a resolver y será necesario, en principio cuándo necesitemos usar una variable atómica por un muy breve periodo de tiempo y que además no necesitará ser una variable compartida.


 

Tomando en cuenta estas observaciones, podemos usar lo aprendido en estos ejemplo y algunas notas anteriores, para desarrollar aplicaciones concurrentes en distintas plataformas y al menos usando Java y C.

¡espero les sea de utilidad!

Saludos a todos!

Android: más sobre sensores; ejemplo de uso

Saludos!

Hace poco publiqué mi biblioteca para el manejo de sensores.. Pero que tal un ejemplo de uso? Bueno, les dejo la liga para descarga de una app que implementa la biblioteca. Aquí les dejo la liga del código fuente.

Espero que les sea de utilidad. Si quieren pueden dejar mensajes para abrir un tema sobre el uso de sensores.

La app está protegida con una licencia GNU General Public License, siéntanse libres de redistribuirla.

¿Es posible anidar clases descendientes de BroadcastReceiver?

Saludos a todos!

Comenzando a desarrollar apps para Android, me encontré con un dilema que parece no estar claro entre la red: clases que extienden a android.content.BroadcastReceiver dentro de un archivo .java de otra clase (anidada).

La respuesta es simplemente: SÍ. Sí es posible anidar clases BroadcastReceiver. Mientras indagaba en la red; tratando de responder a la pregunta, me encontré con que muchos otros se la hacían y la primer respuesta a la mayoría es simplemente que no es posible. Pero si lo es. Hice una app que lo hace, corre en el emulador y en mi teléfono. La solución que descubrí fue la siguiente:

Android permite dar de alta receptores de dos maneras: declarándolos explícitamente en el archivo manifest xml; usando la etiqueta <receiver>; la cual es hija directa de <application>.

La otra es escribiendo en el código (preferentemente en el método onCreate(android.os.Bundle)) la instrucción registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter). Con ésto nuestro BroadcastReceiver queda dado de alta y durante ejecución estará listo para recibir llamadas de algún Intent. Es muy importante destacar que NO es posible mezclar estas formas de dar de alta BroadcastReceivers; de hacerlo, tirará una excepción en tiempo de ejecución cuando dispare un Intent al que debe responder. Dado que no se espera que dicha excepción sea disparada, no se espera que sea atrapada por nadie, por lo que usualmente el programa terminara de en forma de error. Recuerden también llamar al método unregisterReceiver(BroadcastReceiver) en onPause().

Teniendo en cuenta que debemos aplicar; siempre que usemos un receptor, uno y sólo uno de los registros anteriores, es como resolví mi problema para anidar receptores en otras clases.

Resulta muy útil el anidar receptores; pues así podemos responder en una clase que se dedidaca a ciertas tareas, ejecutarse directamente cuando recibe un intente. Por ejemplo en un servicio (una clase que extiende a Service; la clase que permite ejecución de tareas en segundo plano, incluso cuando la app no tiene foco en pantalla), que espera datos de Internet  que se presione el botón de la cámara; o cualquier otra señal Intent, sea nativa o personalizada y compartir recursos con la otra clase sin necesidad de sincronizar hilos o tener que mandar más señales Intent para compartir datos.