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 :)

jueves, 31 de julio de 2025

Intercambiando Punteros entre Visual Basic 6 y Borland C++

Otro tutorial de los viejitos, de casi veinte años atrás, así que sirve como documento histórico :D

Ya que hemos podido pasar un parámetro desde una aplicación en Visual Basic a una Dll compilada con Borland C++ y devolver a Visual Basic el resultado calculado desde la Dll, lo que hace falta ahora es hacer lo mismo con arrays. 
No se puede enviar todo un vector desde Visual Basic a una Dll, lo que se puede hacer al operar con arrays es llamar a la función en la Dll dentro de un bucle que recorra el array en el programa de Visual Basic, pero si se desea que nuestra aplicación sea más rápida es mejor dejarle el manejo de los bucles a C++. En este caso se le enviaría a la Dll la dirección de memoria donde está el array (es decir: la dirección de su primer elemento). 

Un excelente tutorial sobre los punteros en C++ está akí: 

En contra de lo que muchos creen, Visual Basic sí maneja punteros. Más información se encuentra en esta web: http://www.thevbzone.com/secrets.htm 
Pero para este caso no se necesitan estas funciones, basta pasarle a la Dll el primer elemento del array tal y como lo explican en esta otra web: https://recursosvisualbasic.com.ar/rvb/htm/tutoriales/interaccion-visual-basic-c++.htm

Hay que tener cuidado con un par de detalles que no mencionan estas páginas y que explicaré más adelante.
En resumen, lo que se quiere es que la Dll programada en C++ reciba y devuelva un puntero. 
Para una Dll que simplemente recibe un puntero y devuelve un dato el código es:

#include <windows.h>
#include "puntero.h"

BOOL WINAPI DllEntryPoint(HINSTANCE, DWORD, LPVOID)
{
  return TRUE;
}

extern "C"
long __declspec (dllexport) WINAPI sumatotal (long *puntero, int valor) {

  long sumatoria;
  sumatoria = 0;

  for (int i = 0; i < valor; i++)
    sumatoria= sumatoria+ puntero[i];

  return sumatoria;

}

Y el archivo .h es:

#ifndef _PUNTERO_H
#define _PUNTERO_H

#ifdef BUILD_DLL // en la construcción de la librería
#define EXPORT __declspec(dllexport)
#else // en la construcción del ejecutable
#define EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus /* if in a C++ program */
extern "C"
#endif

long __declspec (dllexport) WINAPI sumatotal (long *puntero, int valor);

#endif 

Para probarlos se compilan y se copia la Dll (llamada "puntero.dll") en la carpeta System32. Luego se abre en el Visual Basic un proyecto exe estándar y se ingresa el siguiente código:

Private Sub Form_Load()
  Dim c As Long, i As Integer
  Dim mat() As Long

  ReDim mat(0 To 5)

  For i = 0 To UBound(mat)
     mat(i) = 2 * i
  Next

  c = sumatotal(mat(0), UBound(mat) + 1)
  Form1.Caption = c

End Sub

Y en un módulo .bas se coloca:

Public Declare Function sumatotal Lib "puntero.dll" (ByRef puntero As Long, ByVal valor As Integer) As Long

"Byref" le dice a Visual Basic que lo que va a enviar es un puntero, no una variable.
En este ejemplo se nota la diferencia en el manejo de los índices entre Visual Basic y C++. Los índices del array "mat" son 0, 1, 2, 3, 4, 5. Si se hubiera creado en C++, para que tuviera seis elementos, se habría definido como mat[6] siendo sus índices también 0, 1, 2, 3, 4, y 5 ya que en C++ el número entre corchetes define la cantidad de elementos del array. Debido a la costumbre programé el bucle para tratar al array como si hubiera sido creado en C++, cuando en realidad es un array de Visual Basic en el cual el 5 representa el índice mayor mas no el número de elementos, por ello se le suma uno al segundo parámetro al enviarlo a la función programada en C++.
Y este es el resultado:


 
Ahora, para crear una Dll que reciba y devuelva punteros es necesario hacer unos pequeños cambios:
El archivo se llama "puntero2.cpp" y su código fuente es:


#include <windows.h>
#include "puntero2.h"

BOOL WINAPI DllEntryPoint(HINSTANCE, DWORD, LPVOID)
{
     return TRUE;
}

extern "C"
long* __declspec (dllexport) WINAPI punt (long *puntero, int valor) {

    for (int j=1; j<=valor+1; j++)
        puntero[j]=puntero[j]*2-15;

    return(puntero);

}

"valor" es el tamaño del array cuya dirección está recibiendo la función "punt" la cual, después de ejecutar el bucle, devuelve un puntero. 
Para hacer la Dll también se necesita el archivo "puntero2.h" cuyo código es:

#ifndef _PUNTERO2_H
#define _PUNTERO2_H

#ifdef BUILD_DLL // en la construcción de la librería
#define EXPORT __declspec(dllexport)
#else // en la construcción del ejecutable
#define EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus /* if in a C++ program */
extern "C"
#endif

long* __declspec (dllexport) WINAPI punt (long *puntero, int valor);

#endif 

Nótese que la función se declara con el asterisco junto al tipo de datos al que apunta el puntero, C++ permite en teoría poner el asterisco al comienzo del nombre de la función, pero al hacer esto el compilador me botaba error, así que es mejor hacerlo de este modo.
"Long" es el tipo de datos para enteros largos (de 64 bits) y lo manejan tanto C++ como Visual Basic. Lo malo es que cuando quise enviar punteros que apuntaban a datos de punto flotante el compilador del Visual Basic me botaba el error 16 en tiempo de ejecución: "Expresión demasiado compleja". Así llegué a descubrir que Visual Basic y Borland C++ sólo pueden intercambiar punteros que apunten a enteros, ya sean Long o Byte. Al trabajar con arrays no recomiendo datos del tipo Integer, al operar me salían resultados erróneos.
Después de compilar y copiar la Dll "puntero2.dll" a la carpeta system32 la probé el Visual Basic con el siguiente código, en un proyecto exe estándar:

Option Explicit

Private Sub Form_Load()
    Dim mat(-1 To 8) As Long
    Dim i As Integer

    For i = 0 To UBound(mat)
        mat(i) = i
    Next

    mat(-1) = punt(mat(-1), UBound(mat))

    For i = 0 To UBound(mat)
        Debug.Print mat(i)
    Next
End Sub

Y colocando en el módulo .bas el siguiente código:

Declare Function punt Lib "puntero2.dll" (ByRef puntero As Long, ByVal valor As Integer) As Long

Esta vez quise ver los valores de "mat" en la ventana del inmediato (ya he fastidiado bastante el Caption del Form) y éste es el resultado:



Ya funciona :)
Ahora la gran pregunta: ¿por qué definí el array "mat" desde -1 y no desde 0? Visual Basic tiene la ventaja de poder definir arrays con índices negativos, cosa que no se puede en C++. El índice -1 terminará conteniendo la dirección de memoria del array, un valor que no interesa a la hora de operar y con el que es mejor no trabajar. El índice -1 en Visual Basic pasará a ser el índice 0 en C++, así el índice 0 para Visual Basic será el índice 1 en C++, etc. Para Visual Basic los índices van de -1 a 8 en este ejemplo, para C++ van de 0 a 9 (la cantidad de elementos del array es 10 en total), donde el valor en el índice 0 no interesa, y no se debe operar con él pues al devolver el puntero al Visual Basic éste "chanca" el primer elemento del array con su dirección de memoria.
Al modificar el bucle del programita en Visual Basic que escribe en la ventana inmediato para que vaya desde -1 se podrá ver el valor de mat(-1):



He aquí a los diez elementos del array
 
Una manera de poder operar con valores de punto flotante es convirtiendo estos valores enteros multiplicándolos por 10^n, donde "n" es la cantidad de decimales con la que vamos a trabajar.
Por ejemplo, queremos una Dll que calcule las raíces cuadradas de algunos números dentro de un vector:
La nueva Dll será "puntero3.dll" y el código fuente de puntero3.h es:


#ifndef _PUNTERO3_H
#define _PUNTERO3_H

#ifdef BUILD_DLL // en la construcción de la librería
#define EXPORT __declspec(dllexport)
#else // en la construcción del ejecutable
#define EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus /* if in a C++ program */
extern "C"
#endif

long* __declspec (dllexport) WINAPI cuadrado (long *puntero, int valor, long d);

#endif 

Y el de "puntero3.cpp":

#include <windows.h>
#include <math.h>
#include "puntero3.h"

BOOL WINAPI DllEntryPoint(HINSTANCE, DWORD, LPVOID)
{
    return TRUE;
}

extern "C"
long* __declspec (dllexport) WINAPI cuadrado (long *puntero, int valor, long d) {
    double *c;
    c= new double[valor+1];

    for (int j=1; j<=valor+1; j++){
        c[j-1]= double(puntero[j])/d;
        c[j-1]=pow(c[j-1], 0.5);
        puntero[j]= long(c[j-1]*d);
    }
    return(puntero);
}

La variable "d" es el 10^n con el que se trabajará. Aquí hay que tener cuidado de "castear" los arrays de Double a Long y viceversa o sólo nos resultarán ceros.
Y ahora el código fuente del programa en Visual Basic, primero el módulo .bas (todo es una sola línea):

Declare Function cuadrado Lib "puntero3.dll" (ByRef puntero As Long, ByVal valor As Integer, ByVal d As Long) As Long

Y lo que va en el formulario:

Private Sub Form_Load()
    Dim mat() As Double
    Dim matl() As Long
    Dim divisor As Long
    ReDim mat(-1 To 4)
    ReDim matl(-1 To 4)
    divisor = 100000000

    For i = 0 To UBound(mat)
        mat(i) = (i + 1) / 25
        mat(i) = mat(i) * divisor
        matl(i) = CLng(mat(i))
        Debug.Print mat(i) / divisor
    Next

    matl(-1) = cuadrado(matl(-1), UBound(matl), divisor)

    For i = 0 To UBound(mat)
        mat(i) = matl(i) / divisor
        Debug.Print mat(i)
    Next

End Sub

Y el resultado es éste:



Los cinco primeros números son los datos originales, y los otros cinco sus raíces cuadradas hasta ocho decimales de aproximación.
Éste es un mal ejemplo sobre cómo usar una Dll para hacer cálculos numéricos ya que sería mejor sacar las raíces cuadradas directamente con el Visual Basic y usando solamente el array "mat" declarado como Double, pero deja entender cómo se debe manejar los datos para poder operar con números de punto flotante a partir de enteros. Si se trata de números muy grandes y con muchos decimales se pueden poner la parte entera y la decimal en variables separadas, dividir luego en la Dll la parte decimal entre 10^n y sumar ambas variables. Una Dll es útil cuando se requieren cálculos complejos o comandos que el programa principal realizaría más lentamente, con arrays mucho más largos (se declaran los punteros como "far" tanto en el archivo .cpp y el .h) y todas esas cosillas para las que el Visual Basic se vuelve lentecillo (considerando el hardware de inicios de los 2000s)

viernes, 4 de abril de 2025

El test FizzBuzz en C# y cómo abusar del lenguaje C# versión 8+

Rebuscando en mis archivos viejos me encontré una implementación del algoritmo FizzBuzz. Lo mencionan en esta entrada https://www.variablenotfound.com/2007/02/dnde-se-han-ido-los-programadores.html y también lo mencionan acá https://exponentis.es/el-test-de-fizz-buzz-para-contratar-programadores como uno de los tests básicos para contratar programadores.

Me divertí con este test hace casi veinte años, y por pura nostalgia pongo acá mi primera implementación de aquellos años y que es tan aburrida y genérica que hasta una AI podría hacerla:

for (int i = 1; i <= 100; i++)
{
    t = i % 3;
    c = i % 5;
    tc = i % 15;
    if (t == 0 || c == 0 || tc == 0)
    {
if (t == 0)
    Console.WriteLine("Fizz");
if (c == 0)
    Console.WriteLine("Buzz");
if (tc == 0)
    Console.WriteLine("Fizz Buzz");
    }
    else
Console.WriteLine(i);
}

Esta primera implementación tiene un detalle: para los múltiplos de 15 imprime
Fizz
Buzz
Fizz Buzz

Si sólo se quiere "Fizz Buzz" para los múltiplos de 15 debe ser:

int t = 0, c = 0, tc = 0;

for (int i = 1; i <= 100; i++)
{
    t = i % 3;
    c = i % 5;
    tc = i % 15;

    if (t == 0 || c == 0 || tc == 0)
    {
        if (tc == 0)
            Console.WriteLine("Fizz Buzz");
        else if (c == 0)
            Console.WriteLine("Buzz");
        else if (t == 0)
            Console.WriteLine("Fizz");
    }
    else
        Console.WriteLine(i);
}

Console.ReadLine();

La trampa en este test es que 15 es múltiplo de 3 y 5, así que se debe evaluar si es múltiplo de 15 primero si no se desea imprimir lo demás, de todos modos esto depende de si se considera correcto o no imprimir sólo "Fizz Buzz" con los múltiplos de 15 o si se acepta también que imprima "Fizz" y "Buzz" para este caso; todo depende de la salida que se desee, lo que se considera correcto en este contexto, más las pruebas unitarias... lo clásico del desarrollo de software.

Si se ignoran estos detalles y nos concentramos en el verdadero objetivo del test: saber si se es capaz de escribir un programita que haga algo y que implique un bucle con condicionales, sigue siendo mortalmente aburrido.


Para no aburrirnos tanto, acá hay una implementación que acabo de hacer, en una lambda recursiva porque le tengo manía a las lambdas recursivas:

Func<int, int> F2 = null;
F2 = i =>
{
    int t2, c2, tc2;
    if (i == 101)
        return 0;
    else
    {
        t2 = i % 3;
        c2 = i % 5;
        tc2 = i % 15;
        if (t2 == 0 || c2 == 0 || tc2 == 0)
        {
            if (tc2 == 0)
                Console.WriteLine("Fizz Buzz");
            else if (c2 == 0)
                Console.WriteLine("Buzz");
            else if (t2 == 0)
                Console.WriteLine("Fizz");
        }
        else
            Console.WriteLine(i);
        return i = F2(i + 1);
    }
};
Console.WriteLine("llamando a Función Lambda Recursiva");
F2(0);

Lo sigo viendo aburrido, es el mismo código pero metido en una función lambda. 

Por otro lado, estos días he estado revisando algunas novedades del lenguaje C# y encontré que hay otra forma de escribir la declaración switch, más info en estos enlaces:

https://www.csharp.com/article/c-sharp-12s-switch-expressions-a-more-powerful-alternative-to-traditional-switch-st/

https://stackoverflow.com/questions/44355630/how-to-use-c-sharp-tuple-value-types-in-a-switch-statement

Lo bonito es que también admite tuplas, y esos "ifs/elses" están que me piden ser convertidos en un switch al estilo C# a partir de la versión 8. Un detalle de los nuevos switch es que ya vienen con los breaks de forma implícita, de modo que si cumplen con una de las condiciones, el código no sigue cayendo en las demás. 
Luego de juguetear un poco, otro detalle que encontré es que las tuplas no necesitan ser de sólo dos valores, pueden tener más. En este caso tengo tres variables qué evaluar: tc2, c2 y tc2, además del contador. El resultado: una tupla de cuatro valores (yo la llamaría "cuadrupla").

El código es:

Func<int, int> F2 = null;
F2 = i =>
{
    int t2, c2, tc2;
    if (i == 101)
        return 0;
    else
    {
        t2 = i % 3;
        c2 = i % 5;
        tc2 = i % 15;
                           
        var resultado = (t2, c2, tc2, i);
        Console.WriteLine(resultado switch
        {
            (_, _, 0, > 14) => "Fizz Buzz",
            (_, 0, _, > 4) => "Buzz",
            (0, _, _, > 2) => "Fizz",
            _ => i,
        });
        return i = F2(i + 1);
    }
};
Console.WriteLine("llamando a Función Lambda Recursiva");
Console.WriteLine();
F2(0);

Se ve rarísimo y se puede poner peor, nos podemos deshacer de tanta variable y acortar el condicional:
Func<int, int> F2 = null;
F2 = i =>
{
    var resultado = (i % 3, i % 5, i % 15, i);
    Console.WriteLine(resultado switch
    {
        (_, _, 0, > 14) => "Fizz Buzz",
        (_, 0, _, > 4) => "Buzz",
        (0, _, _, > 2) => "Fizz",
        _ => i,
    });
    return i == 100 ? 0 : F2(i + 1);
};
Console.WriteLine("llamando a Función Lambda Recursiva");
Console.WriteLine();
F2(0);

Y puede ser aún peor, ya dejando de lado el switch podemos abusar de los IEnumerables de C# y terminar con esperpentos como el siguiente:

    Func<IEnumerable<string>, IEnumerable<string>> F3 =
    f =>
       f.Select(c => int.TryParse(c, out _) && int.Parse(c) % 15 == 0 ? c = "Fizz Buzz" : c)
       .Select(c => int.TryParse(c, out _) && int.Parse(c) % 3 == 0 ? c = "Fizz" : c)
       .Select(c => int.TryParse(c, out _) && int.Parse(c) % 5 == 0 ? c = "Buzz" : c);

Console.WriteLine("llamando a Función Lambda que ya no es Recursiva pero se ve más fea");
Console.WriteLine();

IEnumerable<string> f0 = F3(Enumerable.Range(1, 100).Select(c=> c.ToString()));

foreach (var item in f0)
{
    Console.WriteLine(item);
}

Console.ReadLine();

La función TryParse la uso sólo para evaluar si la variable c se puede convertir a entero, de otro modo arroja error de formato. Esa característica del C# de usar el guión largo para ignorar parámetros le da al código un aspecto medio esotérico que me encanta :)

viernes, 10 de enero de 2025

Implementando el algoritmo ADALINE

La idea fue convertir el algoritmo perceptron de esta estrada en el algoritmo ADALINE. La diferencia entre ambos algoritmos está explicada en https://pabloinsente.github.io/the-adaline y es casi mínima. En esta web fue donde conocí inicialmente el algoritmo ADALINE y donde el algoritmo perceptron está mejor explicado. La conversión de uno a otro parecía simple, y en realidad lo fue luego de superar las siguientes dificultades:

Primera dificultad: esta imagen es errónea:

La correcta es:
La forma correcta se deduce del código fuente en la web.

Segunda dificultad:
No especifica cómo debe actualizarse el parámetro bias b de la ecuación del error


e(w,x)=(y(b+wx))2

Mirando los códigos fuente en https://pabloinsente.github.io/the-adaline y en https://medium.com/@mr.sk12112002/understanding-adaline-da79ab8bbc5a  se ve que el bias es la suma de las derivadas de la función del error multiplicada por el ratio de aprendizaje, es decir:

bias +=sum(y(b+wx)) * lr

Tercera dificultad: los códigos de las webs que revisé son para un input X unidimensional o bidimensional con sólo dos valores por muestra, el mió tiene 30 valores por cada muestra.. Revisando la teoría y el código que modifiqué para el perceptron noté dónde debía hacer el cambio. 

El código resultante es:

from tkinter import Y
import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import datasets

class Perceptron:
    
    def sign(self, a):
        if a > 0:
            return 1
        else:
            return 0
    

    def standard_scaler(self, X):
        mean = X.mean(0)
        sd = X.std(0)
        return (X - mean)/sd

    def fit(self, X, y, n_iter = 10**3, lr = 0.001, add_intercept = True, standardize = True):        
        # Add Info #
        if standardize:
            X = self.standard_scaler(X)
        if add_intercept:
            ones = np.ones(len(X)).reshape(-1, 1)
            
        self.X = X
        self.N, self.D = self.X.shape
        self.y = y
        self.n_iter = n_iter
        self.lr = lr
        self.converged = False
        self.accuracy = 0

        vsign = np.vectorize(self.sign)
        
        # Fit #
        beta = 0
        w = np.random.randn(self.D)/5
        yhat = []
        
        for i in range(int(self.n_iter)):
            yhat = np.dot(self.X, w) + beta
            yhat = vsign(yhat)

            # Check for convergence
            if np.all(yhat == self.y):
                self.converged = True
                self.iterations_until_convergence = i
                break
                
            # Otherwise, adjust
            for n in range(self.N):
                yhat_n = self.sign(np.dot(w, self.X[n]) + beta)
                delta = 2 * self.lr * (self.y[n] - yhat_n)
                w += delta * self.X[n]
                beta += np.cumsum(delta)

        # Return Values #
        self.beta = beta
        self.w = w
        self.yhat = yhat
        
        print(self.y[:50])
        print(self.yhat[:50])
        
        accuracy_true = self.y == self.yhat
        self.accuracy = np.count_nonzero(accuracy_true)/self.N * 100
        
        print("accuracy: ", self.accuracy, "%")
        
    def predict(self, X, y, N):
        accuracy_true = 0
        for n in range(N):
            yhat_n = self.sign(np.dot(self.w, self.X[n]) + self.beta)
        
            if (y[n] == yhat_n):
                accuracy_true += 1
                
        accuracy_true = accuracy_true/N * 100
        print("accuracy: ", accuracy_true, "%")
        
    
def main():
    # import data
    cancer = datasets.load_breast_cancer()
    X = cancer['data']
    y = cancer['target']
    perceptron = Perceptron()
    perceptron.fit(X[:-5], y[:-5], n_iter = 1.5e2, lr = 0.01)
    yy= np.atleast_1d(y[-5:])
    perceptron.predict(X[-5:], yy,  yy.size)
    
      
if __name__ == "__main__":
    main()


Al ejecutarlo resulta:


La exactitud es mejor que con el algoritmo perceptron tal y como indica la teoría, pues el algoritmo ADALINE ajusta sus parámetros en cada iteración, mientras que el perceptron sólo los ajusta cuando obtiene un resultado erróneo.