martes, 13 de septiembre de 2016

Interfaz con el Usuario


"La interfaz con el usuario es como un chiste,

si hay que explicarlo entonces no es tan bueno"

miércoles, 10 de agosto de 2016

Contando los Domingos del Siglo XX (Desafíos de Euler)

El Proyecto Euler presenta un conjunto de problemas que desafían las mentes inclinadas a las matemáticas y a la computación.

El problema No. 19 pide contar los domingos que cayeron en primero del mes durante el siglo xx. Realmente el mérito lo tendría quien resuelva analíticamente la cuestión. Quizá tratando el calendario como una serie, estableciendo su término general o ley de formación y encontrando un selector que permita contar los elementos que cumplen las condiciones dadas:
(día del mes = 1 & día de la semana = domingo).

Pues bien, la solución que aquí presentamos no tiene tal mérito. Se trata del siguiente algoritmo de fuerza bruta:

    cuenta:= 0;
    FOR fecha:= 1/1/1901 TO 31/12/2000 DO
       IF (fecha.díaDelMes = 1) & (fecha.DíaDeSemana = "domingo") THEN
         cuenta:= cuenta + 1
       END
    END;
    escribir cuenta



¿Es correcto? (porque eso es lo que primero que debe preocuparnos). Informalmente, sí lo es: la variable "cuenta" se inicializa en cero y se incrementa solamente si se cumple las condiciones; se recorre todo el calendario que corresponde al siglo xx: del primero de enero de 1901 hasta el 31 de diciembre del 2000. El mecanismo mediante el cual el lazo FOR irá incrementando la variable de control "fecha", manejando todas las vicisitudes del calendario que nos recuerdan en el enunciado del problema, está por refinarse.


Es obligación de quien presenta un algoritmo de fuerza bruta tratar de mejorar su eficiencia. No pretendemos lograr una complejidad menor a O(n). Ya dijimos que: famoso se haría quien presente la solución O(1), la solución analítica. Lo que haremos es esforzarnos en reducir el número de iteraciones.

Una vez identificado un "domingo", no tiene sentido probar la fecha que sigue (de antemano sabemos que será "lunes"), ni la que sigue, ni la otra, ni la otra... ¡Um!, ese FOR puede usar un paso de 7 en vez de 1: FOR fecha:= 1/1/1901 TO 31/12/2000 BY 7 DO...

¡Con cuidado! ¡La corrección de un algoritmo es lo esencial! El paso acelerado de 7-en-7 sólo puede aplicarse una vez que se haya identificado el primer domingo, no antes. El enunciado da el dato de que el 1/1/1900 fue lunes, pero eso parece más bien un distractor, o una "cáscara de banano": lo que nos interesa está a un año de distancia de ese lunes.

Aceptemos más bien comenzar nuestro recorrido del siglo xx en la penumbra; vayamos de 1-en-1, hasta encontrar el primer domingo; a partir de allí, pisamos el acelerador. 

Cuando hagamos nuestro primer gran hallazgo: no sólo un domingo, sino un domingo en día primero de mes, el paso del FOR puede ser aun mayor. Si el primero fue domingo, no habrá otro domingo interesante ese mismo mes (sólo hay un día primero en cada mes). ¡Podemos omitir el resto del mes!

¡Con cuidado! ¡La corrección...! Los meses son de longitud variable (el enunciado nos lo recuerda vehementemente). El salto seguro, para manejar bien hasta el más corto de los febreros, no puede ser de más de 28 días. ¡Ojo! Una vez hecho ese salto casi volvemos a la penumbra, y lo más sano será ir al paso de 7 hasta que ocurra otro domingo en primero. Con cualquiera de los dos saltos caemos siempre sobre otro domingo. ¿Cierto? Esto reducirá el número de iteraciones en un poco más de 7 veces (el "poco más" es gracias a los ocasionales 28). 

Es tiempo de reconocer sin embargo que ya el FOR no va a servirnos. Ciertamente es muy segura esa forma de iteración, garantiza que nunca nos quedaremos en un lazo infinito; pero la disciplina del FOR recomienda su uso para cuando el número de iteraciones se conoce de antemano, no para cuando hay "penumbras" y pasos variables como aquí. Ahí están el WHILE y el REPEAT. Confiados en encontrar al menos un domingo en día primero nos embarcaremos en un REPEAT.

Enfrentaremos de una vez el gran lío de incrementar fechas en un calendario de meses de longitud variable. Nos basaremos en algoritmos probados para sumar un valor entero a una fecha.
¿Qué sería de los programadores si no fuera por los subprogramadores?

Una solución para saber cuántos días hay entre dos fechas dadas, o para saber cuál fecha viene "k" días después de otra fecha dada, empieza por convertir las fechas en el número de días transcurridos desde el origen de los tiempos. Fijaremos ese origen de los tiempos en el día 1/1/1. La función Day (fecha) devuelve el número de días transcurridos desde 1/1/1 hasta la fecha dada. por definición Day (1/1/1) = 1. La diferencia en días entre dos fechas f2, f1 es: Day (f2) - Day (f1).

La inversa de Day convierte un número de días (transcurridos desde el origen de los tiempos) en fecha. Planteada como un procedimiento propio, sería DayToDate (k, f), que define "f" como la fecha que corresponde a "k" días desde 1/1/1.

Axiomáticamente las dos funciones mencionadas se relacionan así: DayToDate (Day (f1), f2) => f2 = f1.

Con estas funciones, la implementación de d:= d + k, sería:
Dates.DayToDate (Dates.Day (d) + k, d).

En honor de los subprogramadores presentamos el código de ambas funciones en el apéndice (al pie). Son misteriosas, es cierto. Hay constantes mágicas allí, muchos MOD y DIV, algunos números primos casi desconocidos... ¿Qué esperábamos?, ¿Acaso no es misterioso y mágico el calendario?

Y ahora la versión "final" del contador de domingos en día primero.
Usaremos el siguiente tipo, del módulo "Dates", para las fechas:
TYPE Date = RECORD day, month, year: INTEGER END;

MODULE Sundays;

IMPORT Dates, StdLog;   (* manejo de fechas y escritura de resultados *)

PROCEDURE Count*;
   VAR d: Dates.Date; vueltas, cuenta, paso: INTEGER;
BEGIN
   vueltas:= 0; cuenta:= 0; paso:= 1;
   d.day:= 1; d.month:= 1; d.year:= 1901;   (* fecha inicial *)
   REPEAT
      IF (d.day = 1) & (Dates.DayOfWeek (d) = Dates.sunday) THEN
         Dates.DayToDate (Dates.Day (d) + 28, d);
         INC (cuenta);
         paso:= 7   (* ¡alta entropía! *)
      ELSE
         Dates.DayToDate (Dates.Day (d) + paso, d)
      END;
      INC (vueltas)
   UNTIL d.year >= 2001;   (* salimos del siglo XX *)
   StdLog.Int (vueltas); StdLog.String (" iteraciones"); StdLog.Ln;
   StdLog.Int (cuenta); StdLog.String (" domingos"); StdLog.Ln
END Count;

END Sundays.

Consideraciones Finales

  1. El REPEAT UNTIL requiere mayor atención que un FOR. La pregunta obligada es ¿Termina ese lazo? Veamos. Al entrar al lazo la fecha es d = 1/1/1901; dentro del lazo siempre incrementamos esa fecha en un valor positivo: de tomar la rama THEN la incrementamos en 28; de tomar la rama ELSE, en 1 (durante la penumbra de no haber encontrado ni el primer "domingo en día 1") o en 7 (fuera de la penumbra). Así pues, a fuerza de +1, +28, ó +7, llegaremos sin duda al fin del siglo. Estrictamente hablando la condición de terminación del REPEAT debería ser d > 31/12/2000, que habría que codificar basándose en: d.day, d.month y d.year. La alternativa que usamos es más eficiente. El lector debe convencerse de que tal solución no introduce peligro de contar algún domingo adicional que pertenezca al siglo xxi. Puede argumentarse que un "=", en lugar de ">=" va bien allí. Es cierto, pero por fidelidad a la disciplina del REPEAT dejaremos la condición de terminación en su forma más amplia.
  2. Los algoritmos, además de correctos, eficientes, finitos, completos, deben ser generales. ¿Cómo podemos generalizar el procedimiento "Count"? Podríamos, por ejemplo, admitir como parámetros, la fecha inicial y la fecha final. Así serviría para cualquier siglo o cualquier lapso (y cortamos un gigantesco cordón umbilical que aún nos une al siglo xx). Podemos pasar otros parámetros para contar no necesariamente los domingos ni atender sólo el comienzo de los meses, sino contar por ejemplo todos los viernes 13 que ocurrieron en un lapso dado. Con la versión generalizada pudimos comprobar la conjetura de que ¡todo mes que tenga un domingo primero, tendrá también un viernes 13!
  3. Hemos presentado la versión "final" en un lenguaje tan desconocido como excelentemente diseñado: Component Pascal, apoyado en el BlackBox Framework: joyas al alcance de cualquier programador. ¿Qué toca ahora al amigo lector? Le toca, usando como un pseudocódigo nuestra versión de "Count", convertirla a su lenguaje de programación favorito (Python, C#, Java, Haskell, Electron...) y comprobar que:
  4. 171 domingos cayeron en primero del mes durante todo el siglo xx, que esto lo contamos en 4914 iteraciones (que hubiese sido más de 36 mil sino fuera por el paso variable). 
  5. La generalización de los algoritmos va más allá de la parametrización. Estuvimos tratando de usar la misma idea de sumar un valor a una fecha para predecir las fechas de la luna llena  (un tema ligeramente más complicado porque el valor a sumar es fraccionario: días + horas, minutos, segundos...) La idea surgió la pasada noche de Navidad, que fue luna llena en 25 de diciembre. Nunca sacamos el algoritmo a la luz aquí en el blog. Creíamos que algún bug impedía predecir más allá de 8 o 10 lunas; hasta que descubrimos que el bug no era del programa; es de la tierra. Los movimientos de traslación y de rotación no son tan perfectos. Aunque no lo hemos preguntado a los astrónomos, es posible que hasta el despegue de un jet altere el espiralado viaje de nuestro planeta por el espacio.
  6. Queda mucho por hacer. Los programas no se terminan nunca. Versión "final" se ponen entre comillas.

APENDICE

PROCEDURE Day* (IN d: Date): INTEGER;
      (* For date d, return the number of days since 1/1/1. Day(1/1/1) = 1 
         Precondition ValidDate(d) (not explicitly checked);
         Postcondition (result > 0) & (result < 3652062) *) 
      VAR y, m, n: INTEGER;
   BEGIN
      y := d.year; m := d.month - 3;
      IF m < 0 THEN INC(m, 12); DEC(y) END;
      n := y * 1461 DIV 4 + (m * 153 + 2) DIV 5 + d.day - 306;
      IF n > 577737 THEN n := n - (y DIV 100 * 3 - 5) DIV 4 END;
      RETURN n
   END Day;

   PROCEDURE DayToDate* (n: INTEGER; OUT d: Date);
      (* Convert the number of days since 1/1/1 into a date.
         Precondition (n > 0) & (n < 3652062) (not explicitly checked);
         Postcondition ValidDate(d) & Day(d) = n *)
      VAR c, y, m: INTEGER;
   BEGIN
      IF n > 577737 THEN
         n := n * 4 + 1215; c := n DIV 146097; n := n MOD 146097 DIV 4
      ELSE
         n := n + 305; c := 0
      END;
      n := n * 4 + 3; y := n DIV 1461; n := n MOD 1461 DIV 4;
      n := n * 5 + 2; m := n DIV 153; n := n MOD 153 DIV 5;
      IF m > 9 THEN m := m - 12; INC(y) END;
      d.year := SHORT(100 * c + y);
      d.month := SHORT(m + 3);
      d.day := SHORT(n + 1)
   END DayToDate;


lunes, 8 de agosto de 2016

"A Prueba de Tontos"

"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs and the Universe trying to produce bigger and better idiots. So far, the Universe is winning". 

"La programación hoy día es una competencia entre los ingenieros de software, esforzándose en producir mayores y mejores programas a prueba de tontos, y el universo, tratando de producir más y mejores tontos. Hasta ahora el universo va ganando".


Rick Cook


Rick nació en el año en que aparecía la primera computadora comercial. Se convirtió en escritor, pero las computadoras lo persiguieron y aparecen en sus novelas, en sus chistes, en su fantasía, en su vida.

viernes, 25 de diciembre de 2015

¿Ordenador o Computador?

     Ambos términos, "ordenador" y "computador" son aceptados por la Real Academia Española. El último pareciera proceder del inglés computer, pero en realidad tanto computer como computador vienen directamente del latín computare. Suena entonces como si ya todo estuviese dicho: el término es "computador" y sanseacabó.

     El asunto es que en España sólo se oye "ordenador". Ordenador por aquí, ordenador personal, ordenador por allá... ¿De dónde sacarían eso?, me pregunto. Ordenar es una, entre la infinidad de cosas, que estas máquinas -cuyo nombre estamos buscando- pueden hacer. De hecho fue la primera cosa que hicieron. Ordenar es también uno de los primeros algoritmos que se enseña en los cursos introductorios de programación; como ejemplos son muy ilustrativos debido a la amplia variedad de métodos disponibles para ordenar o clasificar un conjunto de datos.

Sorter: Máquina de Registro Unitario

     Las primeras máquinas eran llamadas máquinas de registro unitario. Se las alimentaba con datos perforados en tarjetas de cartulina. Eran muy especializadas: había máquina para contar, otra aparte para ordenar los datos (típicamente en orden alfabético), las había también para separar datos (agrupándolos en clases según ciertos campos del registro), unas muy ruidosas para mezclar dos lotes de datos en uno sólo. La más útil era la segunda que nombramos, el ordenador.


     Pero la verdadera máquina para procesar información es de propósito general, no es una máquina especializada. Esa es la esencia de tales máquinas: son programables. El programa le dice a la máquina si ahora va a ayudarme a componer este texto, o va a chequear la ortografía, o va a enviarlo a un servidor para su publicación. Oeep! debo interrumpir brevemente mi post para que esta misma máquina abra mi programa de facturación. Ya, listo: facturas impresas. Aunque aún necesito terminar de calcular unas estructuras (yo utilizó ANSYS, pero hay veintenas de otros simuladores que pueden servir)... En fin esta máquina es realmente versátil. ¿Hay algo de ordenamiento en esas aplicaciones?, creo que no; y si lo hay será algo muy accesorio; el fuerte es búsqueda de información en grandes tablas, manejo de cadenas de caracteres, buscar mínimos y máximos... cómputo: aproximaciones sucesivas, muchas sumas y divisiones, raíces, trigonometría, en fin computare.

Computador "todo en uno"
     Ahora sí, nuestra máquina se llama computador. Sólo quedaría por resolver si es "el computador" o "la computadora". Una vez se me ocurrió responder esa pregunta en clase: si nuestra máquina da resultados predecibles -decía yo- nada sorpresivos, entonces es "el computador"; pero si nos desconcierta a cada momento y nos arranca un profundo "¿por qué?", entonces es "la computadora". No fue una ocurrencia afortunada, ya la liberación femenina estaba en marcha y dos de mis alumnas le dieron "unlike" a esa apreciación. Pero la RAE resolvió el asunto: se puede decir de las dos maneras.

En cualquier caso, el "ordenador" se quedó atrás en la historia, en los censos de población, en las primeras contabilidades automatizadas, en aquellos cuartos llenos de máquinas especializadas, donde los operadores movían trabajosamente cajas de tarjetas perforadas de una máquina a otra. Como casi todas esas máquinas estaban ocupadas al mismo tiempo el ruido era infernal. El computador en cambio es tan silencioso; puede emitir "clicks" artificiales cuando pulsamos algunos botones, pero el cómputo fluye raudo y silencioso.

miércoles, 21 de octubre de 2015

La Mecánica para Publicar en
Cuentas de Empresa de LinkedIn

Las cuentas de empresa en LinkedIn no son nada amigables para el administrador. Las cuentas personales en cambio ofrecen todo un conjunto de herramientas para diseñar “posts”: el ícono del lapicito. (Ese ícono aparece únicamente en cuentas personales con idioma seleccionado como inglés).

¿Cómo manejarnos sin el lapicito?:

  1. Entrar a la cuenta personal LinkedIn desde la cual se administra la cuenta de empresa
  2. Ir a Interests->Companies, selccionar la empresa
  3. Ubicar el recuadro para publicar (contiene la frase “share an update”). Allí se puede escribir directamente para publicaciones cortas y sin formato; se puede también hacer click en el clip (a la derecha del recuadro) y escoger un documento de nuestro disco local y publicarlo (pulsando “Share”), pero esa opción no es aconsejable: da una pésima presentación del documento. Lo aconsejable es continuar así:
  4. Entrar a GoogleDrive (https://www.googledrive.com) o a OneDrive (https://onedrive.live.com) u otro servicio de nube
  5. Iniciar Sesion, suplir usuario y contraseña
  6. Buscar el nombre de archivo que interesa (que habría sido almacenado previamente en la nube)
  7. Compartir-> obtener enlace para compartir
  8. Ir al cuadro "Share an Update" en LinkedIn y pegar el enlace allí (no tocar el clip que aparece a la derecha, sólo pegar el enlace). Esperar a que aparezca el preview
  9. Click en el recuadro para imagen y cargar una imagen, que debe estar previamente en alguna carpeta local
  10. Click en la línea de título y arreglar el título (quitar las extensiones de archivo p.e.)
  11. Click en el recuadro para párrafo introductorio y preparar ese párrafo (está limitado a una pocas líneas, afinar el contenido)
  12. Como consecuencia del paso 8, en el cuadro de "Share…" quedó el URL del archivo o documento que tenemos en la nube; puede borrar ese URL y aprovechar ese espacio para un ante-título o "volanta" (para ubicar o complementar lo dicho en el título)
  13. Dar al botón “Share”. Debe pasar algo en la pantalla (como cierto desplazamiento del contenido); si no pasa nada, volver a dar al botón “Share”. Debe aparecer el post que acabamos de hacer.
  14. Si no queda conforme (quiere por ejemplo modificar la volanta, el título, el párrafo inicial o la imagen). Haga DELETE (extremo superior derecho del post) y repita desde el paso 8. ¡LinkedIn no ofrece una opción de EDIT! Por esto es recomendable que la volanta, el título y el párrafo inicial se preparen en una ventana de texto aparte, y se copien y peguen cada vez que se necesite reconstruir el “post” que estamos haciendo.

Este post fue sólo sobre la mecánica de publicar... Habrá que hablar sobre contenidos -que es la esencia- pero eso es otro tema. Vendrá. 


jueves, 6 de agosto de 2015

Tributo a Edsger Wybe Dijkstra

Hoy, 6 de agosto, se cumple 13 años de la desaparición del emérito profesor Edsger Dijkstra.

La ciencia de la computación está llena de términos acuñados por él: display, deadly embrace, semaphores, separation of concerns, goto-less programming, weakest precondition, program derivation, guarded commands, dining philosophers, shortest path... Su influencia resuena en las escuelas de computación.

Prolijo por su concentración y disciplina. Controversial por su discurso liberado de ataduras y convencionalismos, discurso que en muchas ocasiones sacó de sus cabales a políticos, burócratas y hasta profesionales de la computación. Sin embargo, contrariamente a lo que vociferaban los ofendidos, para quien tuvo la suerte de conocerlo personalmente, Dijkstra era cercano, humano, muy humano. Oigamos esta frase pronunciada en las calles de Amsterdam, cuando paseaba tomado de la mano de quien sería su esposa: (hablaban en holandés, quizá esta traducción no sea la mejor) "Quisiera que cuando nos quitemos estos lentes color de rosa a través de los cuales estamos viendo el mundo, cuando descubramos todas nuestras miserias, sigamos sin embargo sintiendo como ahora"

Me tocó la suerte, por allá en un verano de los 80tas, de convivir por unos 20 días con él, en la universidad de California en Santa Cruz. Una noche Dijkstra se sentó ante un piano y comenzó a tocar Mozart; le costaba un poco cambiar las páginas de la partitura y me hizo señas para que lo ayudara. ¡Ni idea!... o bueno, para no ser tan exagerado, mi madre tocaba piano y la oí hablar de la clave de fa y la de sol, pero creo que eso no me serviría de nada aquí... Pero ahí estaban unos ojos, bajo unas desordenadas pestañas. Cuando ese bloque de azules, amarillos y mostazas se movía como pidiendo auxilio, yo cambiaba la página rápidamente y Mozart continuaba como si nada.

Es éste mi pequeño tributo al mayor genio de la computación, al perfeccionista, al Humble Programmer, al cien veces criticado por sus colegas y coterráneos, y a quien se le va concediendo razón en todo lo que parecían sus exageraciones.

Dijkstra fue también famoso por sus frases; citemos sólo una, de las últimas: "Si alguna vez, como programador, sientes que estoy mirando por encima de tu hombro y te dices: --a Dijkstra no le hubiera gustado esto, eso sería suficiente inmortalidad para mí”.