Las tuplas son colecciones heterogéneas de datos, en las que cada elemento de la colección puede ser de un tipo diferente.
La diferencia básica entre los vectores o matrices y las tuplas es esto, que en las tuplas los elementos pueden ser de distinto tipo. En C, las tuplas no existen como tipo de dato, pero se pueden simular con estructuras.
Un tupla (*record* o struct en inglés) es un objeto que contiene otros objetos que denominaremos **campos**. Cada uno de los campos tiene un nombre para identificarlo y es de un tipo de datos.
Por ejemplo, un documento de identidad puede ser representado como una tupla con los campos `nombre`, `apellidos`, `día de nacimiento`, `mes de nacimiento`, `año de nacimiento`, `número de documento`, `dirección`, `teléfono`, `email`, etc. Cada uno de estos campos es un tipo de dato diferente.
En lenguaje algoritmico y en C:
```c
algorithm alg12_01
var
prename: string;
name: string;
birthDay: integer;
birthMonth: integer;
birthYear: integer;
idNumber: integer;
idChar: char;
end var
end algorithm
#include <stdio.h>
#define MAX_NAME_LEN 25
int main(int argc, char** argv) {
/* Variable definition*/
char prename[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
int birthDay;
int birthMonth;
int birthYear;
int idNumber;
char idChar;
}
```
Con esta información sería complicado controlar las distintas variables, por que se pueden mezclar los datos. Por ejemplo, si se quiere cambiar el nombre de una persona, se tendría que cambiar el valor de la variable `name` y no se podría saber si se ha cambiado correctamente.
Para solucionar este problema, se pueden agrupar los datos en una estructura. En C, se puede definir una estructura de la siguiente manera:
```c
type
tPeople = record
prename: string;
name: string;
birthDay: integer;
birthMonth: integer;
birthYear: integer;
idNumber: integer;
idChar: char;
end record
end type
algorithm alg12_02
var
you: tPeople;
end var
end algorithm
#include <stdio.h>
#define MAX_NAME_LEN 25
typedef struct {
char prename[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
int birthDay;
int birthMonth;
int birthYear;
int idNumber;
char idChar;
} tPeople;
int main(int argc, char** argv) {
/* Variable definition */
tPeople you;
}
```
Ahora, para acceder a un campo de una variable de tipo estructurada se emplea el nombre de la variable y el nombre del campo unido por el delimitador `.`. Por ejemplo, para acceder al campo `name` de la variable `you` se escribe `you.name`.
Por ejemplo, para rellenar una ficha de una persona:
Otro ejemplo, para ordenar los datos de dos personas:
```c
type
tPeople = record
prename: string;
name: string;
birthDay: integer;
birthMonth: integer;
birthYear: integer;
idNumber: integer;
idChar: char;
end record
end type
algorithm ageOrder
var
old, young, peopleAux: tPeople;
end var
{read phase first tPeople}
old.prename := readString();
old.name := readString();
old.birthDay := readInteger();
old.birthMonth := readInteger();
old.birthYear := readInteger();
old.idNumber := readInteger();
old.idChar := readChar();
{read phase second tPeople}
young.prename := readString();
young.name := readString();
young.birthDay := readInteger();
young.birthMonth := readInteger();
young.birthYear := readInteger();
young.idNumber := readInteger();
young.idChar := readChar();
{age sort phase}
if (old.birthYear > young.birthYear) or (old.birthYear = young.birthYear and old.birthMonth > young.birthMonth) or (old.birthYear = young.birthYear and old.birthMonth = young.birthMonth and old.birthDay > young.birthDay) then
Todos los campos de un dato estructurado se mantienen en la memoria ocupando posiciones consecutivas. La dirección de memoria de un dato estructurado es la dirección de memoria del primer campo.
Por ejemplo:
```c
type
tDate = record
day: integer;
month: integer;
year: integer;
end record
end type
algorithm alg12_05
var
today: tDate;
end var
end algorithm
#include <stdio.h>
/* Types definition */
typedef struct {
int day;
int month;
int year;
} tDate;
int main(int argc, char** argv) {
/* Variable definition */
tDate today;
}
```
La variable today ocupa 12 bytes en memoria, 4 bytes para cada campo. La dirección de memoria de today es la dirección de memoria del campo day.
Debe comprenderse que el lugar dentro de la tupla donde comienza un determinado campo no tiene ninguna relación con el tipo de este campo, solo depende del espacio ocupado por los campos anteriores.
Si hubiéramos definido una tupla para guardar documentos de identidad, los dos campos serían de tipos diferentes:
Cada campo estaría almacenado ocupando las posiciones correspondientes a su tipo base. En este caso, el campo idNumber ocuparía 4 bytes y el campo idChar ocuparía 1 byte. La dirección de memoria de identification sería la dirección de memoria del campo idNumber.
Cuando se define una tupla no existe ninguna limitación para declarar sus campos. Nada impide definir una tupla que contenga otra tupla.
Por ejemplo:
```c
type
tDate = record
day: integer;
month: integer;
year: integer;
end record
tDni = record
idNumber: integer;
idChar: char;
end record
tPeople = record
prename: string;
name: string;
birth: tDate;
id: tDni;
end record
end type
algorithm alg12_07
var
you: tPeople;
end var
end algorithm
#include <stdio.h>
#define MAX_NAME_LEN 25
typedef struct {
int day;
int month;
int year;
} tDate;
typedef struct {
int idNumber;
char idChar;
} tDni;
typedef struct {
char prename[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
tDate birth;
tDni id;
} tPeople;
int main(int argc, char** argv) {
/* Variable definition */
tPeople you;
}
```
En este caso, la variable `you` ocupa 56 bytes en memoria, 25 bytes para cada campo de tipo `string`, 4 bytes para cada campo de tipo `integer` y 1 byte para cada campo de tipo `char`. La dirección de memoria de `you` es la dirección de memoria del campo `prename`.
El uso de estas tuplas con tuplas (tuplas jerárquicas) obliga también a utilizar una notación jerárquixa para acceder a estos campos.
Por ejemplo:
-`you.name` es el apellido almacenado.
-`you.birth.day` es el día de nacimiento almacenado.
-`you.id.idNumber` es el número de documento almacenado.
Si el campo es de un tipo básico, ya tenemos acceso al valor de este tipo. Si el campo en cuestiónes una tupla, tenemos acceso a la tupla completa y para llegar a uno de los campos deberemos emplear de nuevo el operador punto, seguido del nombre del campo al que se quiera acceder.
Si en un programa los datos que hacen referencia a una entidad (por ejemplo, persona) son lo suficientemente complejos como para crear tuplas, es muy probable que se tenga que trabajar con colecciones de este tipo de objectos. La estructura de datos adecuada son los vectores (o matriz).
Ejemplo con los datos de los estudiantes de un aula:
```c
type
tDate = record
day: integer;
month: integer;
year: integer;
end record
tDni = record
idNumber: integer;
idChar: char;
end record
tPeople = record
prename: string;
name: string;
birth: tDate;
id: tDni;
end record
end type
algorithm alg12_08
var
studentGroup: vector[50] of tPeople;
end var
algorithm
#define MAX_NAME_LEN 25
/* Types definition */
typedef struct {
int day;
int month;
int year;
} tDate;
typedef struct {
int idNumber;
char idChar;
} tDni;
typedef struct {
char prename[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
tDate birth;
tDni id;
} tPeople;
int main(int argc, char** argv) {
/* Variable definition */
tPeople studentGroup[50];
}
```
Para hacer referencia a una posición determinada del vector, nos estaremos refiriendo a un record completo y, para acceder a los campos, deberemos emplear el punto y el identificador del campo.
Por ejemplo:
-`studentGroup[12]` es la tupla que contiene toda la información del estudiante que está en la posición 12 del vector.
-`studentGroup[12].name` es el apellido del estudiante que está en la posición 12 del vector.
-`studentGroup[12].birth.day` es el día de nacimiento del estudiante que está en la posición 12 del vector.
En el caso de que la declaración de la variable sea una matriz, solo se deberá tener en cuenta la doble indexación de la matriz.
Por ejemplo, una escuela de 6 grupos de 50 estudiantes:
```c
type
tDate = record
day: integer;
month: integer;
year: integer;
end record
tDni = record
idNumber: integer;
idChar: char;
end record
tPeople = record
prename: string;
name: string;
birth: tDate;
id: tDni;
end record
end type
algorithm alg12_09
var
school: matrix[6][50] of tPeople;
end var
algorithm
#define MAX_NAME_LEN 25
/* Types definition */
typedef struct {
int day;
int month;
int year;
} tDate;
typedef struct {
int idNumber;
char idChar;
} tDni;
typedef struct {
char prename[MAX_NAME_LEN];
char name[MAX_NAME_LEN];
tDate birth;
tDni id;
} tPeople;
int main(int argc, char** argv) {
/* Variable definition */
tPeople school[6][50];
}
```
En este caso, bien en lenguaje algorítmico o en C:
-`school[2]` hace referencia a un vector, un grupo completo de estudiantes (el de segunda posición).
-`school[2][12]` hace referencia a una tupla, la correspondiente al estudiante 12 del grupo 2.
-`school[2][12].name` serían los apellidos del estudiante 12 del grupo 2.
-`school[2][12].birth.year` sería el año de nacimiento del estudiante 12 del grupo 2.
Muchas veces se necesita definir tuplas para representar una determinada entidad y una de las informaciones es realmente una colección de informaciones más simples.
Por ejemplo, si queremos crear un dato estructurado para mantener la información de una asignatura. Tendremos el nombre de la asignatura, las calificaciones de 5 actividades de evaluación continua (A, B, etc), la calificación del examen y la calificación final. Un record corresponde a estas necesidades:
También en este caso tendremos que ir con cuidado con las referencias.
-`computerScience` hace referencia a la tupla completa; toda la información de la asignatura.
-`computerScience.activitiesMarks` hace referencia a un vector que contiene las calificaciones de todas las actividades.
-`computerScience.activitiesMarks[3]` en lenguaje algorítmico hace referencia a la calificación de la actividad 3 obtenida en la asignatura (recordemos que la primera tiene el índice 1), y en lenguaje C hace referencia a la calificación de la actividad 4 obtenida en la asignatura (recordemos que la primera posición tiene el índice 0). En este ejemplo, la nota de cada actividad está definida por un caracter: 'A', 'B', 'C', 'D', 'E', 'N'.
Esta estructura de datos puede ir creciendo en función de nuestras necesidades. Podemos crear un record que tenga algún campo donde se almacene una colección de datos del mismo tipo (Un vector) y que cada dato sea a la vez un dato estructurado.
Cualquier entidad real de una cierta dimensión se puede representar mediante una estructura de datos de complejidad creciente. Por ejemplo, en un hospital pequeñito tendremos algunos conjuntos de información por entidades que merece la pena tener en records:
- Información sobre la plantila de médicos (Datos personales, la especialidad, cuenta domiciliación bancaria, nómina, etc).
- Información sobre los pacientes (Datos personales, historial médico, pruebas realizadas, diagnóstico, tratamiento, etc).
- Información sobre las habitaciones (Número, planta, tipo, precio, etc).