Tema 1: Arrays

Anuncio
Programación. Tema 1: Arrays
(16/Mayo/2004)
Apuntes elaborados por: Eduardo Quevedo,Raquel López
Revisado por: Javier Miranda el 25 de Mayo de 2004
Tema 1: Arrays
En este primer tema nos familiarizaremos con la programación mediante
objetos con Ada. Para esto, a diferencia de la asignatura vista en el primer
cuatrimestre, utilizaremos los paquetes Ada.
En este tema utilizaremos arrays como estructura de datos básica para
almacenar información en memoria del ordenador. Como ejemplo escribiremos
un programa que gestiona una agenda, y comenzaremos considerando el caso
más sencillo: presuponemos que no es necesario almacenar la información de
forma ordenada. Tras ver el comportamiento que debe tener nuestra estructura
de datos diseñaremos una interfaz que, en sucesivas revisiones, mejoraremos
hasta obtener la funcionalidad deseada. Finalmente veremos una segunda
versión de la agenda en que la información se almacena de forma ordenada,
con lo que podemos utilizar algoritmos de busqueda que mejoran la eficiencia
de nuestra estructura.
Array no ordenado
En el CD que tienes con material complementario de esta asignatura
encontrarás un applet que muestra visualmente el comportamiento del array no
ordenado. Conviene recordar que, por simplicidad, este applet solamente
trabaja con números enteros, y que para utilizar un array como soporte de
almacenamiento no sólo debemos fijar su tamaño máximo (para declarar el
array) sino que también necesitaremos una variable entera que nos indique
cuantos elementos hemos guardado en el array.
Applet:
Este applet nos permite insertar un nuevo elemento, buscar un elemento
o borrar un elemento. Hay que tener en cuenta que cuando en un array se
borra un elemento hay que recolocar todo su contenido, para así cerrar el
espacio libre que se ha dejado al eliminar el número. Por ejemplo si en el
siguiente array se borra el 45, quedará un hueco, eliminándose subiendo el 32
y el 23
6
33
45
32
23
6
33
32
23
6
33
32
23
1
Programación. Tema 1: Arrays
(16/Mayo/2004)
Interfaz:
La programación mediante objetos utilizando el lenguaje de
programación Ada se realiza mediante paquetes. Cada paquete consta de dos
ficheros: la interfaz (fichero con extensión “ads”) y el cuerpo del paquete
(fichero con extensión “adb”). En nuestro caso, las operaciones fundamentales
que debemos poner en la interfaz son:
- Insertar (Valor)
- Existe (Valor)
- Borrar (Valor)
- Mostrar_Contenido
La operación Mostrar_Contenido sólo se utilizará en la práctica como
ayuda para comprobar el paquete, los tres primeros son fundamentales en
cualquier base de datos, ya sea en arrays, árboles, pilas, listas, colas, etc.
V 1.0: Interfaz de Array no ordenado
Para hacer la interfaz en Ada utilizaremos un archivo con la extensión .ads.
package Agenda is
procedure Insertar (Valor : in Integer);
function Existe (Valor : in Integer) return Boolean;
procedure Borrar (Valor : in Integer);
procedure Mostrar_Contenido;
end Agenda;
La implementación (el cuerpo) de este paquete sería así:
package body Agenda is
Contenido
: array (1 .. 100) of Integer;
Num_Elementos : Natural := 0;
procedure Insertar (Valor: in Integer)
begin
Num_Elementos := Num_Elementos + 1;
Contenido (Num_Elementos) := Valor;
end Insertar;
-- Resto de operaciones del paquete
...
end Agenda;
Como vemos, en el cuerpo estamos declarando un array de 100 enteros
(Contenido) y una variable (Num_Elementos) que utilizaremos para recordar
cuantos elementos hemos guardado en el array. Estas dos variables son
globales al paquete ya que están declaradas fuera de los procedimientos. Esto
significa que son comunes a todos los procedimientos (Insertar, borrar, etc.) y
2
Programación. Tema 1: Arrays
(16/Mayo/2004)
guardan la información hasta que se termine el programa (Si las hubiesemos
declarado dentro de un procedimiento o función, desde que se llega al “end” del
procedimiento las variables desaparecerian y no servirian para guardar
información).
Para usar el paquete desde un programa principal hacemos lo siguiente:
with Agenda;
procedure Ejemplo_Uso_1 is
begin
Agenda.Insertar (13);
if Agenda.Existe (10) then
Agenda.Borrar (10);
end if;
Agenda.Mostrar_Contenido;
end Ejemplo_Uso_1;
Los problemas que plantea esta versión son que sólo se puede trabajar con un
array y que hay una limitación de elementos (en cuanto al tamaño de la tabla).
Si no queremos escribir continuamente el prefijo del paquete (Agenda) Ada nos
da la posibilidad de utilizar la sentencia “use” indicar que todas las declaraciones del paquete son immediatamente visibles. Esto significa que podemos
utilizarlas “como si” estuviesen en nuestro programa. Por ejemplo, el siguiente
programa es equivalente al anterior:
with Agenda; use Agenda;
procedure Ejemplo_Uso_2 is
begin
Insertar (13);
if Existe (10) then
Borrar (10);
end if;
Mostrar_Contenido;
end Ejemplo_Uso_2;
El principal inconveniente de esta implementación es que, como solamente
existe una variable Contenido (la que esta declarada en el cuerpo del paquete),
sólo se nos permite tener una agenda.
Solución al problema de tener un único array
Para que un paquete pueda gestionar multiples agendas debemos permitir al
cliente del paquete (a quien hace el “with” de este paquete) que pueda declarar
tantas agendas como quiera. La solución a este problema es declarar en la
interfaz (que contiene todo lo que puede utilizar el cliente del paquete) un
nuevo tipo de dato que tenga toda la información de la tabla. De esta forma,
todo cliente (o usuario) del paquete puede crear tantas tablas como necesite.
Como la información minima que necesitamos para gestionar cada agenda son
dos variables (el numero de elementos y el contenido) tenemos que utilizar un
3
Programación. Tema 1: Arrays
(16/Mayo/2004)
registro (un record) para mantenerlas agrupadas. Por tanto, hacemos la
siguiente modificación en la interfaz:
package Agenda is
type T_Contenido is array (1 .. 100) of Integer;
-- Tenemos que declarar el tipo del array para poder utilizarlo
-- a continuacion en la declaracion de campo Contenido de nuestro
-- registro (record)
type T_Objeto is record
Contenido
: T_Contenido;
Num_Elementos : Natural := 0;
end record;
procedire Insertar ( . . . );
end Agenda;
V 1.1: Interfaz de Array no ordenado
Para que las declaraciones de tipos de datos que hemos puesto en la interfaz
sean realmente utiles debemos modificar también la interfaz de todos los
procedimientos y añadir un parámetro de tipo T_Objeto como primer
parámetro. La interfaz queda entonces de la siguiente forma:
package Agenda is
type T_Contenido is array (1 .. 100) of Integer;
type T_Objeto is record
Contenido
: T_Contenido;
Num_Elementos : Natural := 0;
end record;
procedure Insertar (Objeto: in out T_Objeto;
Valor : in
Integer);
function Existe (Objeto : in T_Objeto;
Valor : in Integer) return Boolean;
procedure Borrar (Objeto : in out T_Objeto;
Valor : in
Integer);
procedure Mostrar_Contenido
(Objeto : in T_Objeto);
end Agenda;
Veamos a continuación un ejemplo de uso de esta nueva interfaz.
4
Programación. Tema 1: Arrays
(16/Mayo/2004)
with Agenda;
procedure Ejemplo_Uso is
Mi_Agenda_1 : Agenda.T_Objeto;
Mi_Agenda_2 : Agenda.T_Objeto;
begin
Agenda.Insertar (Agenda_1, 13);
if Agenda.Existe (Agenda_2, 10) then
Agenda.Borrar (Agenda_2, 10);
end if;
.Agenda.Mostrar_Contenido (Agenda_1);
end Ejemplo_Uso;
Aunque esta solución nos permite tener varias agendas, no nos permite
indicar el tamaño de cada agenda (todas son de tamaño 100 porque la
declaracion viene fijada en la interfaz del paquete para poder declarar el
array).
Solución al problema del tamaño de la Tabla
Para que el usuario del paquete pueda especificar el tamaño de la tabla
solamente tenemos que pasar un parámetro al record. Este parámetro se utiliza
para declarar la variable contenido con el tamaño exacto que se necesite para
cada variable.
type T_Contenido is array (Positive range <>) of Integer;
type T_Objeto (Maximo: Positive) is
record
Contenido
: T_Contenido (1 .. Maximo);
Num_Elementos : Natural := 0;
end record
V 1.2: Interfaz de Array no ordenado
Incorporando esta nueva modificación en nuestra interfaz obtenemos:
package Agenda is
type T_Contenido is array (Positive range <>) of Integer;
type T_Objeto (Maximo : Positive) is record
Contenido
: T_Contenido (1 .. Maximo);
Num_Elementos : Natural := 0;
end record;
procedure Insertar (Objeto: in out T_Objeto;
Valor : in
Integer);
function Existe (Objeto : in T_Objeto;
Valor : in Integer) return Boolean;
procedure Borrar (Objeto : in out T_Objeto;
Valor : in
Integer);
procedure Mostrar_Contenido (Objeto : in T_Objeto);
end Agenda;
5
Programación. Tema 1: Arrays
(16/Mayo/2004)
Fallo de esta interfaz
El principal inconveniente de la interfaz que acabamos de diseñar es que
el usuario puede (por error) modificar directamente los elementos del array sin
utilizar los procedimientos del interfaz. Esto es posible porque la declaración
del record esta completamente visible en la interfaz del paquete.
Solución al posible mal uso de la interfaz
Para resolver este problema Ada nos permite descomponer la interfaz en
dos partes: la parte pública y la parte privada. En la parte pública ponemos todo
lo que puede utilizar el cliente del paquete, y en la parte privada ponemos todas
las declaraciones que hagan falta para que se pueda compilar la interfaz.
V 1.3: Interfaz de Array no ordenado
Si incorporamos declaraciones privadas en nuestra interfaz nos queda asi:
package Agenda is
-- Parte publica de la interfaz del paquete
type T_Objeto (Maximo : Positive) is private;
procedure Insertar (Objeto: in out T_Objeto;
Valor : in
Integer);
function Existe (Objeto : in T_Objeto;
Valor : in Integer) return Boolean;
procedure Borrar (Objeto : in out T_Objeto;
Valor : in
Integer);
procedure Mostrar_Contenido (Objeto : in T_Objeto);
private
-- Parte privada de la interfaz del paquete
type T_Contenido is array (Positive range <>) of Integer;
type T_Objeto (Maximo : Positive) is record
Contenido
: T_Contenido (1 .. Maximo);
Num_Elementos : Natural := 0;
end record;
end Agenda;
El compilador se encarga de que los clientes de este paquete (los clientes son
los programas que hacen “with” de este paquete) solo puedan utilizar los
nombres de los procedimientos y tipos de datos que estan en la parte publica
del paquete, que son todas las declaraciones que hay entre la palabra is (en la
linea 1) y la palabra private. Los clientes de este paquete
6
Programación. Tema 1: Arrays
(16/Mayo/2004)
Inconvenientes:
El principal inconveniente de esta interfaz es que cuando la tabla está
llena y se intenta insertar un elemento, el programa falla (Ada eleva la
excepción Constraint_Error porque si el array esta lleno estariamos accediendo
a una posición que esta fuera del array). De figual forma, cuando el array esta
vacío y se intenta borrar un elemento que no existe el programa falla. Estos dos
fallos se corrigen añadiendo excepciones en la interfaz de paquete, para que
cuando ocurra una de las dos situaciones antes comentadas se notifique el
error elevando la excepción necesaria.
Es evidente que ni Existe, ni Mostrar_Contenido tendrán que elevar
excepción alguna; el primero porque únicamente busca un determinado
elemento y retorna un lógico y el segundo porque se limita a mostrar el
contenido de un array. Por todo lo dicho sólo habrá que declarar dos
excepciones:
Lleno
: exception;
No_encontrado : exception;
V 2: Interfaz de Array no ordenado
Para incorporar las excepciones basta con añadir su declaración en la interfaz
del paquete. Además, es importante documentar (mediante comentarios Ada)
que excepciones eleva cada procedimiento, para que los clientes del paquete
puedan tratar estas excepciones correctamente.
package Agenda is
Lleno
: exception;
No_encontrado : exception;
type T_Objeto (Maximo : Positive) is private;
procedure Insertar (Objeto: in out T_Objeto;
Valor : in
Integer);
-- Excepcion: Lleno, cuando no hay mas espacio en el array
function Existe
(Objeto : in T_Objeto;
Valor : in Integer) return Boolean;
procedure Borrar (Objeto : in out T_Objeto;
Valor : in
Integer);
-- Excepcion: No encontrado cuando la tabla está vacia
procedure Mostrar_Contenido (Objeto : in T_Objeto);
private
type T_Contenido is array (positive range <>) of Integer;
type T_Objeto (Maximo : Positive) is record
Contenido
: T_Contenido (1 .. Maximo);
Num_Elementos : Natural := 0;
end record;
end Agenda;
7
Programación. Tema 1: Arrays
(16/Mayo/2004)
Inconveniente de la interfaz:
El único inconveniente que le queda a esta interfaz es que sólamente
trabaja con números enteros. En la primera práctica de la asignatura deberás
escribir un paquete (la interfaz y el cuerpo) que trabaje con cualquier
información. Esto se hace creando un registro T_Ficha que guarde toda la
información que almacene nuestra agenda.
Para que el programa sea realmente util deberemos utilizar uno de los
campos de este registro como clave para realizar la inserción, busqueda o
borrado de elementos.
V 3: Interfaz de array no ordenado
Si incorporamos todo lo anterior obtenemos la siguiente, y última,
versión de nuestra interfaz:
package Agenda is
type T_Objeto (Maximo : Positive) is private;
type T_Ficha is record
Clave:.......;
-- Aqui va el resto de campos de información de nuestra agenda
end record;
Lleno
: exception;
No_encontrado : exception;
procedure Insertar (Objeto: in T_Objeto;
Ficha : in T_Ficha);
-- Excepcion: Lleno, cuando no hay mas espacio en el array
function Existe
(Objeto : in T_Objeto;
Clave : in ...) return Boolean;
procedure Borrar (Objeto : in out T_Objeto;
Clave : in ...);
-- Excepcion: No encontrado cuando la tabla está vacia
procedure Buscar (Objeto : in out T_Objeto;
Clave : in ...;
Info
: out T_Ficha);
-- Excepcion: No encontrado
procedure Mostrar_Contenido (Objeto : in T_Objeto);
8
Programación. Tema 1: Arrays
(16/Mayo/2004)
private
type T_Contenido is array (positive range <>) of T_Ficha;
type T_Objeto (Maximo : Positive) is record
Contenido
: T_Contenido (1 .. Maximo);
Num_Elementos : Natural := 0;
end record;
end Agenda;
Con respecto al cuerpo de esta interfaz veamos el cuerpo del procedimiento
Insertar, en el que hay que elevar la excepción Lleno cuando la tabla esté llena
y queramos insertar un elemento. Si el array no esta lleno lo que hay que hacer
es añadir un elemento al número de elementos, y almacenar la ficha en el array
utilizando como indice del array el nuevo valor del numero de elementos. De
esta forma estamos realizando la inserción de los elementos siempre al final
del array.
-- INSERTAR -procedure Insertar (Objeto : in out T_Objeto;
Ficha : in
T_FichaI) is
begin
if Objeto.Num_Elementos = Objeto.Maximo then
raise Lleno;
end if;
Objeto.Num_Elementos := Objeto.Num_Elementos + 1;
Objeto.Contenido (Objeto.Num_Elementos) := Ficha;
end Insertar;
Verificación del programa: Test
Pero, ¿cómo podemos saber si el programa que estamos haciendo
funciona y lo hace bien?, la respuesta es sencilla, hacer un test que se
comporte como un cliente de este paquete y verifique todos los posibles usos
del paquete (tanto el uso correctos como el uso incorrectos). Como ejemplo, a
continuación se exponen dos test que verifican si las excepciones están
trabajando bien:
1. El primer test comprueba la función Existe. Para ello creamos una
agenda en la que no insertamos nada (por tanto su array está
completamente vacio) e intentamos buscar una ficha. Obviamente, si la
función nos dice que encuentra la ficha claramente el paquete está
funcionando mal y escribimos en pantalla la palabra “Fallo”; si no se
encuentra es que el programa se ha portado bien (no encuentra la ficha
porque aún no lo hemos insertado nada en la agenda) y el programa
escribe en pantalla:
Buscar en una tabla vacia ......................OK
9
Programación. Tema 1: Arrays
(16/Mayo/2004)
En caso de que la función Existe eleve cualquier excepción será también un
fallo porque en la interfaz del paquete no se ha indicado que esta función eleve
ninguna excepción: solamente debe devolver verdadero o falso.
procedure Test_1 is
Mi_Agenda : Agenda.T_Objeto (Maximo => 30);
-- Esta agenda almacena como maximo 30 fichas.
Mi_Ficha : Agenda.T_Ficha;
begin
Text_IO.Put ("Buscar en una tabla vacia ...................... ");
-- Aqui debo rellenar la ficha con la informacion que quiero guardar
-- en la agenda
...
if Agenda.Existe (Mi_Agenda, Mi_Ficha) then
Text_IO.Put_Line ("Fallo!");
else
Text_IO.Put_Line ("OK");
end if;
exception
when others =>
Text_IO.Put_Line ("Fallo !!");
end Test_1;
10
Programación. Tema 1: Arrays
(16/Mayo/2004)
2. El segundo test sirve para ver si el procedimiento Insertar está elevando
la excepción Lleno cuando se llena la tabla. Para este test creamos una
tabla en la que solo cabe un elemento, e insertamos dos fichas. Si al
insertar la segunda ficha no se eleva la excepción, claramente el
paquete no esta funcionando bien.
procedure Test_2 is
Mi_Agenda : Agenda.T_Objeto (Maximo => 1);
Mi_Ficha : Agenda.T_Ficha;
begin
Text_IO.Put ("Insertar un objeto en una tabla llena........... ");
-- Aqui debo rellenar la primera ficha con la informacion que quiero guardar
-- en la agenda
...
-- Añado la primera ficha a mi agenda
Agenda.Insertar (Mi_Agenda, Mi_Ficha);
begin
-- Aqui debo rellenar la segunda ficha con la informacion que quiero guardar
-- en la agenda
...
-- Añado la segunda ficha a mi agenda
Agenda.Insertar (Mi_Agenda, Mi_Ficha);
-- Si no se ha elevalo la excepcion es que el paquete ha fallado
Text_IO.Put_Line ("Fallo!");
exception
when Agenda.Lleno =>
Text_IO.Put_Line ("OK");
when others =>
Text_IO.Put_Line ("Fallo!!");
end;
exception
when others =>
Text_IO.Put_Line ("Fallo (en la primera insercion)");
end Test_2;
NOTA: Como puede verse en este ejemplo, para realizar este test se ha
utilizado un segundo bloque “begin-end”. De esta forma aseguramos que el
mensaje de fallo está asociado exactamente a la segunda llamada a Insertar
(ya que podria darse el caso de que falle la primera llamada, en vez de la
segunda, y si ponemos el manejador de excepciones en el procedimiento no
sabriamos cual de las dos llamadas es la que elevó la excepción.
11
Programación. Tema 1: Arrays
(16/Mayo/2004)
Array ordenado
La implementación anterior (insertando siempre la información al
final del array) nos fuerza a recorrer todos los elementos del array cada
vez que queremos realizar una búsqueda, ya que no hay forma de saber
en que posición del array está el elemento. Una alternativa consiste en
mantener ordenada la información que almacenamos en el array.
Para realizar esta segunda versión podemos reutilizar la interfaz (el
fichero con extensión “.ads”), los tests y el programa principal; solamente
necesitamos modificar el cuerpo del paquete (el fichero con extensión “.adb).
En concreto, debemos modificar el cuerpo de los procedimientos para realizar
siempre la búsqueda mediante una función que realice la búsqueda binaria y
que nos devuelva la posición donde encuentra el elemento; en caso de no
encontrarlo la posición devuelta es exactamente la posición donde deberemos
realizar la inserción.
if Tabla.Num_Elementos = 0 then
raise Tabla_Vacia;
else
-- Busqueda binaria
loop
Medio := Integer ( (Primero + Ultimo) / 2);
if Tabla (Medio) = Clave then
return Medio;
elsif Primero = Ultimo then
raise No_Encontrado;
elsif Clave > Tabla (Medio) then
Primero := Medio + 1;
else
Ultimo := Medio – 1;
end loop;
end if;
12
Programación. Tema 1: Arrays
(16/Mayo/2004)
Análisis:
Para analizar la eficiencia de los algoritmos lo primero que se nos
podría ocurrir es comparar los tiempos de ejecución de la versión mediante
array no ordenado y la versión mediante array ordenado. La principal
diferencias entre ambas es que la primera versión requiere realizar un bucle
para encontrar la posición adecuada, mientras que en el array ordenado la
busqueda solamente analiza unos pocos elementos para localizar la posición
definitiva donde se debe realizar la inserción, ya que permite utilizar la
búsqueda binaria. En el borrado ambas versiones tienen que hacer un bucle
para cerrar el hueco.
Intentemos llevar esta información a una tabla, para así facilitar la
comparación de ambas versiones. Supongamos que la constante K representa
la velocidad del ordenador:
Inserción
Búsqueda
Borrado
Array No Ordenado
T=K
T = K*N
T = K*N
Array Ordenado
T = K*N
T = K*log2(N)
T = K*N
En realidad, las tablas anteriores tienen información que no es en
absoluto representativa. Si nos fijamos, todos los elementos de la tabla tienen
la constante K, que lo único que hace es oscurecer la información realmente
util. Para evitarla, la comunidad internacional ha definido el concepto de Orden
de un algoritmo, en el que solamente se pone la información realmente
relevante. Por ejemplo, si el tiempo de ejecución de un algoritmo es constante,
decimos que es de orden 1, si al evaluar el tiempo de ejecución obtenemos un
polinomio cuyo exponente de mayor grado es 3, entonces decimos que nuestro
algoritmo es de orden N3: Por tanto, la tabla anterior quedaría asi:
Inserción
Búsqueda
Borrado
Array No Ordenado
O (1)
O (N)
O (N)
Array Ordenado
O (N)
O(log N)
O (N)
Una representación gráfica del orden sería la siguiente:
13
Programación. Tema 1: Arrays
(16/Mayo/2004)
Apéndice al tema 1: Curiosidades complementarias para la
realización de las prácticas en el laboratorio (o en casa).
Depurador:
Para comenzar a usar el depurador hay que tener en cuenta los
siguientes pasos:
1) Borrar del directorio todos los ficheros con extensión ali (*.ali). Los
ficheros ALI (Ada Library Information) son ficheros que genera el
compilador para establecer las dependencias entre los paquetes. Por
tanto, siempre que queramos obligar al compilador a que compile de
nuevo todos los paquetes simplemente tendremos que borrarle estos
ficheros (al compilar, como el compilador no encuentra estos ficheros, se
ve obligado a recompilar todos los paquetes).
2) Recompilar con la opción –g
$ gnatmake –gnatc – g mi_agenda.adb
3) Arrancar el depurador
$ gvd array_no_ordenado
4) Utilizar el depurador. Para el uso del depurador se deben de tener en
cuenta los siguientes comandos básicos:
- Run: Comienza a ejecutar el programa
- Step: Siguiente sentencia (si la linea contiene la llamada a un
procedimiento, fuerza el salto al comienzo del procedimiento,
para comenzar a ejecutarlo)
- Next: Siguiente linea (si la línea contiene la llamada a un
procedimiento, se ejecuta todo el procedimiento y el depurador
se detiene justo despues de completar la ejecución del
procedimiento).
Procedimientos de guardar y cargar:
No tiene sentido una agenda que no guarde el contenido tras haber
insertado una serie de elementos, para ello tienes que implementar dos
procedimientos, uno que guarde el contenido de la agenda en un fichero y otro
que lea el contenido del fichero y lo inserte en la agenda. Aunque puedes
utilizar ficheros secuenciales o ficheros de acceso directo, como en ambos
casos estas tratando con toda la información de la agenda, este es un claro
ejemplo en que es recomendable utilizar un fichero secuencial. Obviamente,
para añadir estos procedimientos tendras que modificar tanto la interfaz como
el cuerpo del paquete. De igual forma también se recomienda añadir todos los
procedimientos y funciones que quieras para completar tu práctica.
14
Descargar