miércoles, 18 de septiembre de 2024

Procedimientos. Datos.

Uso de matrices para acceso a datos.

Aunque ya hablamos de las matrices como [colecciones de datos] como elementos (y objetos) de OOo Basic (1), son muchos y diferentes los usos que podemos hacer de una matriz; no sólo sobre Calc, pero sí especialmente por el manejo de los datos que permite una hoja de cálculo. Una de ellas es dotar de contenido a una matriz pasándole los datos de un conjunto de celdas, generando así un código que se puede asimilar al trabajo con bases de datos. En esta entrada nos vamos a limitar a explicar cómo utilizar las matrices para acceder al contenido de un listado previamente creado en una hoja de cálculo, presentación de información ésta muy frecuente en este servicio, como ya sabes (2).


Ya sabemos cómo implementar datos a una matriz directamente desde código mediante la función Array() (mDatos() = Array(1,2,3,4,5) como ejemplo simple) y como hacerlo usando el índice del elemento (mDatos(1) = 1) (3). Pero cuando tenemos una lista o una tabla de datos ya creada, usar matrices para acceder a los datos que contiene dicha tabla nos permite trabajar posteriormente con su contenido. 

En realidad el procedimiento no difiere mucho de hacerlo mediante variables, pero cuando los datos son muchos, usar variables es poco eficiente.

Para el objetivo de esta entrada vamos partir de una tabla previamente creada en una hoja de cálculo y suponer que deseamos pasar su contenido a un conjunto de matrices para trabajar con esos datos (4). La tabla que tomo como referencia es la siguiente (5)


Como puedes ver, se trata de una simple tabla de datos de alumnos (por ejemplo, del alumnado NEAE de un centro) y deseamos capturar estos datos para mostrar una ficha individual por alumno (6). Podríamos trabajar con variables, accediendo secuencialmente a cada una de las celdas, y mostrando en bucle el contenido de cada registro de esta base de datos; aunque para un objetivo tan simple como el enunciado podría ser una solución bastante satisfactoria, pero si quisiéramos tener disponibles todos los datos de la tabla para tareas posteriores, trabajar con variables nos obligaría a generar tantas como celdas (campos) hay en la tabla, lo que hace poco recomendable, por no decir inviable trabajar con variables (7). Afortunadamente podemos hacerlo empleando matrices. Veamos cómo.

Como casi siempre , no hay una única solución, pero no todas son iguales o igual de eficiente. Aquí veremos dos, la primera, menos eficiente, pero necesaria para comprender la dinámica de trabajo y la segunda, más eficiente y más compleja, aunque  no especialmente difícil. Empecemos por la primera.

Sub CrearMatrizDesdeDatos

'Variables para acceso a celdas
Dim oHoja As Object
Dim oCelda As Object

'Variables para ciclos y tratamiento
Dim i As Integer
Dim vTratam1 As String, vTratam2

'Matrices para recogida de datos
Dim mAlId (19) As String, mAlSexo(19) As String, mAlNom(19) As String, mAlAp(19) As String, mAlCurso(19) As String

'Acceso a datos
oHoja = ThisComponent.getSheets().getByName("Datos")
'Ciclos para pasar datos de celdas a las matrices

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("A" & i+2)
mAlId (i) = oCelda.getString()
Next

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("B" & i+2)
mAlSexo (i) = oCelda.getString()
Next

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("C" & i+2)
mAlNom (i) = oCelda.getString()
Next

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("D" & i+2)
mAlAp (i) = oCelda.getString()
Next

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("E" & i+2)
mAlCurso (i) = oCelda.getString()
Next

 (Aquí va un bucle que permite visualizar el resultado)

End Sub

Aunque creo que los comentarios son suficientemente explicativos (al menos eso he pretendido), no estará de más explicar algunas cuestiones (8), al menos la principal. Me refiero a esa estructura cíclica que se repite cinco veces, una por cada columna de datos de la tabla. Veamos uno de estos bucles, el primero, por ejemplo:

For i= 0 To 19
oCelda = oHoja.getCellRangeByName("A" & i+2)
mAlId (i) = oCelda.getString()
Next

Haciendo uso de la estructura For...Next y del contador i repetimos veinte veces el siguiente proceso: 
  • Primero accedemos a las celdas mediante la variable objeto oCelda (oCelda = oHoja.getCellRangeByName("A" & i+2)). El posicionamiento en las celdas se basa en la unión de la letra que identifica la columna (A)con la suma del valor de i+2 ("A" & i+2) Esto se explica por ser el valor inicial de i=0 (For i= 0 To 19) y la posición de la primera celda significativa A2.
  • La segunda instrucción del ciclo pasa el dato contenido en las celdas a los elementos definidos en la matriz receptora (mAlId (i) = oCelda.getString()), cada uno de ellos identificado por el valor de i a lo largo del ciclo (mAlId (i))
El hecho de que esta estructura se repita cinco veces da una idea de cómo podemos mejorar la eficiencia de este procedimiento: dado que se repite el procedo podemos pasarlo a una función que lo asuma, reduciendo el script principal a una mera llamada a la función. Esta será la segunda solución.

Sub CrearMatrizConFuncion

'Variables para ciclos y tratamiento
Dim i As Integer
Dim vTratam1 As String, vTratam2

'Matrices para recogida de datos
Dim mAlId (19) As String, mAlSexo(19) As String, mAlNom(19) As String, mAlAp(19) As String, mAlCurso(19) As String

'Llamada a función para cargar datos en las matrices
mAlId() = PasarDatos("A")
mAlSexo() = PasarDatos("B")
mAlNom() = PasarDatos("C")
mAlAp() = PasarDatos("D")
mAlCurso() = PasarDatos("E")

(Aquí va un bucle que permite visualizar el resultado)

End Sub

' FUNCIÓN --------------------------------------

Function PasarDatos (Col As String) As Object

'Variables para acceso a celdas
Dim oHoja As Object
Dim oCelda As Object
'Matriz para recogida de datos
Dim mDatos (19) As String
'Variable para recorrer ciclo
Dim i As Integer

'Acceso a datos
oHoja = ThisComponent.getSheets().getByName("Datos")
'Acceso al contenido de la columna de datos
For i= 0 To 19
oCelda = oHoja.getCellRangeByName(Col & i+2)
mDatos(i) = oCelda.getString()
Next

'Paso de valores de la matriz
PasarDatos = mDatos()

End Function

Obsérvese cómo las llamadas a la función desde el script permiten simplificar el código anterior (mAlId() = PasarDatos("A")). El parámetro que pasamos a la función es el identificador de la columna ("A" en este caso) y al asignar el resultado que devuelve la función a la matriz deseada, asignamos los datos que a su vez captura la matriz-base de la función (mDatos()) a la matriz receptora (aquí mAlId()).

La función (PasarDatos (Col As String) As Object) es de tipo objeto por trabajar con el objeto oCelda (9) y asume el acceso al documento y a la hoja (oHoja = ThisComponent.getSheets().getByName("Datos")) y a los objetos Celda (oCelda = oHoja.getCellRangeByName(Col & i+2)), en este caso igual que vimos en el procedimiento primero, esto es, dentro de un bucle que recorre el conjunto de celdas de cada columna de datos. Además, al tratarse de un procedimiento "universal", sólo necesitamos una variable para acceder a los datos (Dim mDatos (19) As String), proceso que se realiza (también de forma similar a cómo vimos en el primer procedimiento), dentro del ciclo, como segunda instrucción del mismo (mDatos(i) = oCelda.getString()). Finalmente asignamos la matriz mDatos() como "Return" de la función (10)

Para finalizar y por eso de que no quede sin explicar, voy a comentar el ciclo en que se basa la generación del "informe" que nos permite comprobar que todo ha funcionado correctamente (11). Cumple, por tanto, función de generador del output y, como recordarás, es el mismo en ambas formulaciones de la solución al tema que se trabaja en esta entrada.

'Bucle para mostrar el contenido de las matrices
For i = 0 To 19
If mAlSexo(i) = "H" Then
vTratam1 = "del alumno"
vTratam2 = "Alumno"
Else
vTratam1 = "de la alumna"
vTratam2 = "Alumna"
End If
MsgBox "FICHA Nº " & i+1 & Chr(13) &_
   "Id " & vTratam1 & ": " & mAlId(i) & Chr(13) &_
       vTratam2 & ": " & mAlNom(i) & " " & mAlAp(i) & Chr(13) &_
       "Curso: " & mAlCurso(i)
Next

Puedes distinguir una estructura For...Next que incluye (anida) una estructura condicional (If) y un mensaje de cierta complejidad basado en un MsgBox.

El condicional utiliza los valores contenidos en la matriz mAlSexo() (columna B de nuestra tabla de datos) para condicionar las expresiones gramaticales ajustas al género gramatical de dos expresiones del mensaje. Estas fórmulas gramaticales quedan asociadas a las variables de tratamiento (vTratam1 vTratam2). Al preceder  el condicional al mensaje, cada recorrido del ciclo nos permite asignar el género gramatical adecuado al sexo del alumno.

El mensaje, además de ajustar las expresiones al género gramatical, resume los contenidos del resto de las matrices que hemos creado y por la propia estructura de la tabla y el modo en que "recuperamos" los datos, esos contenidos quedan asociados como si de un registro unitario se tratara gracias a que ocupan la misma posición en sus respectivas matrices. La impresión final que proporciona nuestro MsgBox es que mostramos la información de cada uno de los alumnos.

Documento. En [Matrices2.ods] puedes encontrar el código explicado en esta entrada.

NOTAS

(1) Además de la entrada anterior, tienes más información básica sobre las matrices en las entradas que siguen a la anterior en la sección [OOo Basic] de este blog. Te remito también a lo que aporta  Mauricio Baeza (2020) en [Aprendiendo OOo Basic], pag. 49 y siguientes.
(2) Dentro del escaso uso que se hace de las hojas de cálculo por parte de los SEO, quizá sea esta de crear listados y tablas una de las más frecuentes. A ello contribuye la demanda de datos por parte de la Administración, que con cierta frecuencia aporta incluso el modelo o soporte del documento, y la facilidad que presenta este soporte para realizar tareas de este tipo. En ocasiones estas tablas funcionan (o pueden funcionar) como bases de datos sencillas.
(3) Esta fórmula también nos sirve para acceder al contenido de una matriz por su índice para, por ejemplo, asignarlo a una variable (vDato = mDato(0))
(4) No entramos en cómo se creo esta tabla de datos y en estos momentos tampoco es relevante en qué consista y cómo se desarrolle el trabajo posterior con esos datos.
(5) Como puede suponer, los datos son inventados. Cualquier coincidencia con datos reales es eso, pura coincidencia.
(6) Como dije en 4, el tratamiento posterior de los datos es irrelevante para el objetivo de esta entrada, así que no te esperes maravillas al respecto. En realidad mostrar esos datos en forma de "Ficha personal" tiene la función de permitirnos comprobar que nuestro procedimiento funciona correctamente.
(7) Sin ser una tabla especialmente grande, necesitaríamos 20 x 5 = 100 variables, que no son pocas. Si incrementamos la tabla en 20 registros (filas) más, cosa nada improbable en cuanto a tamaño real de una base datos sencilla, el número total de variables ascendería a 200. No hacen falta tantos registros para que trabajar con variables deje de resultar viable.
(8) De momento dejaré sin explicar el bucle final que puedes encontrar en el script de la hoja de cálculo que adjunto. Este bucle se repite en ambos procedimientos y es secundario para lo que nos ocupa. Pero dado que, como dije en 6, sirve para comprobar el funcionamiento del procedimiento, no estará de más dedicarle un tiempo al finalizar la explicación de las dos versiones de la solución a nuestro problema.
(9) Siendo sincero, esta es la conclusión a la que he llegado, pero no tengo certeza absoluta de que esta sea la causa. Sí he comprobado que sin esta categorización de la función (por ejemplo, definiéndola como de tipo String, por ser de este tipo los datos que recuperamos de las celdas), se produce un error de ejecución.
(10) Lo de "Return" es una forma de significar la función de la instrucción PasarDatos = mDatos() como forma de retornar o devolver datos de las funciones en OOo Basic. En otros lenguajes, como Python, se emplea esa instrucción, pero en OOo Basic la fórmula consiste en utilizar el nombre de la función a mod de variable a la que se asigna el contenido a retornar. Por cierto que nuestra función nos permite comprobar que es posible devolver una colección de datos (esto es, más de uno) cuando utilizamos una matriz como contenedor/referencia.
(11) Repito aquí de ya dicho en las notas 4 y 6 y me ahorro más comentario.