Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)

viernes, 29 de septiembre de 2023

Hilos con C# (C Sharp)

Otro tutorial viejito, muy viejito, hay un detalle qué arreglar en el código, pero eso lo voy a explicar en una siguiente entrada:

¡Hilos! Visual Basic 6 no los permite, y programarlos en Borland C++ es muy engorroso (personalmente odio tener que estar programando archivos .h y el Borland los necesita para poder usar los hilos). En este tutorial voy a usar C#.

Para el programita que voy a mostrar a continuación hay que abrir el Visual C# (yo uso el 2005), crear un nuevo proyecto tipo Windows Application y en el formulario que aparece, ponerle dos Pictureboxes: pictureBox1 y pictureBox2. Al final, debe verse así:

 

Y luego se le mete este código:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading;

 

namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

       

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true;

            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));

            p1.Start();

            p2.Start();

        }

       

        public void Hilo1() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;                   

                }

 

                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }

                pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

            }

       }

 

        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;

 

                    if (g == 255)

                        b2 = true;

                }

 

                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;

 

                    if (g == 0)

                        b2 = false;

                }     

                pictureBox2.BackColor = Color.FromArgb(100, g, 80); 

            }                  

        }

 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

            p1.Abort();

            p2.Abort();

        }

    }

}

 

Lo que se debe entender primero es que existen dos hilos: "p1" y "p2" (o dos objetos de la clase "Thread", en inglés "Hilo") cada uno asociado a un método: A "p1" le corresponde "Hilo1" y a "p2" le corresponde "Hilo2". Lo que "p1" y "p2" permiten es que ambos métodos (Hilo1 e Hilo2) se ejecuten de manera simultánea.

Todas las otras variables en el programa son de control. Los hilos empiezan a ejecutarse al llamar al método "Start" y se terminan con "Abort". El C# no permite llamar de nuevo a "Start" para un hilo que ya se está ejecutando.

Lo que hace el programa simplemente es variar el color de ambos PictureBoxes en un bucle que jamás termina, para ello se usa el comando CualquierPictureBox.BackColor = Color.FromArgb(100, g, 80);

También uso el método Thread.Sleep que detiene la ejecución del hilo durante el tiempo en milisegundos que va entre paréntesis. Esto es para que los colores varíen despacio y se puedan apreciar.

Cada vez que dentro de un método se usa la palabra "Thread" ésta se refiere al hilo que está asociado a él.

Una buena página para aprender hilos en C# es ésta: https://www.albahari.com/

Y acá se puede descargar mi ejemplo de los PictureBoxes.

sábado, 17 de junio de 2023

Cuadrados recursivos (y con colores) usando la tortuga de Python

Quise jugar un poco más con la recursividad en Python, así que cogí mi código de los arbolitos fractales y lo modifiqué para que dibujara cuadraditos (tuve que hacer unos cálculos en mi cabeza para que los cuadrados pequeños se ubicaran en medio de cada lado del cuadrado más grande):

from operator import length_hint
import turtle as t

def cuadrado(lenght):
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        if lenght < 20:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


Creo que no es necesario el import length_hint, (no recuerdo de qué tutorial lo copié) pero los dibujos tardan su buen rato en terminarse de dibujar, así que al final dejé el import.

El resultado es:



Se ve bien, pero no lo suficiente, hice un pequeño cambio al parámetro que se pasa a la función cuadrado():

from operator import length_hint
import turtle as t

def cuadrado(lenght):
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        if lenght < 20:
            t.color('gray')
        else:
            t.color('black')

        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


El resultado es:


Mucho mejor, pero le falta color, el código para el color lo copié de aquí.


from operator import length_hint
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght):
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
colormode(255)
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


El resultado es:


No está mal, pero esas líneas gruesas del cuadrado grande molestan un poco. Voy a ponder un color distinto a cada línea:

from operator import length_hint
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght):
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(90)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(180)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(270)
    color(randint(0, 255), randint(0, 255), randint(0, 255))
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        drawit2(cx, cy, lenght / 2)
    else:
        t.hideturtle()
        return 0

lenght = 160
colormode(255)
drawit2(0 - lenght / 2, 0 - lenght / 2, 160)
t.mainloop()


Queda asi:


Creo que prefiero la versión en negro y gris.

Como se ve tan feo, encontré un dato en uno de los tutoriales: el módulo colorsys. Con la ayuda de este enlace y luego de probar un poco llegué a este código:

from colorsys import hsv_to_rgb
import turtle as t
from turtle import*
from random import randint

def cuadrado(lenght, n):
    t.pencolor(hsv_to_rgb(n / 36, 0.75, 0.75))
    t.forward(lenght)
    t.seth(90)
    t.forward(lenght)
    t.seth(180)
    t.forward(lenght)
    t.seth(270)
    t.forward(lenght)
    t.seth(0)

def drawit2(x, y, lenght, n):
    if lenght > 5:
        t.penup()
        t.goto(x, y)
        n += 4
                
        t.pensize(lenght/15)
        t.pendown()
        cuadrado(lenght - lenght / 10, n)
                
        cx = x - lenght / 4
        cy = y + lenght / 4
        drawit2(cx, cy, lenght / 2, n + 1)
        cx = x + lenght / 4
        cy = y - lenght / 4
        
        
drawit2(cx, cy, lenght / 2, n + 2)
        cx = x + lenght * 3 / 4
        cy = y + lenght / 4
        
drawit2(cx, cy, lenght / 2, n + 3)
        cx = x + lenght / 4
        cy = y + lenght * 3 / 4
        
drawit2(cx, cy, lenght / 2, n + 4)
    else:
        t.hideturtle()
        return 0

lenght = 160
drawit2(0 - lenght / 2, 0 - lenght / 2, 160, 12)
t.mainloop()


Se ve mucho mejor ahora:


lunes, 24 de abril de 2023

Comandos de git para eliminar commits, y ramas locales y remotas

Intentaba hacer un git checkout otra_rama y me salió este error:

fatal: unable to create 'xxx/.git/index.lock': file exists.

Another git process seems to be running in this repository, e.g. an editor opened by 'git commit'. Please make sure all processes are terminated then try again. If it still fails, a git process may have crashed in this repository earlier: remove the file manually to continue.

Al ejecutarse, git deja un archivo dentro de la carpeta oculta .git llamad index.lock el cual puede eliminarse manualmente. En mi caso, una de las ventanas de consola había dejado de responder y tuve que matarla.

Pongo también el comando que se usa para eliminar el último commit:

git reset --hard HEAD~1

Si se quiere eliminar una rama local, se debe hacer:

git checkout otra_rama

git branch -D rama_que_quiero_eliminar

Lo que no dicen los tutoriales es que primero debemos ubicarnos en otra rama o se lanzará el error:

cannot delete branch checked out at xxx

Para eliminar una rama remota:

git push origin --delete rama_a_eliminar 

o también:

git push origin -d rama_a_eliminar 

Si se ingresa el comando:

git push --delete rama_a_eliminar 

Lanzará el error:

fatal: --delete desn't make any sense without any refs

Me pasé horas buscando cómo restaurar los refs en lugar de revisar el comando y darme cuenta que me faltaba el origin :(

martes, 28 de febrero de 2023

Tutorial viejito: Un Par de Trucos al hacer Guis en Matlab 7

Este tutorial data de mis años de estudiante hace casi dos décadas atrás, enjoy!


Matlab 7 permite poner imágenes en las ventanas hechas con la herramienta Guide, aunque no directamente pues no posee un control Picture o Image como en Visual Basic o C++ Builder. Lo malo es que los archivos de Ayuda de Matlab no explican cómo poner una imagen a una ventana (y si lo hacen yo no pude encontrar dónde), después de una tarde de examinar y diseccionar el Matlab descubrí la manera de hacerlo.

Y aquí viene lo que quiero explicar:

 
CÓMO PONER UNA IMAGEN EN UNA INTERFAZ GRÁFICA


Primero la imagen que queremos colocar en la ventana debe estar en formato BMP y en la misma carpeta donde se guardarán los archivos de la Interfaz Gráfica (el *.fig y el *.m).

Luego se debe crear una ventana en blanco, esto se hace escribiendo "Guide" en la ventana de comandos y presionando enter, entonces se abrirá la siguiente ventana:



Elegimos "Blank GUI" con lo que se abrirá esta ventana:



Después se jala un control Axes (el botón con la curva azul):



Mi ventana la he guardado como "unt1.fig" lo cual genera un archivo "unt1.m", ahora nos vamos para allá. Lo que debemos buscar es la función OpeningFcn que se activa apenas se abre la ventana:


Para el Matlab la ventana tiene por defecto el nombre de "figure1" y el Axes el de "axes1". Estos nombres pueden cambiarse, pero en este caso los vamos a usar así como están.

Lo que debe hacerse es añadir el siguiente código (no hay que hacerle caso a lo que está en verde, son comentarios y pueden borrarse):



Lo que hace este código es cargar la imagen (cuyo nombre es "dsp.bmp") a una matriz con la función "imread", por precaución se cambia a formato entero de 8 bits (aunque una imagen en BMP ya está en este formato).

La tercera línea hace que la imagen dependa de axes1. Las siguientes dos líneas son para configurar axes1: la propiedad "Visible" debe estar en "off" o de otro modo se verán los ejes coordenados a los costados de la imagen.

Lo demás es para que la imagen tenga el mismo tamaño que axes1. Los tres puntos simplemente son para indicarle al Matlab que la función continúa en la siguiente línea.

Al correr nuestra ventana debe verse así:



El ancho y alto de la imagen puede variarse variando el ancho y alto de axes1.

 

CÓMO HACER LA PARTE MÁS OSCURA PARA EL TÍTULO:

Para esto se deben jalar un control tipo "panel" y uno tipo "static text". A ambos se les da click derecho y se escoge "Send to back", lo cual los pondrá detrás de la imagen y no la taparán:


Al hacer doble click sobre cualquiera de ellos aparecerá la ventana de propiedades con las respectivas propiedades que pueden usarse para cada control. Por ejemplo, el panel y el texto estático tienen una propiedad llamada "BackgroundColor", la cual no tiene figure1, pero ésta posee la propiedad "Color". Es importante saber esto, pues no todos los controles tienen las mismas propiedades.

El panel tiene por defecto el nombre de "uipanel1" y el texto estático el de "text1".

Nos volvemos a ir a la función OpeningFcn y añadimos (sí, borré los comentarios del Matlab 7):



El color de la ventana por defecto es el que le da el sistema operativo. Si queremos que la ventana tenga el color que le asigna el windows y no uno fijo, y que el recuadro donde irá el título sea un poco más oscuro, es necesario tener los valores del color asignado (son 3, para rojo, verde y azul). Estos valores se cargan a un vector llamado "cc".

Para oscurecerlo un poco se le resta un número entre 0 y 1 al vector. El valor resultante se asigna a la propiedad "BackgroundColor" de text1 y uipanel1.

El resultado es éste:


Y con otras propiedades de pantalla (en Windows 2000, este es un tutorial MUY viejo):



Se le pueden arreglar algunas propiedades, como el tamaño y color de la letra de text1 y borrar el título de uipanel1 si se desea:


(no podía dejar mi sistema con ese amarillo tan molesto!)

viernes, 6 de enero de 2023

Resolviendo el Problema 108 de ProjectEuler.net

Otra entrada migrada de la vieja web:

Visitando la página de ProjectEuler.Net escogí intentar resolver el problema 108, el cual dice:


Hay que hallar el mínimo valor de n para el que existen al menos 1000 soluciones a la ecuación 1/x + 1/y = 1/n, donde "x", "y", "n" son números naturales.

Parecía que la solución estaba en simplemente meter x e y en dos bucles anidados y evaluar la ecuación para distintos valores de "n" y, junto con un contador, romper los bucles cuando éste exceda mil. Mas el primer problema que hallé es cómo encontrar los límites superiores de los bucles ¿cómo sé que no debo buscar en la infinita totalidad del conjunto de números naturales? En el problema se entiende que la solución existe y es finita.

Después de jugar con la ecuación, despejar variables, quitar denominadores, iguala a cero... y finalmente frustrarme, un foro me dio una pista: la solución de los límites para "x" e "y" está en la ecuación en su forma original.

Entonces me puse a analizarla y a meditarla...



Entonces (si se ignoran todos los dibujitos) salta a la vista que "x" e "y" deben ser mayores que "n" para cumplir la condición que todas las variables son números naturales. El valor de n+1 es el límite inferior de los bucles.

Si hago x = n+1:

Si el valor mínimo de "x" e "y" es n+1, el valor máximo será n^2+n, ya que para cumplir que 1/x + 1/y = 1/n, si "x" tiene un valor máximo, "y" tendrá un valor mínimo. En una fracción, mientras más grande es el denominador, más pequeña será la fracción, y viceversa.

Mi primer intento de resolver el problema 108 tenía dos bucles anidados que evaluaban los valores de "x" e "y", dentro de un bucle while, el cual se rompía cuando un contador superaba el valor de 1000, mientras no lo hiciera incrementaba el valor de "n". Mirando la ecuación también se deduce que "n" debe ser mayor a 32, ya que n^2+n - (n+1) = 31.606, redondeando: 32.

Pero este primer intento era mortalmente lento.

Luego de correr el programita para valores pequeños de "n", noté que las soluciones se repetían cuando "x" e "y" eran iguales a 2*n. Esto se entiende por la propiedad conmutativa de la suma:

El siguiente valor entero es 2n + 1, donde "z" puede ser "x" ó "y", pues los valores se repiten para las dos variables:


Se concluye que se puede decir que "x" va desde n+1 hasta 2n, "y" va desde 2*n+1 hasta n^2+n.

Para que se cumpla la condición de las mil soluciones, "x" debe tener al menos mil valores, es decir: 2n - (n+1) >1000. El valor mínimo para "n" es 999. En el programa empieza desde 1000:


 

Pero esta solución también tardaba siglos.

Entonces, jugando un poco con la ecuación, llegué a esto:


 

Para el problema 108, no interesan los valores de "n", "x" e "y", sólo si la relación xy/(x+y) da mil soluciones enteras. Evalúo los posibles valores de "x" e "y" para el intervalo [n+1, 2n], dentro de dos bucles: 


Queda mucho mejor y da la solución al problema en menos de un minuto, corriendo como código administrado sobre .Net 3.5 :D

Mi programita se veía interesante y bonito... tan bonito que me dije ¿por qué no pasarlo a Linq?

 

 

Dado que el compilador debe hacer varias conversiones si se usa Linq, esta última solución es unas tres veces más lenta.

El código fuente puee descargarse de aquí.

 

martes, 29 de noviembre de 2022

Accediendo a una base de datos en Visual Basic 6

Este tutorial es viejo viejo, de cuando recién aprendía a hacer conexiones a bases de datos con el VB6 en aquellos fines de los 90s/inicios de los 2000s. Lo dejo aquí como parte de la historia de la informatica.

La coneja no tiene nada que ver con el tutorial, pero creo que es un bonito separador :)

Empezamos:


Sucede que quienes programamos, en algún momento, tenemos que toparnos con las bases de datos queramos o no. Lo malo es que éstas son un mundo muy diferente a los punteros, al manejo de puertos, a la captura de señales o a las llamadas a Apis. Lo bueno es que una vez que se domina lo básico son lo más fácil del mundo. Y lo mejor es que los lenguajes de programación de alto nivel ya vienen con herramientas para manejas bases de datos de todo tipo.

Y eso es lo que quiero mostrar aquí: lo básico para manejar una pequeña base de datos en Access 2002 en el lenguaje de programación más fácil de entender de la Historia: Visual Basic 6.

Antes que nada hay que cargar el componente ADO (Click derecho en la barra de herramientas -> Componentes -> Microsoft ADO Data Control 6.0) y ponerlo en el formulario. Luego se guarda y se borra el componente ADO y, si se quiere, la referencia en la barra de herramientas. Esto es para que el Visual Basic reconozca los objetos Recordset y Connection.

El programa (con su código fuente) se pueden bajar de aquí. 

 


Y ahora una explicación:

Los datos a agregar o eliminar se ingresan en Text1 y Text2. La navegación se hace con los botones Anterior y Siguiente (Command3 y Command4). El botón Limpiar (Command2) es sólo para borrar lo escrito en Text1 y Text2. En el control List1 aparecen todos los registros que coinciden con la búsqueda del texto en Text3 realizada al oprimir "Buscar Todos" (Command7), en cambio el botón "Buscar" (Command5) sólo hace aparecer el primer resultado encontrado.

Para manejar la base de datos no es necesario añadir ningún control especial, basta declarar dos objetos:

Dim rs As Recordset
Dim db As Connection


"db" realiza la conexión con la base de datos y "rs" permitirá recorrer los resgistros, borrarlos, añadirlos y buscarlos. Para conextar con la base de datos (llamada "bd1.mdb") bastan estas líneas:

db.Open "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" & pathBD & ";"
rs.Open "select * from Datos", db, adOpenDynamic, adLockOptimistic


bd1.mdb sólo tiene dos tablas: "Datos" y "datos2". El programa sólo trabaja con "Datos". El programador debe saber qué tablas y qué campos tiene su base de datos para poder trabajar con ella.

Es importante saber también en qué programa y en qué versión está hecha la base de datos, sino no podrá conectar. Para versiones anteriores de Access se pone:

db.Open "PROVIDER=Microsoft.Jet.OLEDB.3.51;Data Source=" & pathBD & ";"

Aquí hay que observar el primer parámetro que se le pasa a rs.Open: "select * from Datos" esto significa que rs dee recibir todos los registros de la tabla Datos de la base de datos.

"select" es un comando de los Procedimientos Almacenados de las bases de datos. Los Procedimientos Almacenados son órdenes que éstas interpretan y ejecutan y se basan en la lógica booleana. Así, si yo quisiera todos los registros cuyos Nombres coinciden con lo que se ingresa en Text3 (una búsqueda) basta poner:

rs.Close
rs.Open "select * from Datos where Nombre = '" & Text3.Text & "'", db, adOpenDynamic, adLockOptimistic


Para buscar todos los registros que coincidan con Text1 ó con Text2 (Nombre o Apellido) se pondría:

rs.Open "select * from Datos where Nombre = '" & Text1.Text & "' or Apellido = '" & Text2.Text & "'", db, adOpenDynamic, adLockOptimistic


(hay que tener cuidado de que el contenido de Text1 y Text2, o las condiciones de búsqueda si son de tipo String, estén entre comillas simples).

Como rs se vuelve a abrir, es necesario cerrarlo previamente.

Otro comando es "delete", cuando se lo usa el objeto rs se cierra después de la ejecución porllo que ya no es necesario poner rs.Close. Pero para que se pueda seguir navegando entre todos los registros de la tabla Datos se debe vovler a abrir.

rs.Open "delete * from Datos where Nombre = '" & Text1.Text & "' and Apellido = '" & Text2.Text & "'", db, adOpenDynamic, adLockOptimistic


Esta orden borra los registros cuyo Nombre y Apellido coincidan con los textos en Text1 y Text2.

Los procedimientos almacenados permiten realizar muchas cosas en la base de datos. Por ejemplo, imaginando que se tiene una tabla "Empleados" con campos "Edad" que contiene data numérica, "ID" y "Profesion". Luego de hacer las conexiones respectivas, para obtener a todos los ID y las profesiones correspondientes a mayores de edad se puede poner:

select ID, Profesion from Empleados where Edad > 18

Para agregar un registro basta una función propia de rs: AddNew y luego especificar los campos con la función "Fields" a donde se añadirán los nuevos datos. Al recorrer los registros de atrás o hacia adelante (propiedades MoveNext, MovePrevious, MoveLast y MoveFirst), o al hacer una búsqueda, "Fields" se coloca automáticamente en el registro actual.

Las propiedades de rs: EOF y BOF indican si se está al final o al principio de la tabla Datos. Mientras no se lo esté, sus valores son False. Tanto las búsquedas como los recorridos y los borrados de los registros en las tablas de la base de datos deben hacerse mientras EOF y BOF son False, si no nos "saldremos" de la base de datos y nos botará error.

Si en una búsqueda no se encuentra nada, EOF y BOF serán True.

 

miércoles, 5 de octubre de 2022

La secuencia de Van Eck en Python (y un poco acerca de la palabra reservada yield)

Buscando tutoriales ya no recuerdo de qué, me encontré con esto Van Eck Sequence Using Python - Roy Tutorials (roytuts.com)

¡La Secuencia de Van Eck! Este link también la menciona:

The Van Eck Sequence | IB Maths Resources from Intermathematics 

Y ambos la explican de una forma horrible. En serio, a partir de esas explicaciones tan cutres intenté hacer mi propio algoritmo (sin mirar los de otros) y fue horrible. Tuve que ir a esta otra web Van Eck's sequence - EverybodyWiki Bios & Wiki y jugar un poco frustrarme dos horas armando mi propio código para hallar la verdadera explicación de cómo generar la secuencia de Van Eck:

1. Empieza con cero. En este ejercicio vamos a empezar siempre de cero.

2. Coge el último número de la secuencia.

3. Si ese número no se halla al menos dos veces en la secuencia, añade cero al final. Si se halla al menos dos veces, coge las dos últimas posiciones, réstalas, y el resultado se añade al final de la secuencia.

El código que me salió es:

vanEckSeq = [0]

for _ in range(0, 20):
    valor = vanEckSeq[-1]    
   
    if vanEckSeq.count(valor) < 2:
        vanEckSeq.append(0)
    else:
        #extraer últimos dos indices
        vanEckSeq0 = [c[0] for c in enumerate(vanEckSeq) if c[1] == valor]
               
        index1 = vanEckSeq0[-1]
        index2 = vanEckSeq0[-2]
       
        vanEckSeq.append(index1 - index2)
       

print(vanEckSeq)


No está mal porque una de las condiciones que me impuse es que no podía usar bucles anidados, es decir nada de:

for _ in range(0, 20):
    for _ in range(0, whatever): 
 
Y además usa una función muy bonita de Python: enumerate que devuelve una lista de tuplas cuyos elementos son (posición del elemento en la lista, elemento de la lista).
Mi código se parece al del primer link, pero en el primer link hace algo que no me gusta: evaluar y usar todos los números naturales hasta un límite determinado, cuando en la definición de la secuencia de Van Eck se usan los valores que ya posee la secuencia.

Mi código además permite hacer cosas como esta:


def VanEck():
    vanEckSeq = [0]

    while(True):
        valor = vanEckSeq[-1]
       
        if vanEckSeq.count(valor) < 2:
            vanEckSeq.append(0)
        else:
            #extraer últimos dos indices
            vanEckSeq0 = [c[0] for c in enumerate(vanEckSeq) if c[1] == valor]
                   
            index1 = vanEckSeq0[-1]
            index2 = vanEckSeq0[-2]
           
            vanEckSeq.append(index1 - index2)
           
        yield vanEckSeq[-1]
   
   
generator = VanEck()
   
for _ in range (0, 20):
    print (next(generator))

# ... some code....
print (next(generator))
# ... some other half million lines of code....
print (next(generator))


Puedo generar una función que devuelve el último valor de la secuencia de forma indefinida. En casi todos los tutoriales que he revisado del uso de la palabra resevada yield se usa un bucle para mostrar cómo funciona. Esto no demuetra el poder de yield, el cual permite obtener un generador el cual devolverá el siguiente valor de la operación cuando se necesite, no es necesario que esté dentro de un bucle. yield permite que Python guarde el estado de todas las variables dentro de la función (que en este caso devuelve un generador), para que estén listas para la siguiente llamada next() del generador.

Codifiqué esto en Python 3.9 con Visual Code en Windows 10.

jueves, 9 de junio de 2022

Error "Clase no registrada" al usar Microsoft.Speech en Visual Studio 2019 y Windows 10

Un día quise jugar con las librerías de Microsoft Speech para hacer hablar a mi laptop. Las descargas fueron las siguientes:

Microsoft Speech Platform Runtime. Descargué el instalador de 64 y 32 bits, bastan los instaladores de la plataforma en la que se compila la aplicación, pero por el error que explicaré más abajo tuve que probar ambos:

https://www.microsoft.com/en-us/download/details.aspx?id=27225

Microsoft Speech Platform Runtime Languages:

https://www.microsoft.com/en-us/download/details.aspx?id=27224

Los paquetes para reconocimiento de audio son los que dicen TELE, los paquetes para convertir texto a voz son los que tienen nombres de personas. Yo descargué los paquetes de español México y español España. En total fueron 4 paquetes de idioma.

Lo siguiente que hice fue crear un proyecto de C# con .Net Framework. Referencié el archivo Microsoft.Speech.dll en mi proyecto:

Para 64 bits, la dll está en C:\Program Files\Microsoft SDKs\Speech\v11.0\Assembly

Para 32 bits, está en C:\Program Files (x86)\Microsoft SDKs\Speech\v11.0\Assembly

Finalmente copié y pequé un poco del código de esta web: https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/december/voice-recognition-speech-recognition-with-net-desktop-applications

        static void Main(string[] args)
        {
            try
            {
                ss.SetOutputToDefaultAudioDevice();
                Console.WriteLine("\n(Speaking: I am awake)");
                ss.Speak("He despertado");
                CultureInfo ci = new CultureInfo("es-PE");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
            }
        }

Y apenas empezar el debugueo me saltó este error:

Retrieving the COM class factory for component with CLSID {D941651C-44E6-4C17-BADF-C36826FC3424} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).

Luego de intentar varias soluciones: cambiar a 32 bits, cambiar a .Net Framework 4, iniciar Visual Studio como administrador, lo único que funcionó fue lo propuesto en este foro


En 64 bits el instalador está en C:\Program Files\Microsoft SDKs\Speech\v11.0\Redist

En 32 bits está en C:\Program Files (x86)\Microsoft SDKs\Speech\v11.0\Redist

Al ejecutar los instaladores se pueden ver los mensajes de que se están registrando las dlls.

Lugo volví a referenciar la dll de 64 bits, cambié a .Net Framework 4.5, puse que se trata de una aplicación de 64 bits, y probé.

Funcionó, la aplicación puede decir "He despertado" con la voz de una mujer (apodada Hilda).

Ya puedo ponerle voz a mis aplicaciones de C# :)

jueves, 28 de abril de 2022

"Curlicue Fractals" con Visual C#

Otra entrada migrada de la vieja web, la cual ha perecido con dignidad. De a pocos voy a ir copiando mis viejos tutoriales a este blog, ¡hay que tener paciencia!

Rod Stephens, autor de http://csharphelper.com/ tenía un tutorial sobre cómo dibujar estos fractales, pero él también ha tenido problemas con su web y el tutorial ya no está disponible. Voy a poner aquí lo que yo tengo, mi código está basado en el de Rod, así que los créditos van para él.

Al revisar la documentación en http://mathworld.wolfram.com/CurlicueFractal.html noté que sólo menciona dos ecuaciones cuando en el código fuente de Rod (ya no disponible) se usan cuatro:


Al seguir examinando se puede ver que las ecuaciones de la documentación de Mathworld generan ángulos, y lo que se dibujan son líneas que necesitan las coordenadas de los puntos inicial y final. 

La traducción del algoritmo es la siguiente: Se tiene una línea de longitud 1, paralela al eje x. Se calcula un número theta con la ecuación, luego, con la ecuación se calcula el nuevo ángulo de la siguiente línea de longitud 1, cuyo punto inicial será el extremo final de la línea anterior.
Al final resulta un ángulo. Su módulo es 1. Para calcular las coordenadas se multiplica por el coseno (coordenada x) y el seno (coordenada y).


Mi programa está basado en el algoritmo de Rob, pero con algunas cosas añadidas, como zoom y poder posicionar el fractal, además emplea el objeto Graphics de una forma diferente para dibujar el fractal, también añadí un número aleatorio para jugar con los colores:


El fractal se genera sólo a partir de un número irracional. Si se utiliza un número racional sólo aparecerá una línea horizontal.

La aplicación se puede descargar de aquí (para Visual C# 2008).