Hola a todos:
Hace tiempo que no publicaba en el blog, he estado muy entretenido escribiendo publicaciones en LinkedIn, pero ya iba siendo hora de hacerlo en mi web.
Y no todo va a ser Power Apps, Automate o Typescript, hoy me ha apetecido escribir sobre el cálculo de números primos y, especialmente, sobre la idea de usar un array para almacenar dichos cálculos de forma más eficiente en Excel VBA.
¿Por qué usar un array para almacenar los números primos?
Cuando escribimos datos celda por celda en Excel (por ejemplo, en un bucle For i = 1 To 1000000
), estamos haciendo cientos o miles de llamadas a la API interna de Excel. Esto puede ralentizar muchísimo la ejecución.
En cambio, si acumulamos los datos primero en un array en memoria (que es muy rápido de manejar dentro de VBA) y después volcamos todo el contenido de una sola vez a la hoja, logramos una mejora sustancial en el rendimiento.
¿Cómo funciona el proceso?
- Pedimos parámetros al usuario: A través de un
InputBox
, solicitamos dos números separados por coma, por ejemplo50,600
.- El primero (
LargoColumna
) indica cuántos primos queremos por cada columna. - El segundo (
Limite
) es hasta qué número calcularemos los primos.
- El primero (
- Buscamos y guardamos los primos:
- Recorremos todos los enteros desde 2 hasta
Limite
. - Comprobamos si cada número es primo (usando divisiones hasta la raíz cuadrada).
- Cada vez que encontramos un primo, lo agregamos a un array dinámico llamado
ListaPrimos()
.
- Recorremos todos los enteros desde 2 hasta
- Reorganizamos el resultado en un array bidimensional:
- Una vez que tenemos todos los primos en un array unidimensional, calculamos cuántas columnas necesitaremos para distribuirlos en bloques de
LargoColumna
filas. - Luego volcamos estos datos a un array bidimensional (
FinalArray
), que reflejará el formato que queramos en la hoja (con las filas y columnas adecuadas).
- Una vez que tenemos todos los primos en un array unidimensional, calculamos cuántas columnas necesitaremos para distribuirlos en bloques de
- Asignamos el array bidimensional a la hoja:
- Por último, indicamos el rango (por ejemplo,
A1
con un ancho y alto en función de nuestros datos) y lo rellenamos de golpe conFinalArray
. - Esta acción reduce drásticamente el tiempo de ejecución comparado con el enfoque de ir escribiendo celda por celda en un bucle.
- Por último, indicamos el rango (por ejemplo,
Ejemplo de código
Aquí os dejo el código comentado:
Option Explicit
Sub NumerosPrimosConArray()
Dim ArrParams As Variant
Dim LargoColumna As Long
Dim Limite As Long
Dim ListaPrimos() As Long
Dim CountPrimos As Long
Dim i As Long
Dim j As Long
Dim Raiz As Double
Dim EsPrimo As Boolean
Dim TotalCols As Long
Dim TotalRows As Long
Dim FinalArray() As Variant
Dim cont As Long
Dim r As Long
Dim c As Long
Dim RangoDestino As Range
ArrParams = Split(InputBox("Introduce LargoColumna,Limite (p.e. 50,600)"), ",")
If UBound(ArrParams) < 1 Then
MsgBox "Parámetros no válidos."
Exit Sub
End If
'Validamos
On Error GoTo ErrorHandler
LargoColumna = CLng(ArrParams(0))
Limite = CLng(ArrParams(1))
On Error GoTo 0
If LargoColumna < 1 Or Limite < 2 Then
GoTo ErrorHandler
End If
'Limpiamos hoja
ActiveSheet.Cells.Clear
'Generamos primos y almacenamos en array
ReDim ListaPrimos(1 To 1)
CountPrimos = 0
For i = 2 To Limite
EsPrimo = True
If i = 2 Then
' 2 es primo
ElseIf i Mod 2 = 0 Then
EsPrimo = False
Else
Raiz = Sqr(i)
For j = 3 To Int(Raiz) Step 2
If i Mod j = 0 Then
EsPrimo = False
Exit For
End If
Next j
End If
If EsPrimo Then
CountPrimos = CountPrimos + 1
If CountPrimos > UBound(ListaPrimos) Then
ReDim Preserve ListaPrimos(1 To CountPrimos)
End If
ListaPrimos(CountPrimos) = i
End If
Next i
'Controlamos columnas totales a generar
TotalCols = Application.WorksheetFunction.Ceiling(CountPrimos / LargoColumna, 1)
TotalRows = LargoColumna
'Creamos array final
ReDim FinalArray(1 To TotalRows, 1 To TotalCols)
cont = 1
For c = 1 To TotalCols
For r = 1 To LargoColumna
If cont <= CountPrimos Then
FinalArray(r, c) = ListaPrimos(cont)
cont = cont + 1
Else
Exit For
End If
Next r
Next c
'Volcamos el array a la hoja
Set RangoDestino = ActiveSheet.Range("A1").Resize(TotalRows, TotalCols)
RangoDestino.Value = FinalArray
'Mensaje final
MsgBox "Proceso terminado. Se encontraron " & CountPrimos & " primos hasta " & Limite & ".", vbInformation
Exit Sub
'Gestión de errores
ErrorHandler:
MsgBox "Datos inválidos o proceso cancelado. Asegúrate de escribir algo como 50,600", vbExclamation
End Sub