Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Tipos de Datos Estructurados Departamento de Informática Universidad Nacional de San Luis, Argentina https://sites.google.com/site/disenioyparadigmas e-mail: proggero@unsl.edu.ar 29 de agosto de 2016 Diseño y Paradigmas de Lenguajes - Año 2016 Tipos de datos estructurados Roggero Estructura de datos (objeto de datos estructurado): contiene otros objetos de datos (componentes) que pueden ser elementales o estructurados. Algunos tipos de datos estructurados importantes: • Arreglos • Slices • Registros • Strings (cadenas de caracteres) • Conjuntos • Punteros Diseño y Paradigmas de Lenguajes - Año 2016 Especificación de tipos de datos estructurados Roggero La especificación en estos casos suele incluir los siguientes atributos: • El número de componentes: ¿estructura de tamaño fijo o variable?. • El tipo de cada componente: ¿estructura de datos homogénea o heterogénea?. • Los nombres usados para seleccionar las componentes: ¿subíndices, identificadores definidos por el programador o componentes particulares? Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Especificación de tipos de datos estructurados • El número máximo de componentes (en estructuras de datos de tamaño variable). • La organización de las componentes: ¿secuencia lineal de las componentes o formas multidimensionales?. Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Operaciones sobre estructuras de datos • La selección (o acceso) de componentes: ¿directa o secuencial?. • Operaciones con estructuras de datos completas: la mayoría de los lenguajes proveen un número limitado de este tipo de operaciones (sumar 2 arreglos, asignar un registro a otro, la unión de conjuntos). • La inserción/supresión de componentes: modifican el número de componentes. • La creación/destrucción de estructuras de datos. Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de tipos de datos estructurados Roggero La representación en memoria para una estructura de datos incluye: 1 Memoria para las componentes de la estructura. 2 Un descriptor (opcional) con algunos (o todos) los atributos de la estructura. Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Implementación de tipos de datos estructurados Representacion secuencial Descriptor Representacion encadenada Descriptor (Opcional) Componente Componente Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de operaciones sobre estructuras de datos Roggero En general deben ser simuladas por software. Es fundamental el rol de la selección (acceso) de componentes (directa o secuencial), que está influenciada por el tipo de representación: Con representación secuencial: • la selección directa involucra un cálculo dirección-base-más-desplazamiento (db+d). • el acceso a una secuencia de componentes involucra: seleccionar la primera componente de la serie en base al cálculo db+d 2 para avanzar a siguiente componente, sumar tamaño componente actual a su ubicación. 1 Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Con representación encadenada: • la selección directa involucra seguir la cadena hasta la componente deseada. • la selección de una secuencia de componentes involucra: 1 2 acceder a la primera componente como antes. seguir el puntero a la siguiente componente por cada selección subsiguiente. Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Administración de memoria y estructuras de datos Durante el tiempo de vida de un OD, se pueden crear y destruir distintos pasos de acceso a el. Cuando los tiempos de vida de los OD y de sus pasos de acceso no coinciden pueden surgir dos tipos de problemas: Basura y Referencias desactivadas. Diseño y Paradigmas de Lenguajes - Año 2016 Arreglos (unidimensionales o vectores) Roggero Estructuras de datos integradas por un número fijo de componentes del mismo tipo organizados como una serie lineal simple, cuyas componentes pueden ser accedidas mediante un subíndice, un entero (o valor enumerado) que indica su posición en la serie. Atributos de un vector: • El número de componentes. • El tipo de datos de las componentes. • Valores de subíndice válidos Diseño y Paradigmas de Lenguajes - Año 2016 Arreglos (unidimensionales o vectores) Roggero Ejemplos (en Pascal y C): var a: array [-5 .. 5] of real; float a[10]; Algunos lenguajes incluyen arreglos heterogéneos, es decir cuyas componentes no necesitan ser todas del mismo tipo, por ejemplo Perl, Python, JavaScript y Ruby Diseño y Paradigmas de Lenguajes - Año 2016 Algunas operaciones con vectores Roggero • Acceso (y modificación) de una componente mediante subindización. • Operaciones aritméticas con vectores completos (pocos lenguajes las proveen). • Creación y destrucción de vectores. • No se permite la inserción y supresión de componentes. Diseño y Paradigmas de Lenguajes - Año 2016 Inicialización de Arreglos Roggero Algunos lenguajes permiten la inicialización de arreglos en el punto de declaración: • C, C + +, Java, C#, ejemplo: int list [] = {4, 5, 7, 83} • Arreglos de strings in C y C + +: char *names [] = {“Bob”, “Jake”, “Joe”} Diseño y Paradigmas de Lenguajes - Año 2016 Representación en memoria de vectores Roggero Vector Descriptor Limite Inferior LS Limite Superior Entero E A[LI] A[LI+1] Componentes A[LS−1] A[LS] Tipo de datos LI Tipo de datos (componente) Tamano del componente α Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Acceso a la i-ésima componente de un vector dir (A[i]) = α + (i − LI) × E Pero esta fórmula la podemos escribir como: dir (A[i]) = (α − LI × E) + i × E Notar que una vez que se asigna espacio (α − LI × E) es una constante que se puede llamar K , entonces la fórmula de acceso se reduce a: dir (A[i]) = K + i × E Diseño y Paradigmas de Lenguajes - Año 2016 Acceso en base al origen virtual (OV) Roggero Consideremos ahora la dirección A[0] del vector: dir (A[0]) = (α − LI × E) + 0 × E dir (A[0]) = (α − LI × E) dir (A[0]) = K K representa la dirección de la componente A[0] aunque ésta no exista, debido a que el elemento 0 puede no ser parte del arreglo, esta dirección es llamada origen virtual (OV). OV = dir (A[0]) = α − LI × E Diseño y Paradigmas de Lenguajes - Año 2016 Acceso en base al origen virtual (OV) Roggero Entonces en base al OV podemos reescribir la fórmula de acceso a una componente como: dir (A[i]) = OV + i × E La verificación de que i está entre LI y LS debe preceder el uso de la fórmula, es por eso que estos valores deben estar presentes en el descriptor en tiempo de ejecución. Diseño y Paradigmas de Lenguajes - Año 2016 Ubicación del origen virtual Roggero La ubicación relativa del OV respecto a los elementos del arreglo, puede variar dependiendo de los subíndices utilizados. Ejemplo: supongamos α = 1000 y E = 2. Dibujar la posición del origen virtual respecto a los elementos de los siguientes arreglos de 5 enteros: A: array [3..7] of integer; A: array [-2..2] of integer; A: array [0..4] of integer; Diseño y Paradigmas de Lenguajes - Año 2016 Almacenamiento separado para descriptor y componentes Roggero A[LI] α A[LI+1] Vector OV Origen Virtual LI Limite Inferior LS Limite Superior Entero E A[LS−1] A[LS] Tipo de datos Tipo de datos (componente) Tamano del componente Diseño y Paradigmas de Lenguajes - Año 2016 Arreglos multidimensionales Roggero Los arreglos de una dimensión que hemos visto se denominan vectores. Sin embargo, los arreglos pueden tener más de una dimensión. Para el caso bi-dimensional se suele usar el término matriz con sus correspondientes filas y columnas. Ejemplo: 1 3 5 2 4 6 3 5 7 4 6 8 En general, es posible extender esta idea a arreglos n-dimensionales, con n > 2. Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de arreglos bi-dimensionales (matrices) Roggero Es directa si consideramos una matriz como un vector de vectores. Un arreglo tri-dimensional puede ser considerado un vector cuyas componentes son vectores de vectores, etc. Primera pregunta que surge al implementar una matriz: • ¿es un vector de filas? (representación por filas u orden por filas) • ¿es un vector de columnas? (representación por columnas u orden por columnas) Diseño y Paradigmas de Lenguajes - Año 2016 Representación por filas de una matriz Roggero var A: array [1 .. 3, 2 .. 5] of integer; Matriz LI1 (= 1) LS1 (= 3) 2 1 1 3 2 4 3 5 LI2 (= 2) LS2 (= 5) 4 2 3 4 5 6 3 5 6 7 8 Entero A[1,2] E 1 A[1,3] 2 A[1,4] 3 A[1,5] 4 A[2,2] 3 A[2,3] 4 A[2,4] 5 A[2,5] 6 A[3,2] 5 A[3,3] 6 A[3,4] 7 A[3,5] 8 Tipo de datos Limite Inferior 1 Limite Superior 1 Limite Inferior 2 Limite Superior 2 Tipo de datos (componente) Tamano del componente α Primera fila Segunda fila Tercera fila Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Acceso a la componente A[i, j] de una matriz A A partir de α debo ”saltar” (i − LI1) filas de tamaño S y (j − LI2) elementos de tamaño E. S = cantidad de componentes por fila × tamaño de componente = (LS2 − LI2 + 1) × E dir (A[i, j]) = α + (i − LI1) × S + (j − LI2) × E (4) Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Acceso a la componente A[i, j] de una matriz A Cuando LI1 = 0 y LI2 = 0 la fórmula de acceso se simplifica: dir (A[i, j]) = α + i × S + j × E (5) Este es el caso en los lenguajes que consideran siempre el 0, 0 como la componente inicial. Diseño y Paradigmas de Lenguajes - Año 2016 Acceso en base al origen virtual (OV) Roggero Con la misma idea utilizada con los vectores, el origen virtual en este caso sería: OV = dir (A[0, 0]) = α − (LI1 × S) − (LI2 × E) OV representa la dirección de la componente A[0, 0] si ella existiera en la matriz. En base al OV podemos redefinir la fórmula de acceso para una componete cualquiera, a partir de la fórmula (4): dir (A[i, j]) = OV + i × S + j × E Diseño y Paradigmas de Lenguajes - Año 2016 Representación por columnas de una matriz Roggero var A: array [1 .. 3, 2 .. 5] of integer; Matriz LI1 (= 1) LS1 (= 3) 2 1 1 3 2 4 3 5 LI2 (= 2) LS2 (= 5) 4 2 3 4 5 6 3 5 6 7 8 Entero A[1,2] E 1 A[2,2] 3 A[3,2] 5 A[1,3] 2 A[2,3] 4 A[3,3] 6 A[1,4] 3 A[2,4] 5 A[3,4] 7 A[1,5] 4 A[2,5] 6 A[3,5] 8 Tipo de datos Limite Inferior 1 Limite Superior 1 Limite Inferior 2 Limite Superior 2 Tipo de datos (componente) Tamano del componente α Primera columna Segunda columna Tercera columna Cuarta columna Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Slices (rebanada) Una rebanada es alguna subestructura de un arreglo, sólo son útiles en los lenguajes que tienen operaciones con arreglos. Diseño y Paradigmas de Lenguajes - Año 2016 Ejemplo Slices Roggero Sean las siguientes declaraciones en Python: vector = [2, 4, 6, 8, 10, 12, 14, 16] mat = [[1, 2, 3],[4, 5, 6],[7, 8, 9]] La sintaxis de los slices en Python es un par de expresiones numéricas separadas por :, es decir, vector[3:6]. Una fila de una matriz se especifica dando solo un índice, por ejemplo, mat[1], corresponde a la segunda fila de la matriz. También es posible especificar una parte de una fila, mat[0][0:2], en este caso se refiere al primer y segundo elemento de la primera fila, o sea [1, 2]. Diseño y Paradigmas de Lenguajes - Año 2016 Registros Roggero Especificación y Sintaxis Son estructuras de datos lineales y de longitud fija. Difieren de los arreglos en dos aspectos: • Las componentes de los registros pueden ser heterogéneas. • Las componentes se designan con nombres simbólicos (identificadores). Diseño y Paradigmas de Lenguajes - Año 2016 Atributos de Registros Roggero • El número de componentes. • El tipo de datos de cada componente. • El selector que se usa para nombrar cada componente. Diseño y Paradigmas de Lenguajes - Año 2016 Operaciones Roggero La operación básica es la selección de componentes. La manera de acceder a una componente es vía un nombre y no un valor computado. Por lo general son pocas las operaciones sobre registros completos, la más común es la asignación de registros de estructura idéntica. Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Implementación de Registros La representación de almacenamiento consiste en un sólo bloque secuencial de memoria donde se guardan las componentes en serie. Ejemplo: Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de Registros (Cont.) Roggero La selección de componentes es sencilla debido a que los nombres de campos se conocen durante la traducción. Además la declaración de los registros permiten determinar el tamaño de las componentes y su posición dentro del bloque de almacenamiento durante la traducción. Fórmula de acceso para la i-ésima componente: dir (R.i) = α + O directamente: !i−1 j=1 (tamR.j) dir (R.i) = α + Ki Diseño y Paradigmas de Lenguajes - Año 2016 Strings (cadenas de caracteres) Roggero Objeto de datos compuesto de una secuencias de caracteres. Un aspecto de diseño importante a tener en cuenta es: La manera en que el lenguaje soporta los strings: • ¿Es un tipo primitivo? (Java, Pascal, Ada, JavaScript, Snobol4) • ¿Es un arreglo de caracteres? (C, C++) Diseño y Paradigmas de Lenguajes - Año 2016 Especificación y sintaxis: Roggero • Strings de longitud fija declarada (Cobol, Strings en Java, Pascal) • Strings de longitud variable (con límite declarado) (C y C++) • Strings de longitud variable (sin límite) (JavaScript, Snobol4) El lenguaje Ada provee los 3 tipos. Diseño y Paradigmas de Lenguajes - Año 2016 Algunas operaciones con strings Roggero • Asignación (copia) • Concatenación • Comparación (relacionales) • Longitud • Selección de substrings por posición o concordancia de patrones • Conversión a números • Selección, inserción y supresión de un caracter Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de strings de longitud fija Roggero La longitud es fija y se establece cuando se crea el objeto. Si no se utilizan todas las posiciones, se completan los espacios restantes. R E L A T I V I D A D "" "" "" Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Implementación de strings de longitud variable (con límite) Se reserva espacio para una longitud máxima de string, pero la cadena actual puede ser más corta. Se debe incluir información que permita determinar cual es el último caracter del string actual. Alternativas: • Almacenar longitud actual y máxima 11 14 R E L A T I V I D A D • Almacenar delimitador de fin de string (como en C) R E L A T I V I D A D \0 Diseño y Paradigmas de Lenguajes - Año 2016 Roggero Implementación de strings de longitud variable (ilimitada) El string puede tener cualquier longitud, y variar dinámicamente por la inserción o supresión de caracteres. Alternativas de implementación: • Lista encadenada de caracteres. Fácil inserción y supresión de caracteres. • Se puede usar un arreglo contiguo de caracteres que contenga el string. Es el método usado en C. Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos Roggero Un conjunto es un objeto de datos que contiene una colección no ordenada de valores distintos: Las operaciones básicas sobre conjuntos son: • Pertenecia • Inserción y eliminación de valores. • Unión, intersección y diferencia de conjuntos. Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos Roggero Implementación En los lenguajes de programación, se usa, a veces, la palabra conjunto para nombrar una estructura de datos que representa un conjunto ordenado. Un conjunto ordenado es en realidad una lista, de la que se han eliminado los valores duplicados. Pero si trabajamos con conjuntos no ordenados debemos saber que admiten dos representaciones de almacenamiento. Por ejemplo el type set de Pascal. Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos en Pascal Roggero Sintaxis: TYPE TipoSet = Set of tipo; Ejemplo: type dia = (lu,ma,mi,ju,vi,sa,dm); Frutas = (limon,naranja,uva,pera,platano); conj-caract = Set of Char; digitos = Set of 0..9; dias = Set of dia; clase-fruta = Set of frutas; Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos en Pascal Roggero Y pueden declararse variables de tipo Set de la siguiente manera: var laborable : dias; letras : conj-caract; conj-num : digitos; La asignación de una variable de este tipo puede ser: laborable := [lu, ma, mi, ju, vi]; Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos Roggero Representación de conjuntos por cadenas de bits: es apropiada cuando se sabe que el tamaño del universo subyacente de valores es pequeño. Si tenemos N elementos en el universo e 1 , e2 , .., en , un conjunto de elementos elegido de entre este universo se puede representar por medio de una cadena de bits de longitud N, donde el i-ésimo elemento de la cadena es 1 si ei está en el conjunto y 0 en caso contrario. Diseño y Paradigmas de Lenguajes - Año 2016 Conjuntos Roggero Representación de conjuntos codificada por distribución pseudo-aleatoria (hash): se usa cuando el universo subyacente de valores posibles es grande. Se asigna memoria al menos dos veces más de lo que se espera usar, los elementos se dispersan al azar. El truco está en guardar cada nuevo elemento de manera tal que, su presencia o ausencia se pueda determinar después, de inmediato, sin realizar una búsqueda. Esto se realiza mediante la función de hash. Diseño y Paradigmas de Lenguajes - Año 2016 Punteros Roggero Existen ciertos tipos de datos como los punteros, que bien podrían considerarse elementales, dado que almacenan direcciones, pero su implementación usualmente involucra una organización de estructuras de datos complejas para el compilador. Se necesitan varias características en los lenguajes de programación para permitir el uso de punteros: Diseño y Paradigmas de Lenguajes - Año 2016 Punteros Roggero • Un apuntador de tipo elemental de datos. Un objeto de datos de tipo apuntador contiene la dirección de otro objeto de datos, o un valor especial NULL (o NIL). • Una operación de creación para objetos de datos de tamaño fijo, como arreglos, registros y tipos elementales. En C el malloc o el new en C++. Y si no se provee la desasignación implícita también se requiere de una operación explícita, free en C y delete en C++. • Una operación de desreferenciación para valores de punteros. En C *. Diseño y Paradigmas de Lenguajes - Año 2016 Especificación de Punteros Roggero Un tipo de datos puntero define una clase de objetos de datos cuyos valores son direcciones de otros objetos de datos. Un único objeto de datos de tipo puntero se puede tratar de dos formas: 1 Los punteros sólo pueden hacer referencia a objetos de datos de un solo tipo. Es el enfoque que se usa en Pascal, C y Ada. 2 Los punteros pueden hacer referencia a objetos de datos de distinto tipo. Permitir que un puntero apunte a objetos de datos de tipos variables en diferentes momentos durante la ejecución del programa. Enfoque utilizado en Smalltalk. Diseño y Paradigmas de Lenguajes - Año 2016 Punteros Roggero Operaciones: 1 La operación de creación asigna almacenamiento para un objeto de datos de tamaño fijo y también crea un puntero al nuevo objeto de datos. En Pascal y Ada se llama new. 2 La operación de selección permite seguir un valor de apuntador para alcanzar el objeto de datos designado. En C, por ejemplo la operación de selección que sigue a un puntero hasta su objeto designado se escribe ∗. Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de Punteros Roggero Un objeto de datos de tipo puntero se representa como una ubicación de memoria que contiene la dirección de otra ubicación de memoria. La dirección, es la dirección base del bloque de almacenamiento que representa al objeto de datos al que el puntero apunta. En general, se utilizan dos representaciones para los objetos de datos de tipo puntero: Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de Punteros (Cont.) Roggero • Direcciones absolutas: un valor puntero se puede representar como la dirección de memoria real del bloque de almacenamiento para el objeto de datos. • Direcciones relativas: un valor puntero se puede representar como un desplazamiento, respecto a la dirección base, de algún bloque de almacenamiento(del heap) más grande dentro del cual el objeto está asignado. Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de Punteros (Cont.) Roggero Direcciones Absolutas: Ventajas: 1 Se puede asignar espacio para los objetos de datos en cualquier parte de la memoria. 2 La operación de selección es más efeciente, porque el valor del puntero mismo proporciona acceso directo al objeto de datos. Desventaja: la administración de memoria es más difícil, dado que ningún objeto de datos se puede mover dentro de la memoria si existe un apuntador a él. Diseño y Paradigmas de Lenguajes - Año 2016 Implementación de Punteros (Cont.) Roggero Direcciones Relativas: Se requiere la asignación inicial de un bloque de almacenamiento, dentro del cual tiene lugar la asignación del objeto de dato puntero, por medio de la operación de creación. Ventaja: se puede mover el bloque completo de memoria, en cualquier momento, sin invalidar ningún puntero. Desventaja: la selección es más costosa porque se debe sumar el desplazamiento a la dirección base del área. Diseño y Paradigmas de Lenguajes - Año 2016 Tipo Referencia Roggero Una variable de tipo referencia es similar a un puntero, pero con una diferencia fundamental: Un puntero se refiere a una dirección en memoria, mientras que una referencia se refiere a un objeto o un valor en memoria. Las referencias en Java son identificadores de instancias de alguna clase en particular. Ejemplo: String cad; //referencia a un objeto de la clase String Punto p; //referencia a un objeto de la clase Punto int[] var-arreglo; //referencia a un arreglo de enteros Diseño y Paradigmas de Lenguajes - Año 2016 Tipo Referencia Roggero Una referencia es básicamente un puntero a un objeto, pero, a diferencia de los punteros de otros lenguajes, no obtenemos el valor al que apunta el puntero, sino que el propio Java es el que se encarga de obtener el objeto. Al definir una variable de un tipo de referencia, lo que hacemos es permitir que dicha variable apunte a instancias de dicha clase, es decir, objetos de dicho tipo. Cuando declaramos una variable de este tipo y no la iniciamos, tomará el valor constante NULL.