# PEC 6 [Volver a la página principal de "Fundamentos de la Programación"](../README.md) **Índice** - [PEC 6](#pec-6) - [18. Tipos abstractos de datos](#18-tipos-abstractos-de-datos) - [18.1. Tipo abstracto de datos (TAD)](#181-tipo-abstracto-de-datos-tad) - [18.1.1. Ejemplos de especificación e implementación](#1811-ejemplos-de-especificación-e-implementación) - [18.1.1.1. Números naturales](#18111-números-naturales) - [18.1.1.2. Números binarios](#18112-números-binarios) - [18.2. TAD para representar secuencias de elementos](#182-tad-para-representar-secuencias-de-elementos) - [18.2.1. El TAD Pila (tStack)](#1821-el-tad-pila-tstack) - [18.2.1.1. Lista de operaciones sobre tStack](#18211-lista-de-operaciones-sobre-tstack) - [18.2.1.2. Implementación](#18212-implementación) - [18.2.1.3. Ejemplo de uso](#18213-ejemplo-de-uso) - [18.2.2. El TAD Cola (tQueue)](#1822-el-tad-cola-tqueue) - [18.2.2.1. Lista de operaciones sobre tQueue](#18221--lista-de-operaciones-sobre-tqueue) - [18.2.2.2. Implementación](#18222-implementación) - [18.2.2.3. Ejemplo de uso](#18223-ejemplo-de-uso) - [18.2.3. El TAD Lista (tList)](#1823-el-tad-lista-tlist) - [18.2.3.1. Lista de operaciones sobre tList](#18231--lista-de-operaciones-sobre-tlist) - [18.2.3.2. Implementación](#18232-implementación) - [18.2.3.3. Ejemplo de uso](#18233-ejemplo-de-uso) - [18.2.4. Sintaxis para la declaración (Definición de un TAD de tipo pila, cola o lista)](#1824-sintaxis-para-la-declaración-definición-de-un-tad-de-tipo-pila-cola-o-lista) - [19. Navegación de TAD](#19-navegación-de-tad) - [19.1. Ejemplos sobre el TAD pila](#191-ejemplos-sobre-el-tad-pila) - [19.1.1 Ejemplo 1](#1911-ejemplo-1) - [19.1.2 Ejemplo 2](#1912-ejemplo-2) - [19.1.3 Ejemplo 3](#1913-ejemplo-3) - [19.2. Ejemplos sobre el TAD cola](#192-ejemplos-sobre-el-tad-cola) - [19.2.1 Ejemplo 1](#1921-ejemplo-1) - [19.2.2 Ejemplo 2](#1922-ejemplo-2) - [19.2.3 Ejemplo 3](#1923-ejemplo-3) - [19.3. Ejemplos sobre el TAD lista](#193-ejemplos-sobre-el-tad-lista) - [19.3.1 Ejemplo 1](#1931-ejemplo-1) - [19.3.2 Ejemplo 2](#1932-ejemplo-2) - [19.3.3 Ejemplo 3](#1933-ejemplo-3) ## 18. Tipos abstractos de datos Vamos a ver los tipos abstractos de datos (TAD) y cómo se pueden implementar para representar secuencias de elementos como Listas, Colas y Pilas. Estos TADs emulan conceptos que encontramos fuera del mundo de la programación. Unos ejemplos son: - De listas: una lista de la compra, una lista de tareas pendientes, una lista de reproducción de música. - De colas: una cola de espera en un supermercado, una cola de espera en una llamada telefónica. - De pilas: una pila de platos, una pila de libros, una pila de cartas. Todos los TADs tienen una serie de operaciones que se pueden realizar sobre ellos. Por ejemplo, una pila tiene operaciones como `push` y `pop`, una cola tiene operaciones como `enqueue` y `dequeue`, y una lista tiene operaciones como `insert` y `delete`. ### 18.1. Tipo abstracto de datos (TAD) Un TAD consiste en un conjunto de valores (dominio) y el conjunto de operaciones que se pueden aplicar a este conjunto de valores. El concepto de TAD ya existe en los lenguajes de programación bajo la forma de tipos predefinidos como `int`, `float`, `char`, `string`, etc. Pero también podemos definir nuestros propios TADs. Por ejemplo, en C, el tipo de datos `int` tiene como dominio todos los enteros en el rango `[MININT, MAXINT]` y las operaciones que se pueden aplicar: suma, resta, producto, cociente y módulo. Implementar una TAD significa elegir una representación para el conjunto de valores del tipo de datos, así como codificar sus operaciones utilizando esa representación en un lenguaje de programación. Es posible tener varias implementaciones para un mismo TAD, pero dada la definición de un TAD, el comportamiento ha de ser siempre el mismo para cualquier implementación. La utilidad de definir un TAD surge al diseñar nuevos tipos de datos. La idea es que el nuevo TAD solo puede ser manipulado a través de sus operaciones. #### 18.1.1. Ejemplos de especificación e implementación ##### 18.1.1.1. Números naturales El conjunto de valores que engloba este TAD son todos los números enteros mayores o iguales a 0. Las operaciones que definiremos para aplicar a este conjunto son la `suma` y la `división`. ```alg type natural = integer; end type function suma(x: natural, y: natural): natural return x + y; end function function division(x: natural, y: natural): natural return x / y; end function ``` ```c #include typedef unsigned int natural; natural suma(natural x, natural y) { return x + y; } natural division(natural x, natural y) { return x / y; } ``` En esta definición no se han excluído los números negativos. La responsabilidad del correcto comportamiento del tipo yace en la definición de las operaciones. ##### 18.1.1.2. Números binarios El conjunto de valores que engloba este TAD son todos los números en su representación binaria, es decir, expresados en base 2. Las operaciones que definiremos para aplicar a este conjunto son `desplazamientoIzq` y `complemento`. ```alg const MAXBITS: integer:= 64; end const type binario: vector[MAXBITS] of boolean; end type action desplazamientoIzq(inout b:binario, in n integer) var i: integer; end var for i:= n+1 to MAXBITS do b[i-n]:= b[i]; end for for i:= MAXBITS-n to MAXBITS do b[i]:= 0; end for end action action complemento(inout b:binario) var i: integer; end var for i:= 1 to MAXBITS do b[i]:= not b[i]; end for end action ``` ```c #include #define MAXBITS 64 typedef int binario[MAXBITS]; void desplazamientoIzq(binario b, int n) { int i; for (i = n; i < MAXBITS; i++) { b[i-n] = b[i]; } for (i = MAXBITS-n; i < MAXBITS; i++) { b[i] = 0; } } void complemento(binario b) { int i; for (i = 0; i < MAXBITS; i++) { b[i] = !b[i]; } } ``` ### 18.2. TAD para representar secuencias de elementos Una secuencia lineal es un conjunto de tamaño arbitrario de elementos del mismo tipo. Exceptuando el primero y el último, cada elemento tiene un único predecesor y un único sucesor. En general, un TAD que representa secuencias de elementos posee las siguientes operaciones: - Inicializar la secuencia. - Insertar un elemento en la secuencia. - Eliminar un elemento de la secuencia. - Consultar el valor de un elemento en la secuencia. - Consultar el número de elementos de la secuencia. ¿Dónde insertar un elemento? ¿Qué elemento eliminar? ¿Qué elemento se puede consultar? ¿Cuántos elementos hay? Las respuestas a las anteriores preguntas son las que definen el comportamiento de las operaciones y, por lo tanto, el tipo de secuencia. Tabla comparativa de las diferencias entre Pila, Cola y Lista: | Característica | Pila (Stack) | Cola (Queue) | Lista (List) | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Estructura de Datos** | Implementada típicamente con un arreglo estático o una lista enlazada. | Puede ser implementada con un arreglo estático o una lista enlazada. | Generalmente implementada con una lista enlazada o estructura dinámica. | | **Principio de Acceso** | LIFO (Last In, First Out): Último en entrar, primero en salir. | FIFO (First In, First Out): Primero en entrar, primero en salir. | Acceso Aleatorio: Acceso directo a cualquier elemento. | | **Operaciones Básicas** | `initStack()`: Inicializa la pila vacía
`push(elemento)`: Añade un elemento al tope de la pila.
`pop()`: Elimina y retorna el elemento del tope de la pila.
`isEmptyStack()`: Verifica si la pila está vacía.
`isFullStack()`: Verifica si la pila está llena (en caso de implementación estática).
`heightStack()`: Devuelve número de elementos. | `initQueue()`: Inicializa cola
`enqueue(elemento)`: Añade un elemento al final de la cola.
`dequeue()`: Elimina y retorna el elemento al frente de la cola.
`head()`: Devuelve el elemento situado en el inicio.
`isEmptyQueue()`: Verifica si la cola está vacía.
`isFullQueue()`: Verifica si la cola está llena (en caso de implementación estática).
`lengthQueue`: Devuelve el número de elementos. | `initList()`: Inicia la lista vacía.
`insert(elemento)`: Inserta un elemento en la lista en una posición específica o al final.
`delete(elemento)`: Elimina un elemento de la lista.
`get(posición)`: Busca un elemento específico en una posición.
`isEnd(posición)`: Devuelve verdadero si la posición dada es posterior a la última.
`isEmptyList`: Verifica si la lista está vacía.
`isFullList`: Verifica si la lista está llena.
`lengthList`: Devuelve el número de elementos. | | **Implementación Común en C** | Puede ser implementada usando un arreglo estático con un índice que indica el tope, o con una lista enlazada donde cada nodo contiene el dato y un puntero al siguiente nodo. | Similar a la pila, puede usar un arreglo estático o una lista enlazada. En la implementación de arreglo, se usa un índice para controlar el frente y el final de la cola. | Implementada típicamente usando una lista enlazada donde cada nodo contiene el dato y un puntero al siguiente nodo, permitiendo inserciones y eliminaciones eficientes en cualquier posición. | | **Eficiencia** | - Operaciones de `push()` y `pop()` son O(1) en una implementación con arreglo o lista enlazada.
- Espacio requerido puede ser menos flexible que una lista enlazada si se usa un arreglo estático. | - Operaciones de `enqueue()` y `dequeue()` son O(1) en una implementación con arreglo o lista enlazada.
- Requiere más espacio que una pila debido al almacenamiento de referencias de frente y final. | - Inserciones y eliminaciones pueden ser O(1) o O(n), dependiendo de si se inserta o elimina al principio o final de la lista.
- Búsqueda es O(n) en una lista simple enlazada sin optimizaciones adicionales. | | **Uso Típico** | - Manejo de historial de llamadas (undo/redo).
- Evaluación de expresiones matemáticas (notación polaca inversa). | - Gestión de tareas en una cola (por ejemplo, impresión en una impresora).
- Algoritmos de búsqueda en grafos (BFS utiliza una cola para gestionar los nodos a visitar). | - Almacenamiento de datos que requieren acceso y modificación frecuente, pero no necesariamente en orden específico.
- Implementación de estructuras de datos más complejas como listas enlazadas dobles o listas circulares. | Explicación de términos comunes: - **Arreglo estático**: Un arreglo con tamaño fijo determinado en tiempo de compilación. - **Lista enlazada**: Una estructura de datos dinámica donde cada elemento (nodo) contiene el dato y un puntero al siguiente nodo. - **Acceso Aleatorio**: Capacidad de acceder a cualquier elemento de la estructura de datos en tiempo constante (O(1)). #### 18.2.1. El TAD Pila (tStack) - Una pila es una secuencia lineal de elementos. - Los elementos se insertan únicamente por un extremo de la secuencia. Por eso se dice que es una estructura LIFO (Last In, First Out). - La manipulación y acceso a los elementos de la pila se permite solo en un extremo de la secuencia. Se puede pensar en una pila de libros dentro de una caja. ![](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Lifo_stack.svg/350px-Lifo_stack.svg.png) ##### 18.2.1.1. Lista de operaciones sobre tStack | Operación | Descripción | | -------------- | ------------------------------------------------------------ | | `initStack` | Inicializa la pila. | | `push` | Empila un elemento en la pila. | | `pop` | Desempila un elemento de la pila. | | `top` | Devuelve copia del valor del elemento en la cima de la pila. | | `isEmptyStack` | Consulta si la pila está vacía (true). | | `isFullStack` | Consulta si la pila está llena (true). | | `heightStack` | Consulta el número de elementos de la pila. | ##### 18.2.1.2. Implementación Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la pila en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem. Implementación del tipo: ```alg type tStack = record A: vector[MAX] of elem; {elem represents the type of the elements in th tStack} nelem: integer; end record end type typedef struct { elem A[MAX]; int nelem; } tStack; ``` Implementación de las operaciones del tipo: Tanto la operación de apilar `push` como la de desapilar `pop`, la manipulación de elementos se realiza por el extremo final de la secuencia que es el tope de la pila. ```alg action initStack(out s: tStack) s.nelem:= 0; end action action push(inout s: tStack, in e: elem) if s.nelem = MAX then {error full tStack} else s.nelem:= s.nelem + 1; s.A[s.nelem]:= e; end if end action action pop(inout s: tStack) if s.nelem = 0 then {error empty tStack} else s.nelem:= s.nelem - 1; end if end action action top(in s: tStack, out e: elem) if s.nelem = 0 then {error empty tStack} else e:= s.A[s.nelem]; end if end action function isEmptyStack(s: tStack): boolean return s.nelem = 0; end function function isFullStack(s: tStack): boolean return s.nelem = MAX; end function function heightStack(s: tStack): integer return s.nelem; end function ``` ```c #include #include void initStack(tStack *s) { s->nelem = 0; } void push(tStack *s, elem e) { if (s->nelem == MAX) { printf("\n Full Stack \n"); } else { s->A[s->nelem] = e; /* First position in C is 0 */ s->nelem++; } } void pop(tStack *s) { if (s->nelem == 0) { printf("\n Empty Stack \n"); } else { s->nelem--; } } void top(tStack s, elem *e) { if (s.nelem == 0) { printf("\n Empty Stack \n"); } else { *e = s.A[s.nelem-1]; } } bool isEmptyStack(tStack s) { return s.nelem == 0; } bool isFullStack(tStack s) { return s.nelem == MAX; } int heightStack(tStack s) { return (s.nelem); } ``` ##### 18.2.1.3. Ejemplo de uso Vamos a suponer un ejemplo de una pila de libros. Cada libro dispone de un código identificador y un nombre. Definimos las estructuras de datos necesarias para modelar el ejemplo: ```alg type tBook = record name: string; id: integer; end record tBox = record A: vector[MAX] of tBook; nelem: integer; end record end type ``` ```c #include #define MAX_NAME_LEN = 25 typedef struct { char[MAX_NAME_LEN] name; int id; } tBook; typedef struct { tBook A[MAX]; int nelem; } tBox; ``` Ok. Ahora vamos a implementar una acción que dada una pila de libros y el código de un libro, encuentre este libro en la pila. En caso de encontrarlo, lo retiramos de la pila; en caso contrario, dejamos la pila como estaba. ```alg action findBook(inout s: tBox, in id: integer) var b: tBook; aux: tBox; found: boolean; end var found:= false; initStack(aux); while not isEmptyStack(s) and not found do top(s, b); if b.id = id then push(aux, b); else found:= true; end if pop(s); end while while not isEmptyStack(aux) do top(aux, b); push(s, b); pop(aux); end while end action ``` ```c #include #include void findBook(tBox *s, int id) { tBook b; tBox aux; bool found; found = false; initStack(&aux); while (!isEmptyStack(*s) && !found) { top(*s, &b); if (b.id == id) { push(&aux, b); } else { found = true; } pop(s); } while (!isEmptyStack(aux)) { top(aux, &b); push(s, b); pop(&aux); } } ``` #### 18.2.2. El TAD Cola (tQueue) - Una cola es una secuencia lineal de elementos. - Los elementos se insertan por el final de la cola y se extraen por el principio. Por eso se dice que es una estructura FIFO (First In, First Out). - La manipulación y el acceso a los elementos de la cola solo se permite en los extremos. Un ejemplo de una cola es una cola de personas esperando para comprar una entrada en la taquilla de un teatro. ![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Data_Queue.svg/1280px-Data_Queue.svg.png) ##### 18.2.2.1. Lista de operaciones sobre tQueue | Operación | Descripción | | -------------- | -------------------------------------------------------------- | | `initQueue` | Inicializa la cola. | | `enqueue` | Encola un elemento en la cola. | | `dequeue` | Desencola un elemento de la cola. | | `head` | Devuelve copia del valor del elemento en la cabeza de la cola. | | `isEmptyQueue` | Consulta si la cola está vacía (true). | | `isFullQueue` | Consulta si la cola está llena (true). | | `lengthQueue` | Consulta el número de elementos de la cola. | ##### 18.2.2.2. Implementación Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la cola en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem. Implementación del tipo: ```alg type tQueue = record A: vector[MAX] of elem; nelem: integer; end record end type ``` ```c typedef struct { elem A[MAX]; int nelem; } tQueue; ``` Implementación de las operaciones: Los elementos se encolan por el final de la cola y se desencolan por el principio. Tened en cuenta que cada vez que se elemina un elemento se han de desplazar todos los elementos de la cola una posición a la izquierda. ```alg action initQueue(out q: tQueue) q.nelem:= 0; end action action enqueue(inout q: tQueue, in e: elem) if q.nelem = MAX then {error full tQueue} else q.nelem:= q.nelem + 1; q.A[q.nelem]:= e; end if end action action dequeue(inout q: tQueue) var i: integer; end var if q.nelem = 0 then {error empty tQueue} else for i:= 1 to q.nelem-1 do q.A[i]:= q.A[i+1]; end for q.nelem:= q.nelem - 1; end if end action action head(in q: tQueue, out e: elem) if q.nelem = 0 then {error empty tQueue} else e:= q.A[1]; end if end action function isEmptyQueue(q: tQueue): boolean return q.nelem = 0; end function function isFullQueue(q: tQueue): boolean return q.nelem = MAX; end function function lengthQueue(q: tQueue): integer return q.nelem; end function ``` ```c #include #include void initQueue(tQueue *q) { q->nelem = 0; } void enqueue(tQueue *q, elem e) { if (q->nelem == MAX) { printf("\n Full Queue \n"); } else { q->A[q->nelem] = e; /* first position in C is 0 */ q->nelem++; } } void dequeue(tQueue *q) { int i; if (q->nelem == 0) { printf("\n Empty Queue \n"); } else { for (i = 0; i < q->nelem-1; i++) { q->A[i] = q->A[i+1]; } q->nelem--; } } void head(tQueue q, elem *e) { if (q.nelem == 0) { printf("\n Empty Queue \n"); } else { *e = q.A[0]; } } bool isEmptyQueue(tQueue q) { return (q.nelem == 0); } bool isFullQueue(tQueue q) { return (q.nelem == MAX); } int lengthQueue(tQueue q) { return (q.nelem); } ``` ##### 18.2.2.3. Ejemplo de uso Siguiendo el ejemplo de la cola de una taquilla. Cada cliente de la cola tiene asociado el ordinal en la cola desde que abrió la taquilla y la cantidad de entradas que desea adquirir. Definimos las estructuras de datos necesarias para modelar el ejemplo: ```alg type tClient = record num: integer; quantity: integer; end record tTicketOffice = record A: vector[MAX] of tClient; nelem: integer; end record end type ``` ```c typedef struct { int num; int quantity; } tClient; typedef struct { tClient A[MAX]; int nelem; } tTicketOffice; ``` Ahora necesitamos implementar una acción que atienda el primer cliente de la cola en la taquilla. La acción recibe como parámetros la cola `q` de clientes y un número `available` que indica la disponibilidad de entradas. En caso de haber suficientes entradas disponibles, se actualiza la cantidad de entradas disponibles y se devuelve el valor verdadero en el parámetro sold. Una vez el cliente es atendido, se elimina de la cola. ```alg action serverCliente(inout q: tTicketOffice, in available: integer, out sold: boolean) var c: tClient; end var sold:= false; if not isEmptyQueue(q) then head(q, c); if c.quantity <= available then dequeue(q); available:= available - c.quantity; sold:= true; end if dequeue(q); end if end action ``` ```c #include #include void serverClient(tTicketOffice *q, int *available, bool *sold) { tClient c; *sold = false; if (!isEmptyQueue(*q)) { head(*q, &c); if (c.quantity <= *available) { *available -= c.quantity; *sold = true; } dequeue(q); } } ``` #### 18.2.3. El TAD Lista (tList) - Una lista es una secuencia lineal de elementos. - Los elementos se insertan, se eliminan y se consultan en cualquier posición de la lista. Para entender mejor el concepto de lista, podemos pensar en una lista de la compra del supermercado. ![](https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Singly-linked-list.svg/1280px-Singly-linked-list.svg.png) ##### 18.2.3.1. Lista de operaciones sobre tList | Operación | Descripción | | ------------- | ----------------------------------------------------------- | | `initList` | Inicializa la lista. | | `insert` | Inserta un elemento en la lista. | | `delete` | Elimina un elemento de la lista. | | `get` | Devuelve copia del valor del elemento en la posición `i`. | | `isEnd` | Consulta si la posición `i` es el final de la lista (true). | | `isEmptyList` | Consulta si la lista está vacía (true). | | `isFullList` | Consulta si la lista está llena (true). | | `lengthList` | Consulta el número de elementos de la lista. | ##### 18.2.3.2. Implementación Vamos a crear una implementación en un vector, donde tenemos un número máximo de elementos (MAX). Para conocer el espacio disponible de la lista en cada momento se necesita un atributo que indique el número total de elementos y que llamaremos nelem. Implementación del tipo: ```alg type tList = record A: vector[MAX] of elem; nelem: integer; end record end type ``` ```c typedef struct { elem A[MAX]; int nelem; } tList; ``` Implementación de las operaciones: Los elementos se insertan en cualquier posición de la lista y se eliminan de la misma forma. Cada vez que se inserta un elemento en una posición `i`, se han de desplazar todos los elementos de la lista una posición a la derecha. Cada vez que se elimina un elemento de una posición `i`, se han de desplazar todos los elementos de la lista una posición a la izquierda. ```alg action initList(out l: tList) l.nelem:= 0; end action action insert(inout l: tList, in e: elem, in index: integer) var i: integer; end var if l.nelem = MAX then {error full tList} else for i:= l.nelem to index step -1 do l.A[i+1]:= l.A[i]; end for l.nelem:= l.nelem + 1; l.A[index]:= e; end if end action action delete(inout l: tList, in index: integer) var i: integer; end var if l.nelem = 0 then {error empty tList} else for i:= index to l.nelem-1 do l.A[i]:= l.A[i+1]; end for l.nelem:= l.nelem - 1; end if end action action get(in l: tList, in index: integer, out e: elem) if l.nelem = 0 then {error empty tList} else e:= l.A[index]; end if end action function isEnd(l: tList, pos: integer): boolean return pos = l.nelem; end function function isEmptyList(l: tList): boolean return l.nelem = 0; end function function isFullList(l: tList): boolean return l.nelem = MAX; end function function lengthList(l: tList): integer return l.nelem; end function ``` ```c #include #include void initList(tList *l) { l->nelem = 0; } void insert(tList *l, elem e, int index) { int i; if (l->nelem == MAX) { printf("\n Full List \n"); } else { for (i = l->nelem; i >= index; i--) { l->A[i+1] = l->A[i]; } l->nelem++; l->A[index] = e; } } void delete(tList *l, int index) { int i; if (l->nelem == 0) { printf("\n Empty List \n"); } else { for (i = index; i < l->nelem-1; i++) { l->A[i] = l->A[i+1]; } l->nelem--; } } void get(tList l, int index, elem *e) { if (l.nelem == 0) { printf("\n Empty List \n"); } else { *e = l.A[index]; } } bool isEnd(tList l, int pos) { return pos == l.nelem; } bool isEmptyList(tList l) { return l.nelem == 0; } bool isFullList(tList l) { return l.nelem == MAX; } int lengthList(tList l) { return l.nelem; } ``` ##### 18.2.3.3. Ejemplo de uso Siguiendo el ejemplo de la lista de la compra del supermercado. Cada artículo tiene asociado un nombre, el tipo de artículo clasificado según sea de panadería, frescos, bebidas, congelados, belleza o desayuno, y la cantidad de este artículo. Definimos las estructuras de datos necesarias para modelar el ejemplo: ```alg type tArticleType = {BAKERY, FRESH, DRINKS, FROZEN, BEAUTY, BREAKFAST} tArticle = record type: tArticleType; quantity: real; end record tBuyList = record A: vector[MAX] of tArticle; nelem: integer; end record end type ``` ```c #include typedef enum {BAKERY, FRESH, DRINKS, FROZEN, BEAUTY, BREAKFAST} tArticleType; typedef struct { tArticleType type; float quantity; } tArticle; typedef struct { tArticle A[MAX]; int nelem; } tBuyList; ``` Necesitamos implementar una acción que elimine de la lista de la compra `l`, todos los artículos de un tipo dado (filter) que se recibe como parámetro. ```alg action filterArticleType(inout l: tBuyList, in filter: tArticleType) var a: tArticle; pos: integer; end var pos := 1; while not isEnd(l, pos) do get(l, pos, a); if a.type = filter then delete(l, pos); else pos := pos + 1; end if end while end action ``` ```c #include void filterArticleType(tBuyList *l, tArticleType filter) { elem a; int pos; pos = 0; while (!isEnd(*l, pos)) { get(*l, pos, &a); if (a.type == filter) { delete(l, pos); } else { pos = pos + 1; } } } ``` #### 18.2.4. Sintaxis para la declaración (Definición de un TAD de tipo pila, cola o lista) Para declarar un tipo pila dentro de un algoritmo, acción o función, se utiliza la siguiente sintaxis: ```alg nombreTipoPila = tStack(tipoElemento) ``` por ejemplo, `tBinaryStack = tStack(tBit)` permite declarar el tipo tBinaryStack como un tipo de pila de elementos de tipo tBit. A partir de aquí, se le pueden aplicar todas las operaciones que se han definido para las pilas. Como véis, en lenguaje algorítmico, la declaración queda como un tipo abstracto (su implementación es "oculta") pero en cambio, en lenguaje C, sí que se "ve" su implementación interna: ```c typedef struct{ tBit A[MAXBITS]; int nelem; } tBinaryStack; ``` A partir de aquí, solo se debe utilizar con las funciones y acciones predefinidas para este tipo. De este modo, realmente lo estamos utilizando como un tipo abstracto pila. De forma similar se puede declarar una cola o una lista: ```alg nombreTipoCola = tQueue(tipoElemento) nombreTipoLista = tList(tipolemento) ``` ## 19. Navegación de TAD ### 19.1. Ejemplos sobre el TAD pila #### 19.1.1 Ejemplo 1 Se pide el diseño de una acción que muestre la representación binaria de un número recibido como parámetro. Se debe usar una pila que almacene los resultados de las divisiones parciales por la base (que es 2). Por ejemplo, si la entrada es 77, la salida debe ser 1001101. ```alg type tBinaryStack = tStack(tBit) end type action dev2bin(in n: integer) var b:tBinaryStack; res, bit: integer; end var initStack(b); res := n; while res ≠ 0 do bit := res mod 2; res := res / 2; push(b, bit); end while while not isEmptyStack(b) do top(b, bit); write(bit); pop(b); end while end action ``` ```c #include typedef struct { tBit A[MAXBITS]; int nelem; } tBinaryStack; void dev2bin(int n) { tBinaryStack b; int res, bit; initStack(&b); res = n; while (res != 0) { bit = res % 2; res = res / 2; push(&b, bit); } printf("\n"); while (!isEmptyStack(b)) { top(b, &bit); printf("%d", bit); pop(&b); } printf("\n"); } ``` #### 19.1.2 Ejemplo 2 Se modela el historial de accesos a un conjunto de páginas web. La última página accedida está en la cima de la pila y si nos movemos hacia atrás en el historial iremos accediendo en orden desde el acceso más reciente al más antiguo. En el historial cada página tiene la siguiente info: - Identificador único de tipo entero. - Una dirección de memoria donde se encuentra el código de la página - La fecha de último acceso en formato aaaammdd. Se pide una acción `clearRecentHistory` que elimina todas las páginas web del historial que tienen último acceso posterior a una fecha dada. ```alg type tWebPage = record id: integer; address: integer; lastAccess: integer; end record tHistory = tStack(tWebPage); end type action clearRecentHistory(inout s: tHistory, in date: integer) var end: boolean; p: tWebPage; end var end:= false; while not isEmptyStack(s) and not end do top(s, p); if p.lastAccess > date then pop(s); else end:= true; end if end while end action ``` ```c #include #include typedef struct { int id; int address; int lastAccess; } tWebPage; typedef struct { tWebPage A[MAX]; int nelem; } tHistory; void clearRecentHistory(tHistory *s, int date) { bool end; tWebPage p; end = false; while (!isEmptyStack(*s) && !end) { top(*s, &p); if (p.lastAccess > date) { pop(s); } else { end = true; } } } ``` #### 19.1.3 Ejemplo 3 Continuando con el ejercicio anterior del historial de accesos de páginas web, ahora vamos a diseñar una acción `pushupWebPage` que recarga el acceso a una página dada. Esto significa que sube su posición en la pila hasta la cima, para pasar a ser la de acceso más reciente. ```alg action pushupWebPage(inout s: tHistory, in id: integer); var aux: tHistory; found: boolean; tmp, p: tWebPage; end var found:= false; initStack(aux); while not isEmptyStack(s) and not found do top(s, p); if p.id = id then pop(s); found:= true; else pop(s); push(aux, p); end if end while while not isEmptyStack(aux) do top(aux, tmp); push(s, tmp); pop(aux); end while if found = true then push(s, p); end if end action ``` ```c #include #include void pushupWebPage(tHistory *s, int id) { tStack aux; bool found; tWebPage tmp, p; found = false; initStack(&aux); while (!isEmptyStack(*s) && !found) { top(*s, &p); if (p.id == id) { pop(s); found = true; } else { pop(s); push(&aux, p); } } while (!isEmptyStack(aux)) { top(aux, &tmp); push(s, tmp); pop(&aux); } if (found) { push(s, p); } } ``` ### 19.2. Ejemplos sobre el TAD cola #### 19.2.1 Ejemplo 1 Se modela la cola de trabajos de una impresora. Los trabajos nuevos se insertan al final de la secuencia de trabajos y el primer trabajo de la secuencia es el primero en enviarse a imprimir. Cada trabajo tiene la siguiente información: - Un identificador único de tipo entero. - La dirección donde se encuentra almacenado el trabajo a imprimir. - el tamaño en bytes. - El tipo de impresión (a doble o a simple cara) Se debe diseñar una función `countBig` que devuelva el número de trabajos de la cola de impresión cuyo tamaño es superior a un límite dado en bytes. ```alg type tWork = record id: integer; address: integer; size: integer; doubleSide: boolean; end record tPrintQueue = tQueue(tWork); end type function countBig(q: tPrintQueue, limit: integer): integer var w: tWork; ini, count: integer; end: boolean; end var count := 0; if not is EmptyQueue(q) then head(q, w); ini := w.id; if w.size > limit then count := count + 1; end if dequeue(q); enqueue(q, w); end:= false; while not end do head(q, w); if w.id = ini then end:= true; else if w.size > limit then count := count + 1; end if dequeue(q); enqueue(q, w); end if end while end if return count; end function ``` ```c #include #include // Define la estructura de un trabajo de impresión typedef struct { int id; // Identificador del trabajo int address; // Dirección del trabajo int size; // Tamaño del trabajo bool doubleSide; // Indica si el trabajo es a doble cara } tWork; // Define la estructura de una cola de impresión typedef struct { tWork A[MAX]; // Array de trabajos int nelem; // Número de elementos en la cola } tPrintQueue; typedef tWork elem; typedef tPrintQueue tQueue; // Función para contar cuántos trabajos en la cola son más grandes que un cierto límite int countBig(tQueue q, int limit){ elem w; // Elemento actual int ini; // Identificador del primer elemento int count; // Contador de trabajos grandes bool end; // Indicador de fin de la cola count=0; // Si la cola no está vacía if(!isEmptyQueue(q)){ head(q, &w); // Obtiene el primer elemento ini=w.id; // Guarda el identificador del primer elemento // Si el tamaño del trabajo es mayor que el límite if(w.size>limit){ count++; // Incrementa el contador } dequeue(&q); // Elimina el primer elemento de la cola enqueue(&q, w); // Añade el primer elemento al final de la cola end=false; // Mientras no se haya vuelto al primer elemento while(!end){ head(q, &w); // Obtiene el primer elemento // Si se ha vuelto al primer elemento if(w.id==ini){ end=true; // Indica el fin de la cola }else{ // Si el tamaño del trabajo es mayor que el límite if(w.size>limit){ count++; // Incrementa el contador } dequeue(&q); // Elimina el primer elemento de la cola enqueue(&q, w); // Añade el primer elemento al final de la cola } } } return count; // Devuelve el contador } ``` #### 19.2.2 Ejemplo 2 Siguiendo con el ejercicio anterior. Se debe diseñar una acción `concatQueues` que, dadas dos colas de trabajos, concatena la segunda cola con la primer y vacía la segunda cola de trabajos. Si la primera cola de trabajos está llena, los trabajos que no se hayan concatenado se quedarán en la segunda cola y el parámetro de salida `completed` será falso. Si se puede realizar la concatenación, el parámetro de salida `completed` será verdadero. ```alg action concatQueues(inout q1, q2: tPrintQueue, out completed: boolean) var w: tWork; end var while not isEmptyQueue(q2) and not isFullQueue(q1) do head(q2, w); enqueue(q1, w); dequeue(q2); end while completed := isEmptyQueue(q2); end action ``` ```c #include #include void concatQueues(tQueue *q1, tQueue *q2, bool *completed){ tWork w; // Elemento actual while (!isEmptyQueue(*q2) && !isFullQueue(*q1)){ head(*q2, &w); // Obtiene el primer elemento enqueue(q1, w); // Añade el primer elemento a la cola 1 dequeue(q2); // Elimina el primer elemento de la cola 2 } *completed = isEmptyQueue(*q2); // Indica si se han añadido todos los elementos } ``` #### 19.2.3 Ejemplo 3 Siguiendo con el ejercicio anterior. Se debe diseñar una acción `serveDoubleSide`, que envía a imprimir todos los trabajos cuyo tipo de impresión es doble cara. Se devuelve en el parámetro qsingle, una cola creada con todos los trabajos no enviados a imprimir, es decir, los que son de tipo simple cara. Para enviar un trabajo a imprimir se dispone de la acción `printWork`, que recibe el parámetro el trabajo a imprimir con todos sus datos (identificador, dirección, tamaño y tipo de impresión). ```alg action serveDoubleSide(inout q: tPrintQeueu, inout qsingle: tPrintQueue) var w: tWork; end var initQueue(qsingle); while not isEmptyQueue(q) do head(q, w); if w.doubleSide then printWork(w); else enqueue(qsingle, w); end if dequeue(q); end while end action ``` ```c #include #include void serveDoubleSide (tQueue *q, tQueue *qSingle){ elem w; initQueue(qSingle); while (!isEmptyQueue(*q)){ head(*q, &w); if (w.doubleSide){ printWork(w); } else { enqueue(qSingle, w); } dequeue(q); } } ``` ### 19.3. Ejemplos sobre el TAD lista #### 19.3.1 Ejemplo 1 Se modela la lista de asientos disponibles de un vuelo concreto mediante una lista de asientos. Cada asiento tiene la siguiente información: - Número entero que identifica - Tipo de asiento: - Regular - Puerta de emergencia - No se asignan a menores de 12 años. De cada pasajero se conoce: - Identificador de tipo entero - Número de maletas que ha facturado - Edad Se debe diseñar una acción `seatAssign` que, dada una lista de asientos disponibles y la información de un pasajero, devuelve el asiento que se le ha asignado y el parámetro `found` con valor verdadero. Si no ha sido posible asignarle un asiento, el parámetro `found` será falso. ```alg type tPerson = record id: integer; bags: integer; age: integer; end record tSeatType = {REGULAR, EMERGENCY_DOOR} tSeat = record num: integer; type: tSeatType; end record tListSeat = tList(tSeat); end type action seatAssing(inout l: tListSeat, in p: tPerson, inout s: tSeat, inout found: boolean) var pos: integer; found: boolean; end var pos := 1; found := false; while pos ≤ lengthList(l) and not found do get(l, pos, s); if p.age > 12 or (p.age ≤ 12 and s.type ≠ EMERGENCY_DOOR) then delete(l, pos); found := true; else pos := pos + 1; end if end while end action ``` ```c #include #include typedef struct { int id; int bags; int age; } tPerson; typedef enum {REGULAR, EMERGENCY_DOOR} tSeatType; typedef struct { int num; tSeatType type; } tSeat; typedef struct { tSeat A[MAX]; int nelem; } tListSeat; typedef tSeat elem; typedef tListSeat tList; void seatAssign(tListSeats *l, tPerson p, tSeat *s, bool *found){ int pos; pos = 0 *found = false; while (pos < lengthList(*l) && !*found){ get(*l, pos, s); if (p.age > 12 || (p.age <= 12 && s.type != EMERGENCY_DOOR)){ delete(l, pos); *found = true; } else { pos++; } } } ``` #### 19.3.2 Ejemplo 2 Se modela un conjunto de facturas de una empresa mediante una lista. Las facturas no siguen un orden especial en el alta del conjunto y es posible dar de baja cualquier factura del conjunto. De cada factura se conoce: - Un identificador de tipo entero - Cantidad de dinero de tipo real - Fecha de la factura en formato aaaammdd Se debe diseñar una función `countSmallAmounts` que devuelva el número de facturas con cantidad de dinero menor a una cantidad dada. ```alg type tInvoice = record id: integer; amount: real; date: integer; end record tInvoiceList = tList(tInvoice); end type function countSmallAmounts(l: tInvoiceList, a: real): integer var inv: tInvoice; total: integer; pos: integer; end var pos := 1; total := 0; while pos ≤ lengthList(l) do get(l, pos, inv); if inv.amount < a then total := total + 1; end if pos := pos + 1; end while return total end function ``` ```c #include typedef struct { int id; float amount; int date; /* aaaammdd */ } tInvoice; typedef struct { tInvoice A[MAX]; int nelem; } tInvoiceList; typedef tInvoice elem; typedef tInvoiceList tList; int countSmallAmounts(tList l, float a){ tInvoce inv; int total; int pos; pos = 0; total = 0; while (pos < lengthList(l)){ get(l, pos, &inv); if (inv.amount < a){ total++; } pos++; } return total; } ``` #### 19.3.3 Ejemplo 3 Siguiendo el ejercicio anterior, vamos a diseñar la acción `findInvoice` que, dado el identificador de una factura, devuelve el parámetro `found` verdadero si la factura está en el conjunto de facturas así como su posición en la lista. En caso contrario, la acción devuelve el parámetro found con valor falso. ```alg action findInvoice(in l: tInvoice, in d: integer, out found: boolean, out pos:integer) var inv: tInvoice; end var found := false; pos := 1; while pos ≤ lengthList(l) and not found do get(l, pos, inv); if inv.id = d then found := true; else pos := pos + 1; end if end while end action ``` ```c #include #include void findInvoice(tInvoices l, int id, bool *found, int *pos){ tInvoice inv; *found = false; *pos = 0; while (*pos < lengthList(l) && ! (*found)){ get(l, *pos, &inv); if (inv.id == id){ *found = true; } else { *pos++; } } } ``` [Volver arriba](#pec-6) [Volver a la PR4](../PR4/README.md) | [Seguir a la PEC 7](../PEC7/README.md)