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 DatosRepetidosDim mDatOr() As Integer 'Matriz originalDim mDatTrb() As Integer 'Matriz de trabajoDim c As IntegermDatOr() = 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 repetidosmDatTrb() = mDatOr()mDatTrb() = ElimRep (mDatTrb()) 'Llamada a la funciónFor c = LBound (mDatTrb()) To UBound(mDatTrb()) 'Procedimiento de visualización de datos finalesMsgBox mDatTrb(c)NextEnd 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.