jueves, 3 de octubre de 2024

Funciones. Matrices.

Borrar elementos.

Otra acción básica a realizar con una matriz es borrar alguno de sus elementos. Tampoco OOo Basic dispone de una función integrada para realizar esta tarea, así que es necesario recurrir a funciones desarrolladas por la comunidad de usuarios. También aquí tomo como referencia la función presentada por [Open Office.es]

La función aDel() es una función extensa y de cierta complejidad estructural, como podrás ver a continuación, por lo que su análisis requiere tiempo, así que es posible que esta entrada resulte algo más extensa de lo habitual.

Empezando por los parámetros que recibe, aDel() presenta tres parámetros: la función referenciada (ByRef a()), un valor numérico que marca la posición de inicio de la búsqueda (nIni) y un tercer parámetro también numérico y de carácter opcional, que indica la posición final de la búsqueda.

Dado que el uso del tercer parámetro es opcional, es posible que no sea referenciado en el script, por lo que dentro del código de la función se controla esta posibilidad mediante la función IsMissing() dentro de un condicional que igual su contenido al valor del parámetro de inicio de búsqueda (If IsMissing(nFin) Then nFin = nIni) (1).

Además de los parámetros anteriores, aDel() también cuenta con una matriz de trabajo (aTmp()) y tres variables numéricas que cumplen diferentes funciones (u, r y n) que iremos viendo más adelante.

Este es el código de la función:

Function aDel( ByRef a(), nIni As Long, Optional nFin As Long)

Dim aTmp(), u As Long, r As Long, n As Long

If IsMissing(nFin) Then nFin = nIni

On Local Error GoTo error_aDel

u = UBound(a())

If nIni = -1 Then
ReDim aTmp(u-1)
For n=0 To u-1
aTmp(n)=a(n)
Next
Else
ReDim aTmp(u-(nFin-nIni)-1)
For n=0 To nIni-1
aTmp(n)=a(n)
Next
r=n
For n=nFin+1 To u
aTmp(r)=a(n)
r=r+1
Next
End If

a() = aTmp()

aDel = a()

Exit Function

error_aDel:
ReDim a()
aDel = a()

End Function

Lo primero que destaca en él es la complejidad estructural de la que te hablaba en la presentación de la función, la cual queda perfectamente representada por el núcleo central de su sintaxis: nada menos que un condicional If...Else que contiene en cada una de sus ramas uno y dos bucles respectivamente:

  • En el primero se da respuesta a la posible condición de que el parámetro numérico de inicio (nIni) presente un valor negativo (If nIni = -1): para ese caso, se borra el último elemento.
  • En el segundo (Else), se desarrolla el proceso central de la función: el borrado del elemento o elementos comprendidos entre el valor posicional de inicio y final.
Pero no son estas las únicas condicionalidades que contempla la función; aun antes de entrar en la sección central del código (la indicada antes), se contempla la posibilidad de que se produzca un error interno (On Local Error GoTo error_aDel) y se deriva a un segmento final del código de la función donde se da respuesta a este error (marca posicional -> error_aDel:), haciendo uso de uno de los procedimientos propios del lenguaje Basic cuando estaba pensado únicamente dentro del paradigma de la programación lineal (2).

Suponiendo que no se produce este error interno, lo primero que se hace es pasar a la variable el valor resultante de aplicar la función UBound() a la matriz-parámetro (u = UBound(a())). De este modo u referencia el tamaño de dicha matriz y puede ser usada con diferentes fines, como tendremos ocasión de comprobar, dentro de la estructura central de la función.

Analicemos en detalle y por partes, cómo se concreta esta compleja estructura nuclear que, como dije antes, está definida como una bifurcación con dos ramas u opciones.

La primera de ella, dijimos que trataba el caso de que el valor del parámetro de inicio del recorrido por la matriz tomara un valor negativo (nIni = -1), desarrollado en ese caso el borrado del elemento final de la matriz-parámetro, lo que se consigue en dos fases:

  • Primero se redimensiona la matriz de trabajo al valor u-1
  • Y después, mediante un bucle de recorrido 0u-1 (3) (For n=0 To u-1) se van pasando los valores de la matriz-parámetro a la auxiliar (aTmp(n)=a(n)). 
El resultado es que, al haber limitado el recorrido a una posición anterior a la final de la matriz-parámetro, el resultado es que la matriz auxiliar contiene todos los elementos de esa matriz menos el último.

De cumplirse lo que indica esta rama del condicional, se pasa al bloque final del código en el que se pasa a la matriz-parámetro el contenido de la matriz auxiliar (a() = aTmp()) (4) y se retorna la matriz-parámetro modificada como valor de la función (aDel = a()).

La segunda rama del condicional (Else) es la principal (la esperada) y la de mayor complejidad de funcionamiento y estructural (5). Empieza por redimensionar la matriz auxiliar (ReDim aTmp(u-(nFin-nIni)-1)) empleando como valor numérico el resultado de un complejo cálculo que parte del valor de u (el "tamaño" de la matriz-parámetro) menos la diferencia entre los parámetros de inicio y fin del recorrido por dicha matriz (nFin-nIni), restando -1 al resultado de la resta anterior (6). De este modo se consigue que la matriz auxiliar tenga la dimensión ajustada a los elementos que realmente va a contener al final del proceso; el cual se desarrolla en dos fases, cada una de ellas mediante un bucle:

En la primera fase se pasa a la matriz auxiliar los elementos de la matriz-parámetro (aTmp(n)=a(n)) situados desde su posición 0 hasta el elemento inmediatamente anterior al valor de posición de inicio del segmento de elementos a borrar. Esto se consigue estableciendo como valores del bucle n = 0 como inicio y nIni-1 como final del recorrido.

Una vez finalizada esta fase, se asigna a la variable r el valor de la variable n (r=n) (7), a fin de utilizar ambas dentro del segundo bucle For.

Este segundo bucle tiene como misión pasar a la matriz auxiliar los datos del intervalo superior de la matriz-parámetro (nFin -> UBound(a()), esto es aTmp(r)=a(n).

Se observará que ahora se plantea un problema que obliga a establecer referentes específicos para las posiciones a recorrer en función de la matriz que determina el recorrido (a() y no aTmp()); siendo el segundo intervalo de esta matriz el que interesa, el valor de inicio del bucle no puede ser 0, sino la posición inmediatamente superior (o siguiente) al valor de nFin (límite superior del intervalo de elementos a borrar), de ahí que ese referente se enuncie como n=nFin+1; el final del recorrido de este bucle es el mismo que UBound(a()), esto es, el valor de la variable (8).

Pero mientras que n es adecuado para recorrer la matriz-parámetro (su segundo intervalo), no lo es para recorrer la matriz auxiliar, puesto que ambas tienen dimensiones diferentes. Además, también el recorrido por la matriz auxiliar debe hacerse no desde su posición 0, sino desde la posición posterior a la que ya fue objeto de asignación en el bucle anterior. Es por ello por lo que se asigna a la variable-contador r el valor final de n en el ese primer bucle (9).

Este contador (r) se usa como referente para indicar las posiciones a recorrer en la matriz auxiliar (aTmp(r)), mientras que se emplea el contador n para recorrer la matriz-parámetro (a(n)), pero debemos incrementar el valor de r de forma específica e independiente del incremento que sufre n (r=r+1) para evitar errores en el posicionamiento en aTmp(r).

Una vez finalizado este bucle, se desarrolla el mismo proceso que se dio anteriormente, ya que es común (está fuera del condicional principal): 

a() = aTmp() -> aDel = a()

... y se sale de la función (Exit Function) (10)

NOTAS

(1) La función también controla la posibilidad de que el valor del parámetro de inicio sea -1, pero en este caso lo que sucede es que se elimina el último elemento. En la entrada se explicará el procedimiento que se sigue para ello.
(2) Entiendo que se trata de este modelo de programación, ya que se usa la instrucción GoTo y se deriva a una referencia posicional (error_aDel). En aDel() se contempla una solución para el caso de que la matriz-parámetro sólo conste de un elemento o ninguno. En este caso se redimensiona a 0 (ReDim a()) y se devuelve así como return (aDel = a()).
(3) Como referente para establecer el límite inferior del recorrido se usa la variable privada n.
(4) Y a la vez se redimensiona, o lo que es lo mismo, se sustituye su contenido y dimensión al que tiene la matriz auxiliar.
(5) Ambas características son evidentes; la primera por el complejo uso que se hace de las variables numéricas privadas y los parámetros numéricos y la segunda por la presencia de dos bucles sucesivos.
(6) Esta última resta es para ajustar la dimensión deseada para  aTmp() a lo que deriva del uso de 0 como posición de inicio de toda matriz en OOo Basic.
(7) Esto es, el valor que en ese momento tiene n, variable usada como contador en el bucle anterior, que será igual al número de ciclos desarrollados hasta alcanzar la posición nIni-1. Si nIni es posición 2, nIni-1 será la posición 1, con lo que el bucle se habrá realizado 3 veces: dos con resultado positivo (para valores n = 0 y n = 1) y el tercero (n=2) que supone el cierre del bucle, ya que se alcanza la posición (nIni-1)+1, que supera el límite superior (To) del bucle.
(8) Dado que anteriormente u = UBound(a())
(9) Esto es r=n
(10) Esta salida anticipada de la función se debe a que en su parte final se recogen las acciones a desarrollar en función de la detección del error interno (On Local Error GoTo error_aDel). Obviamente, salvo que se de ese error no nos interesa redimensionar a(), por lo que esto implica de borrado de los datos pasados mediante los procesos descritos.


No hay comentarios:

Publicar un comentario

Comenta esta entrada