sábado, 5 de octubre de 2024

Funciones. Matrices.

Eliminar datos repetidos

Con frecuencia trabajamos con listas o matrices que, por diversas razones, contienen datos repetidos. Estas repeticiones pueden formar parte de lo que nos interesa de los datos (su frecuencia), o ser debidas a errores en la entrada de datos de la que surgió ese listado. El tratamiento de ambas circunstancias es diferente, pero en ambos casos, en un momento determinado nos puede interesar eliminar esas repeticiones. Para ello he desarrollado esta función que, en principio, está pensada para trabajar con datos numéricos.


La función que presento aquí, ElimRep(), es una función compleja por lo que podría dividirse en partes, conformando cada una de ellas una función simple, pero como todas sus partes contribuyen al logro del objetivo, he considerado que podría ser interesante contar con una única función que realice todo el trabajo, sin que sea necesario recurrir a llamadas a terceras funciones, lo que siempre implica mayor complejidad en el desarrollo del algoritmo y, en consecuencia, mayor probabilidad de error.

El objetivo de la función queda reflejado en su nombre: eliminar los datos (aquí numéricos) que pueden estar repetidos en una lista o matriz unidimensional (matriz simple, si se prefiere). Se presupone que este procedimiento es necesario para el logro de determinado objetivo (1). El resultado esperado es una matriz simple o lista en la que cada dato sea único.

Paso a presentar el código de la función, que explico a continuación. No obstante, y aunque no suelo hacerlo, en este caso he considerado pertinente incluir un script que hace uso de la función, tanto para ejemplificar su uso (en abstracto) como para explicar los ajustes que resultaron necesarios para el desarrollo de la propia función. En resumen, lo que a continuación se muestra es un script (en rojo) y la función ElimRep(). lo que explicaré será el código de la función, pero, con fines didácticos, puedo recurrir al script para aclarar alguna cuestión. 

'Script desde el que se llama a la función ---------------------------------------

Sub DatosRepetidos

Dim mDatOr() As Integer 'Matriz original
Dim mDatTrb() As Integer 'Matriz de trabajo
Dim c As Integer

mDatOr() = Array(1,3,4,5,6,1,3,5,7,9,10,21,10,21,12,15,5,12) 'Preservamos para comparaciones

'(1,3,4,5,6,-1,-1,-1,7,9,10,21,-1,-1,12,15,-1,-1) -> Matriz resultante de la sustitución
'(1,3,4,5,6,7,9,10,21,12,15) -> Matriz resultante de la eliminación de repetidos

mDatTrb() = mDatOr() 

mDatTrb() = ElimRep (mDatTrb()) 'Llamada a la función

For c = LBound (mDatTrb()) To UBound(mDatTrb()) 'Procedimiento de visualización de datos finales
MsgBox mDatTrb(c)
Next

End Sub

'Función ----------------------------------

Function ElimRep (a())

Dim mAux() As Integer        'Variables internas de la función

Dim i As Integer, c As Integer, Dato As Integer, nR As Integer

For i = LBound (a()) To UBound(a())    ' Detección y sustitución de repetidos

Dato = a(i)

For c = i + 1 To UBound(a())
If a(c) <> -1 Then
If a(c) = Dato Then
a(c) = -1
End If
End If
Next

Next

nR = 0                                                    'Contabiliza elementos repetidos...

For i = LBound (a()) To UBound(a())
If a(i) <> -1 Then
nR = nR + 1
End If
Next

ReDim mAux(nR-1)                            '... y redimensiona la matriz auxiliar

i = 0

c = 0

For i = LBound (a()) To UBound(a())    'Asigna a la matriz aux. los elementos no repetidos
If a(i) <> -1 Then
mAux(c) = a(i)
c = c+1
End If
Next

ElimRep = mAux()                        'Retorno

End Function 

La función ElimRep () recibe como único parámetro una matriz que contiene elementos posiblemente repetidos. Como variables privadas cuenta con una matriz auxiliar o de trabajo (mAux()) y cuatro variables Integer: dos con funciones de contador (i y c), una para contener el datos de la matriz que sirve de referencia para la búsqueda (Dato) y una última para redimensionar la matriz auxiliar (nR).

Su estructura presenta cierta complejidad, ya que está formada por cuatro ciclos y cuatro condicionales. Las estructuras condicionales (If) se encuentran anidadas dentro de los bucles, estableciendo condicionalidad al funcionamiento de éstos. El resultado es tres bloques ciclo-condicional que ejecutan funciones específicas.

El primer bloque es el de mayor complejidad estructural y funcional. Consta de dos ciclos anidados y dos condicionales dentro del ciclo interno y anidados a su vez. La función de este bloque es recorrer la matriz-parámetro ítem a ítem, identificando los elementos repetidos y sustituyéndolos por un dato pensado para no generar confusiones con los originales (-1), a fin de facilitar el posterior traslado condicionado a la matriz auxiliar.

El segundo ciclo consta de un ciclo que contiene un condicional y su función es recorrer la matriz-parámetro y contabilizar el número de elementos sobre la variable nR, en función de un segundo condicional que excluye los valores repetidos (identificados por su contenido: -1. De este modo obtenemos el dato que nos servirá para redimensionar la matriz auxiliar sin necesidad de tener que aplicar posteriormente y de forma reiterada, las instrucciones ReDim Preserve (2).

Finalmente, el tercer grupo, al igual que el anterior, consta de un ciclo que anida un condicional. Su función es trasladar a la matriz auxiliar (mAux()), previamente redimensionada según el valor de nR (ReDim mAux(nR-1)) (3), el contenido de la matriz-parámetro que no contiene como dato el establecido como sustituto de los valores repetidos, esto es, -1.

Finaliza la matriz con el Return ElimRep = mAux() que al ser devuelto al script sobre la matriz secundaria (de trabajo), sustituye completamente a ésta, siendo que originalmente quedó conformada como copia de la matriz principal (mDatTrb() = mDatOr()). Este modo de proceder obedece al objetivo didáctico de este ejemplo  (4).

El bloque crítico es, como dije, el primero de los tres, que posiblemente requiera una explicación más detallada de su funcionamiento.

  • El primer ciclo, o ciclo principal, posicionado en el exterior de la estructura, sobre el contador i, recorre las posiciones de la matriz se trabajo y asigna a Dato el contenido de cada una de ellas (Dato = a(i)). 
  • Gracias al segundo ciclo, anidado en el primero y que se rige por el contador c, realizamos un segundo recorrido sobre las posiciones de la matriz. Dentro de este bucle actúan las dos condiciones anidadas, que son las responsables de el que proceso se desarrolle correctamente.
  •  La primera condición nos asegura que no actuamos sobre las posiciones con el valor sustitutivo (-1) (If a(c) <> -1 Then)
  • Y la segunda que se ejecuta la sustitución (a(c) = -1) cuando se da la condición correcta (If a(c) = Dato Then)
El resultado que se obtiene al finalizar este proceso es que matriz-parámetro mantiene los elementos  no repetidos, siendo sustituidos paulatinamente los que se repiten por el valor -1. Sobre el conocimiento de este hecho actúan los dos bloques que siguen, según las funciones respectivas, que describí antes.

NOTAS

(1) No interesa aquí cual pueda ser éste, pero evidentemente tiene que ajustarse a la naturaleza de los datos y del procedimiento que se quiere desarrollar. El ejemplo más simple es eliminar errores, pero no es el único.
(2) Lo que sería la alternativa lógica, pero que no genera demasiada confianza por la posibilidad de que provoque la pérdida de datos previos.
(3) Según lo dicho antes. A nR se le aplica el consabido factor de corrección (nR-1), en función de índice inicial de las matrices en OOo Basic.
(4) En una formulación real no sería precisa esta matriz de trabajo, pero aquí nos sirve para poder monitorizar el proceso y comparar los resultados obtenidos en las diferentes fases del mismo con lo esperado según el modelo de datos implementados en la matriz principal. Este es también el objetivo de los comentarios que contienen los datos de la matriz tras la sustitución de los elementos repetidos y tras su eliminación.