DEPARTAMENTO DE INFORMÁTICA TRELEW ALGORÍTMICA Y PROGRAMACIÓN I APUNTES DE CÁTEDRA Profesor: Dr. Diego Andrés Firmenich Autoras: Mg. Lic. Alicia Beatriz Paur, Mg. Zulema Beatriz Rosanigo Universidad Nacional de la Patagonia San Juan Bosco Facultad de Ingeniería - Sede Trelew Departamento Informática Trelew Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Algorítmica y Programación I RESOLUCIÓN DE PROBLEMAS ............................................................................................................................ 3 No es lo mismo programar que codificar. ............................................................................................................ 3 ALGORITMO .......................................................................................................................................................... 6 ¿Cómo expresamos un algoritmo? ................................................................................................................. 6 Diagrama de Flujo ......................................................................................................................................... 7 Diagrama Nassi-Schneiderman (N-S) o Diagrama de Chapin.......................................................................... 8 Pseudocódigo................................................................................................................................................ 9 Algunas definiciones ............................................................................................................................................... 9 Constantes y variables .......................................................................................................................................... 10 Estructura del algoritmo ........................................................................................................................................ 10 Declaración de constantes y variables .......................................................................................................... 11 Datos y tipos de datos........................................................................................................................................... 11 Tipos Numéricos.......................................................................................................................................... 11 Tipo Lógico.................................................................................................................................................. 13 Tipo Caracter............................................................................................................................................... 13 Tipo Enumerado .......................................................................................................................................... 13 Tipo Subrango ............................................................................................................................................. 14 Tipo cadena de caracteres ........................................................................................................................... 14 Expresiones.......................................................................................................................................................... 15 Las comparaciones entre valores de tipo real .................................................................................................... 16 Estructuras de control ........................................................................................................................................... 17 Primitivas de control o composición .............................................................................................................. 18 Combinación de estructuras de control ......................................................................................................... 23 Especificación de algoritmos ................................................................................................................................. 23 Metodología de resolución de problemas ............................................................................................................... 25 Un primer programa ..................................................................................................................................... 26 Otro programa: variante del problema anterior .............................................................................................. 28 Variables de uso particular .................................................................................................................................... 30 Prueba del algoritmo ............................................................................................................................................. 30 Validación de datos de entrada ............................................................................................................................. 31 Cómo finalizar la entrada de un conjunto de datos ................................................................................................. 31 Descomposición de problema: Funciones y Procedimientos................................................................................... 34 Tipos de acciones con nombre ......................................................................................................................... 36 Funciones.................................................................................................................................................... 36 Procedimientos ............................................................................................................................................ 40 Recursión ............................................................................................................................................................. 44 Ambito: variables locales y globales ...................................................................................................................... 51 Procedimientos anidados.................................................................................................................................. 52 Efectos laterales............................................................................................................................................... 52 Métodos de paso de parámetros ........................................................................................................................... 54 Estructuras de datos ............................................................................................................................................. 55 Arreglos ........................................................................................................................................................... 56 Recorridos en el arreglo ............................................................................................................................... 57 Accesos aleatorios en el arreglo................................................................................................................... 58 Definición del rango del índice...................................................................................................................... 58 Llenado de datos de un arreglo .................................................................................................................... 58 Algoritmos básicos con arreglos ................................................................................................................... 58 Ejemplos de problemas que requiere la utilización de arreglos. ..................................................................... 63 Cadenas de caracteres..................................................................................................................................... 63 Registros ......................................................................................................................................................... 65 Registros con variante ................................................................................................................................. 66 Conjuntos ........................................................................................................................................................ 68 Archivos........................................................................................................................................................... 69 Estructuras de datos en memoria principal ................................................................................................... 69 Clasificación de archivos.............................................................................................................................. 69 Organización de archivos. ............................................................................................................................ 69 Modos de acceso a los registros de un archivo ............................................................................................. 70 Operaciones sobre archivos ......................................................................................................................... 70 Entorno de programación mínimo para trabajar con archivos secuenciales. ................................................... 71 Archivos de texto ......................................................................................................................................... 71 Ejemplos de algoritmos con archivo de texto ................................................................................................ 72 Archivos de un determinado tipo de datos .................................................................................................... 73 Otras operaciones útiles para cualquier tipo de archivo ................................................................................. 75 Ejemplos de algoritmos con archivos de datos de un tipo determinado .......................................................... 75 Actualización de un archivo .......................................................................................................................... 79 Ejemplo de actualización en línea ................................................................................................................ 80 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 2 Algorítmica y Programación I Ordenamiento, búsqueda e intercalación ............................................................................................................... 83 Métodos de ordenamiento ................................................................................................................................ 83 Método de selección .................................................................................................................................... 83 Método de intercambio o burbuja ................................................................................................................. 84 Método de inserción o de la baraja ............................................................................................................... 85 Método de ordenamiento rápido o quicksort.................................................................................................. 86 Ordenamiento en archivos ........................................................................................................................... 86 Métodos de Búsqueda...................................................................................................................................... 88 Búsqueda lineal ........................................................................................................................................... 88 Búsqueda binaria......................................................................................................................................... 89 Búsqueda en archivos.................................................................................................................................. 91 Análisis de los algoritmos de búsqueda............................................................................................................. 92 Intercalación .................................................................................................................................................... 92 Fusión de archivos....................................................................................................................................... 94 Cortes de control .................................................................................................................................................. 97 Listados - Consideraciones Generales .............................................................................................................. 97 Estructura de un algoritmo de listado ................................................................................................................ 98 Formato genérico de listados con subtotales ................................................................................................... 100 Ejemplos ........................................................................................................................................................ 100 Estructuras de datos dinámicas ........................................................................................................................... 105 Ejemplo de lista dinámica ............................................................................................................................... 106 Lenguaje Pascal ................................................................................................................................................. 109 Estructura de un Programa en Pascal ............................................................................................................. 109 Paralelo entre el seudocódigo y Pascal ........................................................................................................... 110 Precedencia de operadores en Pascal ............................................................................................................ 112 Compiladores de Pascal ................................................................................................................................. 114 Archivos en Turbo Pascal y en Dev Pascal ..................................................................................................... 114 Operaciones para archivos de texto en TurboPascal y Dev Pascal .................................................................. 115 Operaciones para archivos tipados en TurboPascal y Dev Pascal ................................................................... 115 Manejo de errores de entrada salida en TurboPascal y Dev Pascal ................................................................. 116 Bibliografía ......................................................................................................................................................... 118 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 3 Algorítmica y Programación I RESOLUCIÓN DE PROBLEMAS Un problema es un enunciado del que se conocen ciertos datos y que se trata de averiguar el modo de obtener un resultado o solución. Problema del mundo real Existen métodos sistemáticos y métodos científicos para resolver problemas. Cualquier persona puede aprender algo de estos métodos e incrementar su capacidad para resolver problemas. Se pueden considerar cuatro aspectos en la solución de problemas: 1. Formulación de problemas: un problema debe estar formulado en forma correcta, completa y precisa. 2. Expresión rigurosa de la solución o "algoritmo". Un algoritmo es una enunciación sistemática y precisa del modo de resolver un problema formulado correctamente. Implica el desarrollo de un modelo "matemático" que represente el problema. Se debe identificar las variables pertinentes e importantes del problema y las relaciones existentes entre dichas variables y la solución del problema. Modelo Matemático Programa de computadora 3. Formas de expresar algoritmos: mediante gráficos o diagramas de flujo, narraciones, pseudocódigo. 4. Representación óptima de la información para el procesamiento sistemático o de computadora Si tenemos un problema y hemos encontrado la manera de resolverlo por medio de una computadora, es obvio que debemos tener métodos para indicar a la misma cómo llevar a cabo el proceso. Esto es lo que se denomina "programación de computadoras". No es lo mismo programar que codificar. Programar es un proceso mental complejo dividido en varias etapas. Su finalidad es comprender claramente el problema que se va a simular o resolver mediante la computadora, y entender también con detalle cuál será el procedimiento mediante el cual la máquina llegará a la solución deseada. La codificación es una etapa posterior a la programación y consiste en describir en un lenguaje de programación la solución ya encontrada mediante la programación. Fases de creación de un programa: 1. Entender el problema: Esta fase está dada por el enunciado del problema, el cual requiere una definición clara y precisa. Implica hacer un mapa mental del problema y abarcarlo como un todo. No hay reglas ni métodos para esta fase, pero si no se comprende bien el problema la solución puede no ser la correcta. Es importante entender con claridad antes de abocar recursos a su solución. 2. Análisis del problema: Una vez que se ha comprendido lo que se desea de la computadora, es necesario definir: Los datos de entrada y posibles restricciones para sus valores. Cuál es la información que se desea producir (salida) Los métodos y fórmulas que se necesitan para procesar los datos. Implica armar el modelo del problema y abstraerlo en sus distintas estructuras. Este modelo no puede existir sin que se hayan especificado con claridad todos y cada uno de los componentes estructurales del sistema. 3. Programar el modelo de solución propuesto (diseño del algoritmo): Una vez comprendido el problema se trata de determinar qué pasos o acciones tenemos que realizar para resolverlo. La razón de este paso es de disponer de un programa que puede ser probado mentalmente para averiguar si en principio es correcto, y para determinar en qué grado considera todo el análisis hecho anteriormente. Un programa está constituido por dos tipos de componentes: estructuras de datos y estructuras de control. Las estructuras de control son las distintas formas que existen para dirigir el flujo de acciones que el procesador efectuará sobre los datos que manejan en un programa. Una Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 4 Algorítmica y Programación I filosofía a la hora de diseñar algoritmos es el refinamiento por pasos, y es partir de una idea general e ir concretando cada vez más esa descripción hasta que tengamos algo concreto para resolver. Pasamos de lo más complejo a lo más simple. Si el problema es complejo, lo mejor es dividirlo en partes más pequeñas e intentar resolverlas por separado. Esta metodología de “divide y vencerás” también se conoce con el nombre de diseño descendente. Al dividir el problema en módulos o partes se comprende más fácilmente y también resulta más sencillo mantenerlo. En cuanto a los resultados, se probarán mucho mejor comprobando si cada módulo da el resultado correcto que si intentamos probar de un golpe todo el programa ya que si se produce un error sabemos en qué módulo ha sido. Los métodos usuales para representar un algoritmo son: - Formas gráficas: diagrama de flujo y diagramas estructurados N-S (NassiSchneiderman). - Lenguaje de especificación de algoritmos: pseudocódigo 4. Codificación: Como un programa en pseudocódigo o mediante diagramas no es ejecutable en una computadora, se debe traducir a un lenguaje de programación. La codificación es la operación de escribir la solución del problema (de acuerdo a la lógica del diagrama de flujo o pseudo código), en una serie de instrucciones detalladas, en un código reconocible por la computadora, la serie de instrucciones detalladas se le conoce como código fuente, el cual se escribe en un lenguaje de programación. Ejemplos de lenguajes de programación son Pascal, C, java, Ada, Basic, etc. 5. Ejecución y ajuste: Los errores humanos dentro de la programación de computadoras son muchos y aumentan considerablemente con la complejidad del problema. El proceso de identificar y eliminar errores, para dar paso a una solución sin errores se le llama depuración. Existen dos tipos de fallas que es posible encontrar en un programa ya codificado: errores de sintaxis/semántica y errores de lógica de programación. Un error de lógica apunta claramente a omisiones y errores en el modelado que se está tratando de hacer de la realidad, generalmente se debe a un deficiente análisis o a una programación en pseudocódigo incompleta y apresurada. Los errores de sintaxis/semántica son fácilmente encontrados por el traductor del lenguaje de programación, ya sea intérprete o compilador, y se trata de formulaciones incorrectas de las expresiones del lenguaje, como por ejemplo ausencia de un punto y coma, falta de declaración de un identificador, mal uso de una primitiva, etc. 6. Documentación: Es la guía o comunicación escrita en sus variadas formas, ya sea en enunciados, procedimientos, dibujos o diagramas. A menudo un programa escrito por una persona, es usado por otra. Por ello la documentación sirve para ayudar a comprender o usar un programa o para facilitar futuras modificaciones (mantenimiento). La documentación abarca tres partes: o Documentación Interna: Son los comentarios o mensaje que se añaden al código fuente para hacer más claro el entendimiento de un proceso. o Documentación Externa: Se define en un documento escrito los siguientes puntos: Descripción del Problema Nombre del Autor, fecha de realización Algoritmo (diagrama de flujo o pseudo código) Diccionario de Datos Código Fuente (programa) o Manual del Usuario: Describe en detalle la manera cómo funciona el programa, con el fin de que el usuario obtenga el resultado deseado. 7. Mantenimiento: Se lleva a cabo después de terminado el programa y se realiza a lo largo de su vida útil, cuando se detecta que es necesario hacer algún cambio, ajuste o complementación al programa para que siga trabajando de manera correcta. Para poder realizar este trabajo se requiere que el programa este correctamente documentado. Hay que ser capaces de hacer alteraciones no estructurales al sistema con un costo mínimo de análisis y programación. Cualquiera sea el tamaño de un programa tenemos el compromiso de hacerlos claros y flexibles para que admitan mejoras o sugerencias posteriores. Un programa es la suma del algoritmo y las especificaciones necesarias para que éste emita un resultado. En términos formales: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 5 Algorítmica y Programación I Programa = estructuras de datos + estructuras de control Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 6 Algorítmica y Programación I ALGORITMO La palabra algoritmo se deriva de la traducción al latín de la palabra árabe Al-Khowarîzmî, nombre del matemático y astrónomo árabe Mohammed ibn-Musa Al-Khowarizmi que escribió un tratado sobre la manipulación de números y ecuaciones en el siglo IX. Su obra se empezó a conocer como "de AlJuarizmi" y por deformación en la traducción llegó hasta "algoritmo". Actualmente el término algoritmo significa procedimientos operativos que permiten resolver cualquier problema de un determinado tipo. Básicamente, un algoritmo es un método para resolver un problema mediante una serie de pasos. Las características de un buen algoritmo son: Debe tener un punto particular de inicio. Debe ser definido, no debe permitir dobles interpretaciones. Si se aplica partiendo de la misma situación inicial, debe obtenerse siempre el mismo resultado. Debe ser general, es decir, soportar la mayoría de las variantes que se puedan presentar en la definición del problema. Debe ser finito en tamaño y tiempo de ejecución. Ejemplo: Algoritmo para armar la lista de aprobados y desaprobados de un examen escrito. Tomar una hoja en blanco y dibujar dos columnas: Una para aprobados y otra para desaprobados. Traer la pila de exámenes corregidos. Mientras la pila no esté vacía Tomar el examen de la cima de la pila. Si el examen está aprobado, anotarlo en la columna de aprobados sino, anotarlo en la columna de desaprobados. Colocar el examen en la pila de exámenes pasados Publicar la hoja con las dos columnas que se acaba de completar. En esta descripción de pasos, podemos distinguir: o Objetos que deben existir para que el algoritmo se pueda realizar, como la pila de exámenes corregidos y la hoja para anotar. o Condiciones o enunciados lógicos que pueden ser verdaderos o falsos, como “la pila no esté vacía”, “el examen está aprobado” o Secuencias de acciones: “tomar una hoja en blanco”, “dibujar dos columnas”. o o Alternativas de acción: “anotar en aprobados” o “anotar en desaprobados” pero no ambas. Repetición de acciones según sea cierta una condición: “mientras la pila no está vacía” se hacen un conjunto de acciones. Todos los objetos que son necesarios para que el algoritmo se pueda desarrollar conforman el “ambiente” del algoritmo. Pueden mantenerse constantes en toda la ejecución o pueden ir variando su valor o estado. El lenguaje con que se expresan los pasos debe ser claro y entendible por cualquier persona o entidad que lo deba interpretar. ¿Cómo expresamos un algoritmo? Se dispone de una notación algorítmica que permite: o o o Describir las operaciones puestas en juego (acciones). Describir los objetos manipulados por el algoritmo (datos/informaciones). Controlar la realización de las acciones, indicando el modo de organización de estas acciones en el tiempo (mediante las primitivas de composición o de control). Debe subrayarse que la notación algorítmica no es en ningún caso un lenguaje de programación. Lo esencial de la notación algorítmica es que las acciones elegidas sean las necesarias y suficientes para expresar todo algoritmo. En este sentido, autores como E. Dijkstra, C.A.R. Hoare y otros consolidaron a finales de los años 60 los fundamentos de la Programación Estructurada, mediante la proposición del Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 7 Algorítmica y Programación I uso de un conjunto de construcciones lógicas (secuencia, decisión e iteración) con las que se podría escribir cualquier programa. La notación algorítmica puede realizarse mediante diagramas de flujo, diagramas estructurados NassiSchneiderman y lenguaje de especificación de algoritmo o pseudocódigo. Diagrama de Flujo de Datos Es una herramienta gráfica de descripción de algoritmos. Se caracteriza por utilizar un conjunto de símbolos gráficos normalizados y expresar de forma clara los posibles caminos de ejecución de las acciones (secuencia, decisión e iteración). El principal inconveniente que plantea el diagrama de flujo es que permite realizar cualquier tipo de construcción algorítmica, independientemente de que ésta sea correcta o no desde el punto de vista de la programación estructurada. Los elementos básicos son: rectángulos con puntas redondeadas para indicar el comienzo o el final del algoritmo, rectángulos para indicar acciones, rombos para indicar condiciones que permiten tomar caminos alternativos, y flechas. Ejemplo de diagrama de flujo: Algoritmo para armar la lista de aprobados y desaprobados de un examen escrito. Inicio Tomar una hoja en blanco Dibujar dos columnas: aprobados y desaprobados. Traer la pila de exámenes corregidos no pila no vacía si Tomar el examen de la cima de la pila no Anotarlo en la columna de desaprobados examen aprobado si Anotarlo en la columna de aprobados Colocar el examen en la pila de exámenes pasados Publicar la hoja con las dos columnas Fin Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 8 Algorítmica y Programación I Diagrama Nassi-Schneiderman (N-S) o Diagrama de Chapin El diagramas N-S de Nassi-Schneiderman, también conocido como diagrama de Chapin, es un método gráfico para la descripción de algoritmos que combina la descripción textual, propia del pseudocódigo, con la representación gráfica del diagrama de flujo. Cuenta con un conjunto limitado de símbolos gráficos y palabras reservadas para representar los pasos del algoritmo. El símbolo básico utilizado es el rectángulo, por lo que también se lo conoce como diagrama de cajas. El modelo de diagramas fue desarrollado por Isaac Nassi y Ben Shneiderman, publicándose en el año 1973. Corresponden a las normas DIN 66261 e ISO/IEC 8631:1989. Dado que muestran las estructuras de un programa, también se denominan "estructogramas". Los diagramas proporcionan toda la lógica necesaria para que programadores puedan escribir programas estructurados en cualquier lenguaje de programación o a los efectos de documentar procedimientos específicos. Cada bloque (o secuencia de acciones) se representa dentro de un rectángulo; cada estructura utiliza una forma de representación particular, pudiendo a su vez encerrar otros bloques. Existen seis estructuras: Secuencial, Decisión (si condicional), Selección, Para, Mientras y Repetir Presentan una serie de ventajas: Dada su forma de construcción es imposible representar algoritmos incorrectos desde el punto de vista de la programación estructurada. El ámbito de cada estructura está perfectamente definido. Y también una serie de inconvenientes: Cada diagrama sólo refleja la estructura de un único módulo. No pueden modificarse fácilmente. Es necesario construirlos de nuevo, aunque sea para realizar pequeños cambios. Ejemplo de diagrama NS: Algoritmo para armar la lista de aprobados y desaprobados de un examen escrito. Tomar una hoja en blanco Dibujar dos columnas: aprobados y desaprobados. Traer la pila de exámenes corregidos Mientras pila no vacía Tomar el examen de la cima de la pila examen aprobado si no Anotar en Aprobados Anotar en Desaprobados Colocar el examen en la pila de exámenes pasados Publicar la hoja con las dos columnas Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 9 Algorítmica y Programación I Pseudocódigo El pseudocódigo es una notación algorítmica textual caracterizada por: Permitir la declaración de los datos (constantes y/o variables) manipulados por el algoritmo. Disponer de un conjunto pequeño y completo de palabras reservadas que permitan expresar: las acciones elementales, las primitivas de composición de acciones y la definición de acciones con nombre (funciones y procedimientos). El pseudocódigo es un lenguaje neutro y completo, es decir, denota independencia respecto a alguna máquina en particular y tiene poder suficiente para expresar cualquier idea computacional desde la perspectiva de los procedimientos estructurados. Cuando se escribe un pseudocódigo, lo que se hace es indicar las acciones que se tienen que realizar para dar solución a un determinado problema, en español o en inglés estructurado permitiéndonos escribir de forma natural, como "relatando" los pasos que se tienen que seguir. Utilizar un pseudocódigo tiene la ventaja de que el programador no se preocupa por los detalles que exigen los lenguajes de programación, permitiendo una mayor concentración en la lógica de la resolución del problema. Existen palabras específicas, denominadas metapalabras que tienen un significado y objetivo específico dentro del pseudocódigo. Las metapalabras que utilizaremos son las siguientes, su significado y uso se irá viendo a lo largo del curso: Abrir Cadena Conjunto Entero Falso Hasta Lógico Ó o OR Registro Sino Verdadero o Verd Algoritmo Carácter Constantes o Cons Entonces Fin Ingresar Mientras Paso Repetir Tipo Y o And Archivo Cerrar Crear Eof Función Inicio Mostrar Procedimiento Según Variables o Var Arreglo Con Desde Escribir hacer Leer No Real Si Veces Algunas definiciones Ambiente del algoritmo: es el conjunto de todos los recursos para la ejecución de un trabajo. Acción: es un evento o acontecimiento que modifica el ambiente. Una acción es primitiva o simple si su enunciado es suficiente para que pueda ejecutarse sin información adicional. Por el contrario, una acción es no-primitiva o compuesta si para ejecutar su enunciado es necesario descomponerla en acciones primitivas. Nos interesarán solamente aquellos acontecimientos que tienen lugar durante un período de tiempo finito y producen un resultado bien definido y previsible. El hecho de que una acción dure un tiempo finito significa que es posible hallar un instante de inicio de la acción (t0) y un instante de finalización de la acción (t1). Para que sea posible reconocer el resultado es preciso observar en el sistema una serie de atributos o características que cambien su valor en ese intervalo de tiempo. Proceso: Conjunto de acontecimientos organizados en el tiempo y concebido como activo. En nuestro contexto informático, describir un proceso es interpretar un acontecimiento como un conjunto de acciones más elementales cuyo efecto acumulativo es el mismo que el producido por el acontecimiento completo. Estado (de un sistema para un proceso determinado) en un instante dado t: Conjunto de los valores de los distintos atributos o características de interés en el instante t del desarrollo del proceso. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 10 Algorítmica y Programación I El estado en el instante t0 de una acción define los datos de la acción, mientras que el estado en el instante t1 define los resultados de la acción (o también datos de entrada y datos de salida, respectivamente). Por otra parte, el hecho de que el resultado de la acción sea previsible significa que si se conoce el estado del sistema en el instante t0 entonces se puede decir con total precisión cuál será el estado en el instante t1, incluso antes de que la acción en cuestión se produzca. Condición: es una afirmación lógica sobre el estado del problema que puede ser verdadera o falsa en el momento de observación. Constantes y variables Constante: es un objeto cuyo valor no puede variar. Puede tener un nombre que la identifique o no, si no lo llevan, se llaman literales. La ventaja de usar constantes con nombre es que en cualquier lugar donde quiera que vaya la constante, basta con poner su nombre y luego el compilador lo sustituira por su valor. Por ejemplo, PI es el nombre de la constante 3.14159... Variable: es un objeto cuyo valor puede variar. Debe tener un nombre que la identifique y debe indicarse el tipo de dato que guardará. Los atributos de una variable son: nombre o identificador, valor, tipo, dirección de memoria. Elección de nombres: Los nombres permiten identificar a los distintos objetos del ambiente. Deben ser significativos, es decir, deben dar idea de lo que representan. Los nombres no pueden tener espacios, deben comenzar con alguna letra del alfabeto y puede ser seguida de otras letras, dígitos y el guión subrayador. Dependiendo del lenguaje de programación, las reglas de nombres pueden ser sensibles a mayúsculas y minúsculas, es decir, dos nombres que suenan iguales pero uno en mayúsculas y otro en minúsculas como N y n son identificadores distintos, mientras que otros lenguajes los toman como iguales. Nosotros adoptaremos la última forma. Ejemplo: Identificadores válidos MiCasa, este_ejemplo, total1 no válidos mi casa, 1dia, $ Estructura del algoritmo Cabecera: Debe indicarse el nombre del algoritmo, y la lista de parámetros si los hubiera. Generalmente se la acompaña de un comentario que sirva de documentación. Declaración del ambiente: Se detallan los recursos que se utilizarán, indicando si se trata de declaraciones de constantes, variables, tipos, etc. Cuerpo: Es el conjunto de acciones que deberán realizarse. Van encerradas entre las metapalabras Inicio y fin Cabecera Declaración del ambiente Cuerpo Algoritmo armarListaAprobadosDesaprobados Ambiente Pila_Examenes Hoja Examen Inicio Tomar una hoja en blanco y dibujar dos columnas: Una para aprobados y otra para desaprobados. Traer la pila de exámenes corregidos. Mientras la pila no esté vacía Tomar el examen de la cima de la pila. Si el examen está aprobado, anotarlo en la columna de aprobados sino, anotarlo en la columna de desaprobados. Colocar el examen en la pila de exámenes pasados Publicar la hoja con las dos columnas que se acaba de completar. Fin Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 11 Algorítmica y Programación I Inclusión de comentarios: en cualquier parte del algoritmo, se pueden incluir comentarios que sirvan para clarificar la solución del problema. El comentario es un texto libre encerrado entre secuencias de (* y *). También podemos utilizar // para indicar el comienzo de un comentario de línea, todo lo que sigue al // y hasta el final del renglón es un comentario Ejemplo: (* esto es un comentario *) // y este otro también Declaración de constantes y variables Dentro del ambiente del algoritmo, distinguiremos una sección para la declaración de constantes y otra para la declaración de variables. La sección de constantes será precedida por la metapalabra Constantes o Const, y a continuación se declararán las constantes que se utilizarán siguiendo la sintaxis: <identificador> = <valor> Ejemplo Const PI = 3.14159 Tope = 100 Universidad = “U.N.P.S.J.B.” El tipo de una constante se deduce de su valor. PI es una constante real, Tope es entera y Universidad es de tipo cadena de caracteres. La sección de variables será precedida por la metapalabra Variables o Var, y a continuación se declararán las variables que se utilizarán siguiendo la sintaxis: <identificador>: <tipo> Ejemplo Var Cantidad: entero Superficie: real EsEstudioso: lógico Datos y tipos de datos Los datos que maneja un programa pertenecen a un tipo determinado. El tipo define: o o o el conjunto de valores posibles (rango), el conjunto de operaciones posibles (lo que podríamos llamar su álgebra), su representación interna. Podemos clasificarlos en simples y compuestos (o estructurados). Los primeros denotan un solo valor, los segundos representan un conjunto de valores. Los datos pueden ser variables o constantes. Simple Ejemplo Estructurado Ejemplo entero real lógico carácter enumerados subrango 290, -21 32.17, -2.002 verdadero, falso ‘A’ , ‘5’, ‘+’ cadena arreglo registro archivo conjunto “hola a todos” {3, 5, 7, 3, 15} {“Ana”, 15, verdadero} Tipos Numéricos Permiten representar valores escalares de forma numérica, esto incluye a los números enteros y los reales y realizar operaciones aritméticas comunes. Entero: tipo simple ordinal que se utiliza para representar valores numéricos enteros, en un rango finito dado por la implementación. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 12 Algorítmica y Programación I Variantes de enteros: De acuerdo con la cantidad de bytes de la implementación pueden variar en: Entero corto: Utiliza uno o dos bytes para su implementación. Entero normal: Utiliza dos o cuatro bytes Entero largo: Utiliza cuatro o seis bytes. n-1 n-1 El rango de valores de cada caso, será -2 .. 2 -1, donde n es la cantidad de bits de la representación. Por ejemplo, un entero corto de un byte, tendrá un rango que va desde –128 a 127. Ejemplos: 12 919 (número de mes) (páginas de un libro) 35.000.000 (habitantes de Argentina) Real: tipo simple que se utiliza para representar valores numéricos del conjunto de números reales. Pueden ser de simple y doble precisión. Utiliza implementación de punto flotante. Representación Bit de signo exponente mantisa Recordar que existe un rango de valores reales cuya representación es la misma debido al límite finito de dígitos de la mantisa y que números reales con un número finito de dígitos decimales puede tener una mantisa con una serie infinita de números binarios, como por ejemplo el valor decimal 0.10, por lo que es muy importante tener presente estas cuestiones en el momento de comparar valores. Ejemplos: 3.14159 (pi) 9.8 (aceleración de la gravedad) PuntoFijo: Algunos lenguajes brindan un tipo especial de dato para representar montos de dinero. Ya que la cantidad de decimales de un monto de dinero es fija (generalmente es dos, por los centavos), es conveniente utilizar un tipo de dato de punto fijo en lugar de punto flotante. En este caso, se debe aclarar en su definición con cuántos decimales se fija. De no poseer este tipo, magnitudes como el dinero pueden ser representadas en un entero largo en centavos o miliavos, y evitar de esta manera los errores propios del redondeo y truncamiento de números reales. Operaciones numéricas Operación Operador Enteros Reales PuntoFijo Suma Resta Producto División entera División real Resto de la división entera + * div / mod Sí Sí Sí Sí No Sí Sí Sí Sí No Sí No Sí Sí Sí No Sí No Funciones Argumento Resultado Ejemplo Siguiente Anterior Valor absoluto Redondear Truncar Raiz cuadrada Valor absoluto Trigonométricas Entero Entero Entero o real Real Real Real Real o entero Real entero Entero Entero o real Real Entero Real Real o entero Real Logaritmo natural Real Real Sig(n) Ant(n) Abs(n) Redondear(x) Trunc(x) RC(x) Abs (x) Seno(x) Coseno(x) Tg(x) Ln(x) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 13 Algorítmica y Programación I Exponencial Real Real Exp(x) Tipo Lógico Lógico: tipo simple ordinal que se utiliza para representar valores booleanos (verdadero, falso). Representan el resultado de una comparación entre otros datos (numéricos o alfanuméricos). Alcanza un bit pero la menor unidad de direccionamiento es el byte por lo que se utiliza un byte. Ejemplos: verdadero falso (soy alumno de la Facultad de Ingeniería) (estoy volando por las nubes) Operaciones lógicas Operación Negación Conjunción Disyunción Operador No (not) Y (and) O (or) Ejemplo No esBueno Estudia Y esBueno Estudia O esBueno Tipo Carácter Carácter: tipo simple ordinal que se utiliza para representar los distintos caracteres empleados para formar palabras, expresiones aritmético-lógicas o para graficación. Abarca letras mayúsculas, letras minúsculas, dígitos y caracteres especiales. Este conjunto de valores se normalizó, por un estándar llamado ASCII (American Standard Code for Information Interchange), el cual permite establecer un orden de precedencia entre los mismos. Los valores constantes de este tipo se escriben entre apóstrofos o comillas simples. Ejemplos: ‘B' '#' (be mayúscula) (numeral) '' '2' (asterisco) (dígito 2) Operaciones con caracteres Funciones Argumento Resultado Ejemplo Sig(carácter) da el siguiente carácter carácter de la tabla carácter Sig(‘B’) da la letra ‘C’ Ant(carácter) da el carácter anterior carácter en la tabla carácter Ant(‘C’) da la ‘B’ Ord(carácter) da el número de orden carácter en la tabla de caracteres entero Ord(‘A’) da 65 Chr(entero) da el carácter que se Entero (0-255) encuentra en un determinado lugar de la tabla. carácter Chr(65) da ‘A’ Tipo Enumerado Son aquellos cuyos valores se pueden definir por extensión. Abarcan a los caracteres, enteros y lógicos. También es posible definir nuevos tipos de datos por enumeración de sus valores. En este caso se define un nuevo nombre de tipo y se lo asocia con el conjunto de valores dado por enumeración. Cada nuevo valor tiene un nombre no utilizado hasta el momento y el sistema le asocia una representación interna. La posibilidad de definir nuevos tipos de datos contribuye a dar mayor claridad al problema. Ejemplo: crearemos un nuevo tipo de datos llamado colorPrimario con los valores de esos colores. Para ello en el ambiente, habrá una sección de definición de tipos. Tipo ColorPrimario = (rojo, amarillo, azul) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 14 Algorítmica y Programación I Var Color: ColorPrimario La variable color podrá tomar cualquiera de los valores detallados para su tipo. Operaciones con tipos enumerados Funciones Argumento Resultado Ejemplo Siguiente(enumerado) da el Enumerado siguiente enumerado de su lista de definición Enumerado Sig(rojo) da el valor amarillo Anterior(enumerado) da el Enumerado previo enumerado de su lista de definición Enumerado Ant(amarillo) valor rojo da el Tipo Subrango Se pueden crear nuevos tipos de datos por subrango de valores de los tipos enumerados. Ejemplos: Tipo ColorPrimario = (rojo, amarillo, azul) Digito = 0..9 // Subrango de enteros Incognita = ‘X’.. ‘Z’ // subrango de caracteres ColorBoca = amarillo..azul // subrango de ColorPrimario Una variable de tipo Digito podrá tener valores entre 0 y 9 y podrá aparecer en cualquier contexto de tipo entero, una de tipo Incógnita podrá tener valor ‘X’, ‘Y’ o ‘Z’ y podrá utilizarse en cualquier contexto que espere un carácter. Una variable de tipo ColorBoca podrá tener valor amarillo o azul y podrá utilizarse en el contexto que espere un tipo ColorPrimario. Observar que para poder definir el subrango ColorBoca, previamente tuvo que estar definido un tipo donde los valores inicio y fin del subrango estén definidos, en este caso se trata del tipo ColorPrimario. Las operaciones permitidas para los tipos subrangos son aquellas del tipo al que corresponden los extremos del subrango el cual debe ser de algún tipo enumerado (entero, carácter, lógico o enumerado creado por el usuario). Tipo cadena de caracteres Cadena: tipo compuesto por una secuencia de caracteres que es utilizado para expresar palabras. Es una secuencia de caracteres alfanuméricos que permiten representar valores identificables de forma descriptiva, esto incluye nombres de personas, direcciones, etc. Es posible representar números como alfanuméricos, pero estos pierden su propiedad matemática, es decir no es posible hacer operaciones con ellos. Las constantes de este tipo de datos se representan encerradas entre comillas. Ejemplos: “Mi perro” “No” “” (la cadena vacía) “S” (cadena unitaria) “32.324.123” Operaciones con cadenas de caracteres Funciones Argumento Resultado Ejemplo Longitud(cadena) da la cantidad Cadena de caracteres actual en la cadena entero Long(“yo” ) es 2 Concatena(cadena1,cadena2) Dos cadenas da como resultado una cadena formada por las recibidas como cadena concatena(“Hola”, “ a todos”) da como resultado “Hola a Todos” Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 15 Algorítmica y Programación I argumentos Subcadena(cadena, pos, n): da 1 cadena, 2 cadena la subcadena de n caracteres a enteros partir de la posición pos Acceso a un elemento de la cadena de caracteres Subcadena(“Hola”,3,2) da “la” Para acceder a un elemento de la cadena (un carácter), se tiene el operador [] . Se debe indicar el índice del carácter al que se quiere acceder, el índice es un valor entero que podrá variar entre 1 y la longitud de la cadena. Ejemplo: Sea nombre de tipo cadena y nombre contiene el valor “Juan”. Nombre[1] nos dará la ‘J’ y nombre[4] nos dará la ‘n’ Expresiones Las expresiones son combinaciones de constantes, variables, símbolos de operación, paréntesis y nombres de funciones especiales. Por ejemplo: a + (b + 3) / c Cada expresión toma un valor que se determina tomando los valores de las variables y constantes implicadas y la ejecución de las operaciones indicadas. Una expresión consta de operadores y operandos. Según sea el tipo de datos que manipulan, se clasifican las expresiones en: o o o Aritméticas Relacionales Lógicas Operadores Aritméticos: Los operadores aritméticos permiten la realización de operaciones matemáticas. Pueden ser utilizados con tipos de datos enteros o reales, constantes o variables. Si ambos son enteros, el resultado es entero; si alguno de ellos es real, el resultado es real. + * div Ejemplos: Expresión Resultado Suma Multiplicación División entera 7.6 / 2 3.8 / Mod 15 mod 4 3 Resta División real Modulo (residuo de la división entera) 14 + 2 * 5 24 Prioridad de los Operadores Aritméticos 1. Todas las expresiones entre paréntesis se evalúan primero. Las expresiones con paréntesis anidados se evalúan de dentro a fuera, el paréntesis más interno se evalúa primero. 2. Dentro de una misma expresión los operadores se evalúan en el siguiente orden. a. *, /, div, mod Multiplicación, división real, división entera, módulo. b. +, Suma y resta. 3. Los operadores en una misma expresión con igual nivel de prioridad se evalúan de izquierda a derecha. Ejemplos: 14 + 2 * 5 = 24 203 * 2 div 5 = 81 30 + 5 * (10 - (21 + 4)) = -45 12.2 / 2 + 5 mod 2 = 7.1 46.5 / 5.0 = 9.3 13 mod 5 + 5 * (10 - 6) = 23 Operadores Relacionales: Se utilizan para establecer una relación entre dos valores. Compara estos valores entre sí y esta comparación produce un resultado de certeza o falsedad (verdadero o falso). Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 16 Algorítmica y Programación I Los operadores relacionales comparan valores del mismo tipo (numéricos, carácter, enumerados o cadenas). Tienen el mismo nivel de prioridad en su evaluación pero tienen menor prioridad que los aritméticos. > Mayor que < Menor que > = Mayor o igual que < = Menor o igual que < > Diferente = Igual Ejemplos: Si a vale 1, b vale 2, c vale 3, nomb1 vale “Ana” y nomb2 vale “Amalia” a+b>c “Juan” <> nomb2 “Mara” < “Amalia” Falso Verdadero Falso a - b <= c nomb1[1] = ‘A’ nomb1 < nomb2 Verdadero Verdadero Falso Ejemplos no válidos: a<b<c nomb1 < 30 porque compara más de dos elementos a la vez porque compara operandos de distinto tipo Operadores Lógicos: Estos operadores se utilizan para establecer relaciones entre valores lógicos, los cuales pueden ser resultado de una expresión relacional. No Negación Tablas de verdad Y Conjunción O Op1 Op2 Op1 y Op2 Op1 o Op2 V V F F V F V F V F F F V V V F Ejemplos: (10 < 20) V Y (20 > 30) F (10 < 20) V F O Disyunción No Op1 V F (20 > 30) F V Prioridad de los Operadores Lógicos No Y O Prioridad de los Operadores en General: Aunque cada lenguaje de programación establece el orden de prioridad, nosotros tomaremos el siguiente orden: 1. 2. 3. 4. 5. 6. 7. () *, /, Div, Mod +, No Y O >, <, > =, < =, < >, = Las comparaciones entre valores de tipo real Los datos de tipo real se almacenan en formato de punto flotante. Como la cantidad de dígitos significativos de un número real puede ser matemáticamente infinita y la representación en la máquina es finita, se provocan redondeos y truncamiento, pudiendo resultar que dos números reales matemáticamente distintos tengan igual representación interna. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 17 Algorítmica y Programación I También puede darse el caso que dos valores matemáticamente iguales tengan representación computacional distinta: el resultado de dos operaciones sea matemáticamente idéntico pero por surgir de representaciones distintas antes del cálculo, den resultados computacionalmente diferente. Por ejemplo: 5.4 – 3.1 es matemáticamente igual a 308.93 – 1006.80 + 700.17. Si el resultado de esas operaciones se almacenan respectivamente en variables R1 y R2 de tipo real, es muy probable que al hacer la comparación R1 = R2 dé el valor falso. Se llama épsilon de la máquina al valor del intervalo entre la representación de 1 y el menor número mayor que 1 (pero distinto de 1). Los valores que disten de 1 la mitad de épsilon o menos tendrán igual representación que el 1 y aunque sean matemáticamente distintos, se verán iguales. Para evitar errores en las comparaciones por problemas de redondeo se debe utilizar un valor pequeño DELTA positivo (fijado por la precisión del problema o el épsilon de la máquina) haciendo: Hacer En lugar de: Abs (R1 –R2) <= DELTA R1 = R2 Abs (R1 –R2) > DELTA R1 <> R2 R1 > R2 + DELTA R1 > R2 R1 < R2 – DELTA R1 < R2 R1 > R2 – DELTA R1 >= R2 R1 < R2 + DELTA R1 <= R2 Gráfico Rango de reales con igual representación interna que R2 Menores R2-DELTA R2 R2+DELTA Mayores Estructuras de control Las estructuras de control son construcciones lógicas que dirigen el flujo de acciones que efectuará el procesador sobre los datos que maneja un programa. Para que un programa funcione de manera que satisfaga plenamente los requisitos para los cuales fue creado, debe permitir: o o o Ejecutar una serie de acciones o instrucciones. Poder repetir un conjunto de acciones o instrucciones, según se satisfaga o no una condición. El empleo de acciones alternativas para poder elegir entre una acción o un grupo de acciones cuando la situación así lo requiera. Para poder dar cabida a estos requisitos, existen estructuras de control perfectamente definidas: o Secuenciación: ejecutar las acciones indicadas, una después de otra. o Selección: Evaluar cierta condición lógica, si el resultado es verdadero ejecutar la acción 1, en otro caso ejecutar la acción 2. o Iteración condicional: Evaluar cierta condición lógica, cuando el resultado es verdadero, ejecutar una acción y continuar así mientras la condición siga siendo verdadera. En este sentido, en mayo de 1966, Böhm y Jacopini demostraron que un programa puede ser escrito utilizando solamente estos tres tipos de estructuras de control. Secuencia Cuando se deben ejecutar sucesivamente distintas acciones, se escribirá la lista de dichas acciones en el orden en el que deban ser ejecutadas. Pseudocódigo E1 E2 E3 Diagrama de flujo E1 E2 Diagrama N-S E1 E2 E3 E3 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 18 Algorítmica y Programación I Selección Construcción que evalúa una expresión, y en función de los valores de ésta, dirige el flujo del proceso. Pseudocódigo Si C entonces E1 Sino Diagrama de flujo V Diagrama N-S F C ¿C? Si E2 E1 Fin Si E2 No E1 E2 Iteración condicional Construcción que indica la repetición de un grupo de enunciados de acuerdo al valor de determinada condición. Pseudocódigo Mientras C hacer E1 Fin Mientras Diagrama de flujo F Diagrama N-S Mientras C C V E1 E1 Primitivas de control o composición Acción elemental: ejecución de un acontecimiento elemental. Considerando que en última instancia es una computadora la máquina que ejecutará las instrucciones de un programa, las acciones elementales vendrán determinadas por las tareas que puede realizar una computadora. Básicamente estas tareas son: o realizar una serie de operaciones (aritméticas o lógicas) sobre unos operandos. o posibilidad de obtener dichos operandos a través de un dispositivo de entrada o de la memoria de la computadora. posibilidad de almacenar los resultados de dichas operaciones en memoria o enviarlos hacia un dispositivo de salida. o Instrucción de Asignación: Operación mediante la cual se da valor a una variable. Utilizaremos el símbolo <- para indicar que la variable del primer miembro toma el valor de la expresión del segundo miembro. Algunos lenguajes de programación adoptan el símbolo := y otros el símbolo =. Sintaxis: <var> <- <expresión> Ejemplo: Cant <- 0 Instrucción de entrada de datos: Operación mediante la cual se lee un dato de la entrada estándar y se lo asigna a una variable. Hay una asignación implícita. La variable debe ser de un tipo simple o cadena. No puede ser de tipo enumerado creado por el usuario ni de otro tipo compuesto. Sintaxis: Ingresar(<var>) Ejemplo: Ingresar(cant) Instrucción de salida: Operación mediante la cual se muestran datos en la salida estándar. La expresión debe ser de un tipo simple o cadena. No puede ser de tipo enumerado creado por el usuario. Sintaxis: Mostrar(<expresión> [,<expresión>]) Ejemplo: Mostrar(“Mes =”, mes) Selección simple o composición condicional: sentencia que evalúa una condición, y si es cierta, realiza la acción propuesta. Si la condición es falsa, dicha acción no es realizada. ¿C? V acción Diagrama de flujo Cond F Si acción No seguir Diagrama NS Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 19 Algorítmica y Programación I Sintaxis: Si (<condición>) entonces <acción> Fin Si Ejemplo: Si ( a < b) entonces Dif <- b – a Fin Si Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 20 Algorítmica y Programación I Selección dicotómica o composición alternativa: sentencia que evalúa una condición, y en función de ella realiza una u otra acción: si es la condición es verdadera, se realiza accionV y si es falsa la accionF. Sintaxis: Si (<condición>) entonces <accionV> Sino <accionF> Fin Si Ejemplo: Si ( a < b) entonces Dif <- b - a Sino Dif <- a - b Fin Si V F Cond accionV accionF Diagrama de flujo ¿C? Si No AccionV AccionF Diagrama NS Selección múltiple o composición selectiva: enunciado formado por una expresión discreta (entera, carácter, enumerado), cuyo valor pertenece a un conjunto de opciones, y según el cual se ejecuta uno sólo de los enunciados alternativos. Sintaxis: Según <expr> hacer expr <valor1> : <accion1> v1 v2 ... vN otro <valor2> : <accion2> Diagrama de flujo ... <valorN> : <accionN> Accion1 Accion2 [Sino <accionD>] AccionD Fin Según Accion2 Ejemplo: Según mes hacer expr 1 : Mostrar (“enero”) 2 : Mostrar (“febrero”) Valor1 ... ValorN Otro ... Acción1 ... acciónN acciónD 12 : Mostrar (“diciembre”) Diagrama NS Sino Mostrar(“incorrecto”) Fin Según Esta estructura se introduce para expresar de una forma cómoda las diversas condiciones y su relación con cada una de las acciones. La ejecución de esta primitiva de control consiste en la ejecución de aquella acción cuyo valor asociado coincida con el valor observado del indicador que aparece en la primitiva. Para que esté bien construida esta estructura debe ejecutar una y sólo alternativa de entre todas las que se enumeran. Si ( mes = 1) entonces Observaciones: mostrar (“enero”) o Se requiere que el indicador tenga, en el Sino momento de su evaluación, un valor del Si ( mes = 2) entonces conjunto enumerado en la composición. mostrar (“febrero”) o Opcionalmente, se puede indicar una acción Sino por defecto que se realizará en el caso de que Si (mes = ...) el indicador tome un valor distinto de los N entonces valores explícitamente enumerados en la ... composición. Sino ... Esta estructura de decisión puede ser reemplazada Fin si por estructuras de selección dicotómica anidadas. Fin si Fin si Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 21 Algorítmica y Programación I Notar que la comparación que se realiza entre la expresión (mes) y el valor constante es por igual. Estructuras de repetición Se distinguen dos tipos: basadas en condición y basadas en contador. Las basadas en condición pueden ser con pretesteo: la condición se verifica antes de realizar la acción, como en la Iteración mientras, o con postesteo donde se realiza la acción y luego se verifica la condición, como en el caso de la iteración Repetir-hasta. Las iteraciones basadas en contador, permiten repetir acciones un número finito de veces, tal es el caso del “Repetir X veces” o la iteración Desde. Iteración mientras: La condición es evaluada antes de ejecutar por primera vez la acción y ésta se ejecuta mientras la condición sea verdadera. Si el valor lógico de la condición es inicialmente falso, la acción no se ejecutará. Si la condición es siempre cierta, se produce un ciclo infinito y el algoritmo no finalizaría. Alguna de las variables que intervienen en la condición debieran ser modificadas durante la ejecución de las acciones encerradas en el mientras, para dar la posibilidad que alguna vez la condición se haga falsa y el bucle finalice. Sintaxis: Mientras <condición> hacer <acción> Fin Mientras F Mientras C Cond V acción acción Ejemplo: Mientras cant > 0 hacer cant <- cant -1 Fin Mientras Diagrama de flujo Diagrama NS Cuidado con los ciclos infinitos!!!! Si en el ejemplo precedente, cant partiera de un valor negativo, la condición sería falsa y nunca entraría al bucle; si parte con valor positivo, la condición será cierta y en cada iteración disminuye su valor por lo que en un tiempo finito dejará de ser positiva, y el bucle finaliza. Pero si la acción a realizar en lugar de disminuir el valor, lo aumentara, si cant fue inicialmente positiva, se produciría un ciclo infinito y el programa nunca terminaría, dando la apariencia que la máquina está “bloqueada”. Para evitar ciclos infinitos, es necesario pero no suficiente que en las acciones del cuerpo del bucle se modifique alguno de los elementos que compone la condición, pues si no ocurriera, y la condición fue inicialmente cierta, seguiría siéndolo indefinidamente. Iteración Repetir-hasta: La acción se realiza al menos una vez y luego se evalúa la condición, si ésta es falsa, se vuelve a repetir la acción, sino se prosigue con la sentencia siguiente. Si la condición es siempre falsa, se produce un ciclo infinito y el algoritmo no finalizaría. Alguna de las variables que intervienen en la condición debe ser modificada durante la ejecución de las acciones encerradas en el repetir, para dar la posibilidad que alguna vez la condición se haga cierta y el bucle finalice. Sintaxis: Repetir <acción> hasta <condición> Ejemplo: Repetir Ingresar (valor) hasta valor > 0 y valor < 100 Cuidado con los ciclos infinitos!!!! Acción acción F Repetir hasta <cond> Cond Diagrama NS V Diagrama de flujo Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 22 Algorítmica y Programación I Si en el ejemplo precedente, se ingresa un valor entre los límites 0 y 100, la condición se hace cierta y la iteración finaliza; si el valor no cumple con la condición, se vuelve a ingresar un nuevo valor y nuevamente será evaluado y es de esperar que en un tiempo finito se cumplirá la condición y el bucle finaliza. Pero si la acción a realizar en lugar de ingresar un valor, lo mostrara, si inicialmente este valor no cumple la condición, se produciría un ciclo infinito y el programa nunca terminaría, dando la apariencia que la máquina está “bloqueada”. Para evitar ciclos infinitos, es necesario pero no suficiente que en las acciones del cuerpo del bucle se modifique alguno de los elementos que compone la condición, pues si no ocurriera, y la condición fue inicialmente falsa, seguiría siéndolo indefinidamente. Repetir un número fijo de veces: En este caso, la estructura maneja un contador interno y la acción se realiza la cantidad de veces establecida. Sintaxis: Repetir <valorEnteroPositivo> veces <acción> Fin repetir Ejemplo: Repetir 5 veces mostrar(“Muestra 5 veces lo mismo”) Fin repetir Iteración Desde: En este caso, la estructura maneja un contador y tiene como parámetros el valor inicial con que se parte, el valor final y el incremento a realizar en cada paso. Sintaxis: Desde <var> <- <valor_inicial> hasta <valor_final> [con paso = <valor_paso>] hacer <acción> Fin Desde Ejemplo: Muestra cada carácter del nombre y luego lo vuelve hacer en sentido inverso. Desde i <- 1 hasta long(nombre) hacer Mostrar (nombre[i]) Fin Desde Desde i <- long(nombre) hasta 1 con paso = -1 hacer Mostrar (nombre[i]) Fin Desde El número de iteraciones a realizar está dado por las veces que es posible incrementar a la variable que oficia de contador, en el valor del paso sin que supere el valor final, habiendo partido del valor inicial. Existe la posibilidad de realizar 0 iteraciones, cuando el valor inicial es mayor que el final y el paso es positivo. Cuando no se indica paso, el Var <- valInicial incremento es 1. Funciona de la siguiente manera: 1. se inicializa la variable con el valor inicial 2. se compara el valor de la variable con el valor final. Si el paso es positivo, la comparación es por menor o igual, y si es negativo, se compara por mayor o igual. F Var <= valFinal V acción Si el resultado de la comparación da falso, 3. se finaliza el bucle. Si da verdadero, 4. se ejecutan las acciones del bucle. 5. se incrementa la variable en el valor del paso. Var <- var + paso Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 23 Algorítmica y Programación I 6. se vuelve al paso 2 Acción con nombre: La notación algorítmica permite incorporar nuevas acciones y operaciones al repertorio. Para ello debemos proporcionar: Un nuevo nombre para la acción u operación. Una especificación de la acción (qué hace). El cuerpo de la acción, es decir, el conjunto de acciones más elementales que constituyen la acción. De esta forma la notación algorítmica permite agrupar un conjunto de acciones e identificarlas mediante un nombre, de manera que la aparición de dicho nombre en un algoritmo provoca la ejecución de todas las acciones contenidas en el cuerpo de la acción con nombre. Este tipo de acciones se estudiará en detalle cuando veamos Funciones y procedimientos. o o o Combinación de estructuras de control Es posible combinar las estructuras de control de secuenciación, selección e iteración condicional, utilizando para ello la secuenciación, la selección y la iteración condicional. Esto dio origen al concepto de programación estructurada. Un programa estará bien construido si está formado por estructuras de control válidas, de acuerdo con la regla recursiva: Base: la secuenciación, la selección y la iteración condicional son estructuras válidas de control que pueden ser consideradas como enunciados primitivos. Regla: las estructuras de control que se pueden formar combinando de manera válida la secuenciación, selección e iteración condicional también serán válidas. Ejemplos: Decisiones anidadas Si ( a < b) entonces Dif <- b – a Sino Si ( a < c) entonces Dif <- c – a Sino Dif <- c – b Fin Si Fin Si Iteraciones anidadas Mientras ( a < b) hacer b <- b – abs(a) Repetir c <- c + b mostrar(“c”, c ) hasta c > b Fin Mientras Iteraciones anidadas Desde i <- 1 hasta 10 hacer Desde j <- 1 hasta 10 hacer Mostrar(“i=”, i ,“j=”, j) Fin Desde Mostrar (“Fin desde j”) Fin Desde Mostrar (“Fin desde i”) Combinación de secuencia, decisión e iteración Mientras ( a < b) hacer Si ( a < c) entonces Si ( a < 0) entonces b <- c – a Sino a <- a – 1 Fin Si Sino a <- c – b b <- b - 1 Fin Si Fin Mientras Especificación de algoritmos Especificación de un algoritmo, proceso o acción es la definición precisa de su efecto. Una posibilidad para especificar el significado de un objeto (proceso, algoritmo o acción) sería utilizar el lenguaje natural, Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 24 Algorítmica y Programación I pero las definiciones de este tipo suelen incorporar ambigüedades. Cuando se busca precisión es preferible utilizar especificaciones formales, para lo cual existen múltiples métodos, uno de ellos es el de la semántica axiomática. Hemos visto que la ejecución de una acción (no trivial) supone un cambio en el estado del sistema. Se puede especificar un proceso describiendo la relación entre el estado inicial y el estado final del mismo. La semántica axiomática consiste en formalizar esta relación mediante el uso de predicados. Un predicado es una proposición lógica, es un enunciado cuyo valor es verdadero o es falso. Precondición: Es el predicado o condición que debe ser cierta antes de ejecutar una acción. Poscondición: Es el predicado o condición que debe ser cierta después de ejecutar una acción. En la precondición se expresan las condiciones que deben cumplir los datos de la acción para garantizar un funcionamiento correcto de la misma, y la poscondición expresa la relación que debe existir entre los datos y los resultados de la acción. Informalmente, el significado de que una acción S cumple la especificación dada por la precondición P y la poscondición Q, es el siguiente: si los datos de la acción S cumplen la precondición P, la acción ha de calcular unos resultados que cumplan la poscondición Q. Observación: Si no se cumple la precondición no se puede garantizar nada acerca de los resultados de la acción. Además, este hecho no influye en la corrección de la acción. Ejemplo: la especificación de una computadora indica que el equipo funciona correctamente en un rango de temperaturas comprendido entre 0 ºC y 40 ºC. Si en algún momento el equipo se pone a funcionar fuera de este rango de temperaturas no se puede garantizar nada sobre si el mismo funciona o no correctamente. Además, bajo estas circunstancias, si el equipo no funciona correctamente no podremos responsabilizar al fabricante del mal funcionamiento del mismo. Un algoritmo S satisface una especificación con precondición P y poscondición Q, denotada {P}S{Q}, si al ser ejecutado sobre unas variables que cumplen P, el algoritmo termina, y lo hace de forma que dichas variables cumplen Q. Ejemplo 1: La especificación: lado, sup: real { lado = L L > 0} superficie cuadrado { sup = L2} {P} S {Q} puede interpretarse como que dada una variable lado que tiene un valor L positivo, al realizar el algoritmo o acción denominado “superficie cuadrado” se obtiene una variable sup que es igual al cuadrado de L. Ejemplo 2: Dadas las siguientes especificaciones, x, y: entero {x=X y=Y} intercambiar {x=Y y=X} {P} S {Q} puede interpretarse como que dada una variable x que tiene un valor X y una variable y con valor Y, al realizar el algoritmo o acción denominado “intercambiar” la variable x queda con el valor Y y la variable y queda con el valor X. Ejemplo 3: La especificación de un algoritmo que determinase si un entero es potencia de 2 podría ser la siguiente: x: entero es_potencia: booleano {x=X} {P} potencia? S { x = X es_potencia = n X = 2n } {Q} puede interpretarse como que dada una variable x que tiene un valor X al realizar el algoritmo “potencia“ la variable x queda con el mismo X y la variable es_potencia toma el valor de la existencia o no de un Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 25 Algorítmica y Programación I número n>=1 tal que X=2n. En esta especificación aparece tanto en la precondición como en la poscondición que x = X con lo que se quiere indicar que el valor de la variable x es el mismo al principio y al final del proceso. Observar que en todos los casos que requerimos denotar que una variable tiene un valor, lo hacemos expresándolo a través de un valor simbólico. ¿Cuál es la diferencia entre la especificación de un algoritmo y un algoritmo? Un algoritmo describe cómo solucionar un problema, ya que el algoritmo no es más que la expresión (en una notación algorítmica) de una sucesión de acciones (no ambiguas, predecibles y que se realizan en un tiempo finito) para resolverlo. Por el contrario, la especificación de un algoritmo (o en general, de una acción) describe qué hace el algoritmo (o acción). Esta descripción se realiza indicando la relación existente entre los datos de la acción (o situación inicial previa a la realización de la acción) y los resultados de la misma (o situación final alcanzada tras la realización de la acción). Es importante resaltar que podemos utilizar un predicado para describir la situación (inicial y final) de un algoritmo porque podemos ver al predicado como un conjunto de estados (situación), y viceversa. Metodología de resolución de problemas Una metodología de resolución de problemas es el diseño TOP DOWN, como medio para desarrollar simultáneamente algoritmos y estructuras de datos. Ambos comienzan como "cajas negras". Cada caja negra especifica qué hace pero no cómo lo hace. A medida que el proceso de resolución avanza, las cajas negras son refinadas gradualmente, hasta que sus acciones no se pueden seguir descomponiendo. El nivel de descomposición al que se llega depende de los conocimientos de quien va a implementar la solución (obviamente, el nivel de detalle al que puede arribar un experto no es el mismo que al que llegaría un novato). Esta metodología, también conocida como diseño descendente, consiste en establecer una serie de niveles de mayor a menor complejidad (arriba-abajo) que den solución al problema y en efectuar una relación entre las etapas de la estructuración, de forma que una etapa jerárquica y su inmediato inferior se relacionen mediante entradas y salidas de información. Es decir que con esta metodología, resolver el problema original se reduce a resolver una serie de problemas más simples o subproblemas. En cada paso del proceso de resolución cada subproblema es refinado, hasta llegar a un punto en el que está compuesto de acciones tan simples que ya no tiene sentido seguir refinando. Podemos decir que en el diseño Top Down vamos de lo general a lo particular. Una regla muy importante en este proceso de resolución de problemas es que ninguna caja negra necesita "saber" cómo otra realiza su tarea, sólo conoce cuál es la tarea. La utilización de la técnica de diseño Top-Down tiene los siguientes objetivos básicos: o Simplificación del problema y de los subprogramas de cada descomposición. o Las diferentes partes del problema pueden ser programadas de modo independiente e incluso por diferentes personas. También permite modificar una parte de la solución sin afectar en forma significativa a las otras partes. o El programa final queda estructurado en forma de bloque o módulos lo que hace más sencilla su lectura y mantenimiento. En contraposición a esta metodología Top Down está la BOTTOM UP. En ésta, vamos de lo particular a lo general. Es decir que se comienza por lo que serían las ramas terminales del árbol y se van juntando las componentes hasta que se llega a obtener la solución del problema original. Gráficamente, podemos verlo como un árbol de la siguiente manera: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 26 Algorítmica y Programación I problema Bottom Up Top Down De los subproblemas al problema Del problema a los subproblemas subproblemas Cuando nos enfrentamos a la resolución de un problema, en general encontramos que no es posible para la mente humana atender todo a la vez. La modularidad es una solución a esto. Qué ventaja provee la modularización? A medida que la complejidad de un problema crece, las tareas de resolución también crecen. La modularización es un medio para disminuir la velocidad de crecimiento de la dificultad. Un primer programa Se desea un programa para calcular el monto bruto, el monto del impuesto y el monto a pagar por la compra de cierta cantidad de unidades de un mismo tipo de producto cuyo costo por unidad es conocido. La compra está sujeta a un impuesto del 18%. Análisis del problema De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: Datos de entrada Número de unidades adquiridas: unidades Costo unitario del producto: costoUnitario Impuesto: 18% unidades Datos de salida Monto bruto: montoBruto Monto del impuesto: montoImpuesto Monto a pagar: montoPago montoBruto costoUnitario montoImpuesto impuesto = 18 montoPago Restricciones Unidades y costo unitario deben ser positivos Observar que el número de unidades adquiridas y el costo unitario del producto son dependientes de cada compra mientras que el porcentaje del impuesto ya tiene valor preestablecido, por lo tanto este último puede tratarse como una constante que no requiere ser ingresada. Diseño del algoritmo Primer diseño descendente En un primer momento el problema puede descomponerse de manera bastante general en obtener datos de entrada, calcular montos y mostrar resultados. Inicio 1. ingresar unidades y costoUnitario válidos 2. cálculo de montos 3. mostrar montoBruto, montoImpuesto, montoPago Fin Primer refinamiento Como las unidades y el costo unitario no pueden ser valores negativos, debiéramos controlar esto antes de calcular los montos. Es decir, debemos asegurarnos que los valores ingresados sean positivos, caso Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 27 Algorítmica y Programación I contrario, no tendría sentido el problema. Esto se puede hacer con una estructura de repetición que siga pidiendo los datos hasta que sean correctos. Luego de un refinamiento en el subproblema Cálculo de montos llegamos al siguiente diseño: Inicio 1. Repetir ingresar unidades y costoUnitario hasta unidades > 0 y costoUnitario > 0 2. Cálculo de montos a. Calcular montoBruto b. Calcular montoImpuesto c. Calcular montoPago 3. mostrar montoBruto, montoImpuesto, montoPago Fin Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 28 Algorítmica y Programación I Segundo refinamiento Finalmente llegamos al algoritmo definitivo: Algoritmo Facturacion Const Impuesto = 18 Var unidades: entero costoUnitario, montoBruto, montoImpuesto, montoPago : puntoFijo(2) Inicio // Ingreso de datos válidos Repetir Mostrar (“Ingrese unidades y costo unitario”) Ingresar (unidades, costoUnitario) hasta unidades > 0 y costoUnitario > 0 // Cálculo de montos montoBruto <- unidades * costoUnitario montoImpuesto <- montoBruto * Impuesto / 100 montoPago <- montoBruto + montoImpuesto // Salida de resultados Mostrar (montoBruto, montoImpuesto, montoPago) Fin Otro programa: variante del problema anterior Se desea un programa que lleve la cuenta de la cantidad de compras realizadas y la sumatoria de montos brutos al final del día, además de calcular e informar para cada compra el monto bruto, el monto del impuesto y el monto a pagar por la compra de cierta cantidad de unidades de un mismo tipo de producto cuyo costo por unidad es conocido. La compra está sujeta a un impuesto del 18%. Análisis del problema Datos de entrada Número de unidades adquiridas: unidades Costo unitario del producto: costoUnitario Impuesto: 18% Datos de salida Monto bruto: montoBruto Monto del impuesto: montoImpuesto Monto a pagar: montoPago Cantidad de compras: cantCompras MontoTotal: montoBrutoTotal montoBruto unidades montoImpuesto costoUnitario montoPago impuesto = 18 montoBrutoTotal cantCompras El número de unidades adquiridas y el costo unitario del producto son dependientes de cada compra mientras que el porcentaje del impuesto ya tiene valor preestablecido, por lo tanto este último puede tratarse como una constante que no requiere ser ingresada. Diseño del algoritmo Primer diseño descendente En un primer momento el problema puede descomponerse de manera bastante general en repetir el proceso de obtener datos de entrada, calcular montos y mostrar resultados de cada compra e ir contando las compras realizadas y acumulando los montos brutos hasta finalizar el día. Luego de este proceso repetitivo, nos queda como tarea, mostrar la cantidad y el monto bruto total. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 29 Algorítmica y Programación I Inicio Repetir ingresar unidades y costoUnitario válidos Cálculo de montos y cantidad de compras mostrar montoBruto, montoImpuesto, montoPago hasta finalizar el día mostrar cantidad y montoBrutoTotal Fin Primer refinamiento Debemos plantearnos cómo interpretaremos que finaliza el día. Una manera podría ser que preguntemos después de cada compra si se trata de la última y en ese caso, finalizamos la repetición. Esto requiere que mostremos un mensaje con la pregunta al usuario, tomemos su respuesta y de acuerdo al valor de ella decidir si continuamos o finalizamos. Otra manera, podría ser que se ingrese un valor especial de cantidad de unidades que signifique que ya no hay más compras. Para este caso, debiera existir un valor que no fuera válido para dato de entrada del problema, como un valor negativo, por ejemplo -1. Podemos informarle al usuario que ingrese la cantidad de unidades o –1 si ya no hay más compras. Si el valor es –1 no se debe hacer ningún cálculo y se debe finalizar el proceso. Para este ejemplo vamos a adoptar un criterio similar al primer caso, le indicaremos después de cada compra que oprima “F” si quiere finalizar o cualquier tecla para continuar. Luego de un refinamiento también en el subproblema Cálculo de montos y cantidad llegamos al siguiente diseño: Inicio Repetir // ingreso de datos válidos Repetir ingresar unidades y costoUnitario hasta unidades > 0 y costoUnitario > 0 //Cálculo de montos y cantidad Calcular montoBruto Calcular montoImpuesto Calcular montoPago Contar compra Acumular montoBruto mostrar (montoBruto, montoImpuesto, montoPago) mostrar (“Oprima F para finalizar o cualquier tecla para terminar”) ingresar (tecla) hasta tecla = “F” mostrar (cantidad, montoBrutoTotal) Fin Segundo refinamiento Contar compra significa sumar 1 a la cantidad hasta el momento, para ello antes tuvo que ser inicializada en cero. Acumular montoBruto significa sumarle el montoBruto recientemente calculado al montoBrutoTotal hasta el momento, para ello antes tuvo que inicializarse en cero. Finalmente llegamos al algoritmo definitivo: Algoritmo FacturacionDiaria Const Impuesto = 18 Var unidades, cantCompras: entero costoUnitario, montoBruto, montoImpuesto, montoPago : puntoFijo(2) tecla: caracter Inicio cantCompras <- 0 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 30 Algorítmica y Programación I montoBrutoTotal <- 0 Repetir // Ingreso de datos válidos con mensajes aclaratorios Repetir Mostrar (“Ingrese unidades y costo unitario”) Ingresar (unidades, costoUnitario) hasta unidades > 0 y costoUnitario > 0 // Cálculo de montos y unidades montoBruto <- unidades * costoUnitario montoImpuesto <- montoBruto * Impuesto / 100 montoPago <- montoBruto + montoImpuesto cantCompras <- cantCompras + 1 montoBrutoTotal <- montoBrutoTotal + montoBruto // Salida de resultados por compra Mostrar (montoBruto, montoImpuesto, montoPago) // Determinar si finaliza el dia o no Mostrar(“Oprima F para finalizar o cualquier tecla para continuar”) Ingresar (tecla) hasta tecla = ‘F’ Mostrar (cantCompras, montoBrutoTotal) Fin Variables de uso particular Contador: es una variable que se utiliza para contar. La variable cantCompras del ejemplo anterior es un contador. Requiere que se la inicialice en cero y luego se utiliza dentro de una estructura de repetición de la forma contador <- contador + valor_cte, donde valor_cte es un incremento o decremento fijo. Acumulador de suma: es una variable que se utiliza para acumular la suma de valores. La variable montoBrutoTotal del ejemplo anterior es un acumulador. Requiere que se la inicialice en cero y luego se utiliza dentro de una estructura de repetición de la forma acumulador <- acumulador + valor, donde valor es un incremento o decremento variable. Toda vez que encontremos una fórmula en la que interviene el símbolo de sumatoria,, utilizaremos un acumulador de suma. Acumulador de producto: es una variable que se utiliza para acumular el producto de valores. Requiere que se la inicialice en uno y luego se utiliza dentro de una estructura de repetición de la forma acumulador <- acumulador * valor, donde valor es un factor variable. Toda vez que encontremos una fórmula en la que interviene el símbolo de productoria, , utilizaremos un acumulador de producto. Bandera: es una variable que sirve para indicar un estado el cual será consultado para tomar o no una decisión. Puede ser de tipo lógico si la consulta es por sí o no, o enumerado si el estado puede tomar más de dos valores. Prueba del algoritmo La depuración es el proceso por el cual descubrimos las causas de los problemas y los resolvemos. Las pruebas deben considerar los detalles de diseño así como en los requerimientos generales. Una manera de probar si el algoritmo funciona o de encontrar errores, es realizando una traza, es decir ejecutando “a mano”, usando datos representativos y anotando los valores que van tomando los objetos, acción tras acción. Todo programa debe ejecutarse muchas veces y con diversas entradas con el fin de detectar posibles errores. Si probamos con todos los valores posibles que pueden tomar los datos de entrada y funciona correctamente, podemos asegurar que el algoritmo es correcto. Si al probar la ejecución con un valor, el algoritmo falla, debemos corregirlo y volver a probar. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 31 Algorítmica y Programación I Ejemplo probar que el siguiente algoritmo almacena y muestra el menor de dos valores ingresados. Cuando son iguales almacena uno de ellos. Algoritmo MuestraMenor Var Nro1, nro2, menor : entero Inicio 1 Mostrar (“Ingrese un par de números”) 2 Ingresar (nro1, nro2) 3 Si nro1 < nro2 entonces 4 menor <- nro1 Sino 5 menor <- nro2 Fin Si 6 Mostrar (menor) Fin Prueba 1: ingresa 10 Traza Nro1 Nro2 1 2 10 12 3 10 12 4 10 12 6 10 12 y 12 menor 10 10 Prueba 2: ingresa 10 Traza Nro1 Nro2 1 2 10 10 3 10 10 5 10 10 6 10 10 y 10 menor 10 10 Prueba 3: ingresa 25 Traza Nro1 Nro2 1 2 25 15 3 25 15 5 25 15 6 25 15 y 15 menor 15 15 Validación de datos de entrada Es de suma importancia que los datos de entrada sean válidos para el problema a resolver. Si deben cumplir algún tipo de condición, esto debe ser controlado antes de ser usado en el cálculo. Es necesario pedirlos una vez, y si son incorrectos volverlos a pedir. El esquema de validación puede ser realizado de las siguientes formas: Mostrar (“ingrese dato válido”) Ingresar (dato) Mientras dato no es válido hacer Mostrar (“dato no válido, reingrese”) Ingresar (dato) Fin Mientras Repetir Mostrar (“ingrese dato válido”) Ingresar (dato) Hasta dato es válido Las condiciones de validez dependerán de cada problema en cuestión y el mensaje a mostrar deberá orientar al usuario para que sepa cuáles son los valores esperados. Hay que tener en cuenta que a pesar que se indique con un mensaje que el dato a ingresar debe cumplir ciertas condiciones, puede ser que se ingrese un dato que no las cumpla, por eso la estructura de repetición se encarga de reingresarlo hasta que sea correcto. El primer esquema permite un mensaje especial para cuando es inválido. El segundo esquema no distingue el primer ingreso (obligatorio) de los que se realizan por no cumplir con la validación. Cómo finalizar la entrada de un conjunto de datos El criterio para finalizar el ingreso de datos lo establece el programador, en función del modelo de solución que esté programando. En general se presentan alguna de las siguientes situaciones: o Se conoce la cantidad de datos a ingresar en tiempo de diseño del algoritmo. o Se conoce la cantidad de datos a ingresar en tiempo de ejecución del algoritmo. o El usuario informa la finalización a través de un valor especial de alguno de los datos de entrada. o El usuario informa la finalización a través de contestar a una pregunta expresa, por ejemplo contesta ‘N’ a la pregunta “Desea continuar?” o Se cumple una cierta condición con un valor calculado del problema. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 32 Algorítmica y Programación I Si se conoce la cantidad de datos a ingresar en tiempo de diseño del algoritmo, el esquema de solución a este proceso repetitivo es utilizar una estructura de repetición del tipo Desde 1 hasta la cantidad de veces (indicada como una constante con nombre). Ejemplo: Mostrar la suma de cien números enteros ingresados por el usuario Algoritmo CienVeces Const Datos de entrada: Tope=100 números a sumar: nro Var i, nro, suma: entero cantidad de números: 100 Inicio Datos de salida: suma Suma<-0 Desde i<-1 hasta Tope hacer Ingresar (nro) suma <- suma + nro Fin Desde Mostrar ( suma) Fin Si se conoce la cantidad de datos a ingresar en tiempo de ejecución del algoritmo, el esquema de solución a este proceso repetitivo es utilizar una estructura de repetición del tipo Desde 1 hasta la cantidad de veces (previamente ingresada). Ejemplo: Mostrar la suma de una cierta cantidad, dada por el usuario, de números enteros ingresados por el usuario. Datos de entrada: cantidad de números a ingresar: tope números a sumar: nro Datos de salida: suma Tener en cuenta que el tope de números a ingresar es conocido por el usuario, por lo tanto primero debemos solicitar este valor y debemos validar que sea positivo. Algoritmo XVeces Var i, nro, suma, tope: entero Inicio suma<-0 Repetir Mostrar (“Ingrese cantidad de números a sumar”) Ingresar (tope) Hasta Tope > 0 Desde i<-1 hasta tope hacer Ingresar (nro) suma <- suma + nro Fin Desde Mostrar (suma) Fin Si el usuario informa la finalización a través de un valor especial de alguno de los datos de entrada, debe existir un valor que no pueda ser utilizado como válido para el cálculo y entonces ser utilizado como indicación de que ya no hay más datos. Esta solución es aplicable si no se conoce la cantidad de datos y hay un dato que puede tomar un valor que sirva para indicar la finalización. Tenemos que tener presente que una variable puede tener valores de un solo tipo, si el dato a ingresar fuera un número, no podemos esperar que ingrese un carácter para indicar la finalización. Si el número debiera ser positivo, podríamos elegir un valor negativo como –1 para indicar la finalización. En cambio, si todos los valores fueran aceptables para el problema en cuestión, no habría forma de aplicar este esquema. El esquema de solución consiste en una estructura de repetición en la que se solicita el dato y se lo valida (puede tomar valores de los casos válidos del problema o el valor adoptado para finalización) Si no es el caso de finalización, realizar las acciones correspondientes a la solución del problema. La estructura de repetición finaliza cuando el dato toma el valor elegido para finalización. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 33 Algorítmica y Programación I Ejemplo: Mostrar la suma de una cierta cantidad de números enteros ingresados por el usuario. Cuando el usuario ingresa el valor cero, finalizar el proceso y mostrar la suma. Datos de entrada: números a sumar: nro valor de nro para terminar = 0 Datos de salida: suma Se puede utilizar una estructura Repetir o una estructura Mientras. Esta última solución resulta más eficiente que la anterior y es la que utilizaremos para esta situación. Algoritmo Veces0r Var nro, suma: entero Inicio suma<-0 Repetir Ingresar (nro) Si nro <> 0 entonces suma <- suma + nro Fin Si Hasta nro = 0 Mostrar ( suma) Fin Algoritmo Veces0m Var nro, suma: entero Inicio suma<-0 Ingresar (nro) Mientras nro <> 0 hacer suma <- suma + nro Ingresar (nro) Fin Mientras Mostrar ( suma) Fin Si el usuario informa la finalización a través de contestar a una pregunta expresa, por ejemplo contesta ‘N’ a la pregunta “Desea continuar?”, se realiza un proceso repetitivo de ingresar el dato y procesarlo, mostrar la pregunta y tomar la respuesta y validarla, hasta que la respuesta signifique No. Ejemplo: Mostrar la suma de una cierta cantidad de números enteros ingresados por el usuario hasta que el usuario no desee continuar. Datos de entrada: números a sumar: nro repuesta del usuario: resp Datos de salida: suma Algoritmo VecesNS Var nro, suma: entero resp: caracter Inicio suma<-0 Repetir Ingresar (nro) suma <- suma + nro Repetir Mostrar (“Desea continuar? S/N”) Ingresar (resp) Hasta resp = ‘n’ o resp = ‘N’ o resp = ‘S’ o resp = ‘s’ Hasta resp = ‘n’ o resp = ‘N’ Mostrar ( suma) Fin Si se debe cumplir una cierta condición con un valor calculado del problema, el esquema de solución consiste en una estructura de repetición del tipo repetir-hasta, en la que se ingresa el dato y se lo procesa. Ejemplo: Mostrar la suma de una cierta cantidad de números enteros ingresados por el usuario. El proceso finaliza cuando la suma supera el valor 1000. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 34 Algorítmica y Programación I Datos de entrada: valor tope para la suma = 1000 números a sumar: nro Datos de salida: suma Algoritmo VecesCond Const ValorTope=1000 Var nro, suma: entero Inicio suma<-0 Repetir Ingresar (nro) suma <- suma + nro Hasta suma > ValorTope Mostrar ( suma) Fin Descomposición de problema: Funciones y Procedimientos En general un problema complejo puede ser resuelto de manera más fácil y eficiente si se divide en problemas más pequeños. Esto implica que el gran problema original será resuelto por medio de varios módulos, cada uno de los cuales se encarga de resolver un subproblema determinado. Esos módulos pueden ser representados por “acciones con nombres” que están asociadas a un subalgoritmo con la misión de resolver un subproblema en particular. La notación algorítmica permite agrupar bajo un nombre (identificador de la acción) a un conjunto de acciones. Este mecanismo permite ampliar el repertorio de acciones disponibles en la notación algorítmica (hasta ahora, compuesto por acciones elementales (asignación, lectura y escritura) y por primitivas de composición (secuencia, decisión y repetición)) mediante la definición de una acción con nombre. La definición de una acción con nombre consiste en expresar convenientemente el subalgoritmo que desempeña la tarea específica requerida a dicha acción (abstracción procedimental). Una vez definida una acción con nombre, un algoritmo puede hacer uso de ésta mediante una llamada o invocación a la misma. La llamada o invocación a una acción con nombre es una acción válida en la notación algorítmica que se EXPRESA mediante el nombre asociado a la acción, y cuyo SIGNIFICADO es la ejecución de las acciones encerradas en su definición. Los subalgoritmos se escriben sólo una vez, luego es posible hacer referencia a ellos, "llamarlos" o “invocarlos” a través del nombre desde diferentes puntos de un pseudocódigo. La principal ventaja es que nos permite reutilización y evita la duplicación de código. Además, los subalgoritmos son independientes entre sí, en el sentido de que se puede escribir y verificar cada módulo en forma separada sin preocuparse por los demás módulos, simplemente asumiendo que los módulos que utiliza hacen lo que tienen que hacer en forma correcta. Por ello, es menos complicado localizar un error y también, cuando se necesita modificarlo por alguna razón (cambiaron las reglas de negocio, se prefiere una solución más eficiente, etc.), la modificación y el testeo se concentra en ese módulo, sin tener que variar o rehacer en todos los lugares donde se hubiera repetido ese código. No existe un criterio fijo para determinar el tamaño, ni muy grandes ni muy pequeños, la idea fundamental es que realicen una única cosa y muy concreta. En todo algoritmo o programa existirá un módulo o programa principal que es al que se transfiere el control cuando comienza la ejecución del programa, y luego desde él, se va llamando al resto de los subprogramas. Un subprograma puede, a su vez, invocar o llamar a otros subprogramas, inclusive puede llamarse a sí mismo (esto se conoce como recursividad y será visto más adelante). Cuando se invoca a un subprograma, se transfiere el control al mismo, el cual se comienza a ejecutar desde el principio, y al finalizar retorna el control al subprograma que lo llamó. No existen limitaciones en cuanto a las acciones que pueda ejecutar un subalgoritmo, puede realizar las mismas acciones que el algoritmo principal: aceptar datos, realizar cálculos, devolver resultados. Los Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 35 Algorítmica y Programación I subalgoritmos pueden recibir valores del algoritmo que los invoca, trabajar con ellos y devolver un resultado al algoritmo invocante. Los módulos o subprogramas reciben diferentes nombres según el lenguaje de programación y según su tipo. Se llaman procedimientos y funciones (Pascal, C), subrutinas (Basic, Fortran), secciones (Cobol). Parámetros de una acción con nombre Una acción con nombre al igual que cualquier otra acción, en general actuará sobre unos datos y así habrá que expresarlo, tanto en su definición como en su invocación. En la definición de una acción, los datos genéricos sobre los que debe actuar la misma se expresarán mediante la lista de parámetros formales. En la invocación de una acción, los datos concretos sobre los que actúa la acción se expresarán mediante la lista de parámetros reales. Los parámetros reales de una invocación se asocian con los parámetros formales de su definición de acuerdo a un criterio posicional, es decir, el 1er parámetro formal se corresponde con el 1er parámetro real, el 2º parámetro formal con el 2º real, y así sucesivamente. Este hecho obliga también a que el número de parámetros coincidan y que el tipo de dato de cada asociación parámetro formal-real también coincidan. Según la relación de valores entre los parámetros formales y reales, se puede hablar de tres tipos de parámetros: Entrada, Salida y Entrada/Salida. Parámetros de Entrada. El parámetro real de la invocación SÍ tiene un valor definido, que además es el valor que tomará inicialmente el correspondiente parámetro formal. Al finalizar la acción NO interesa que las posibles modificaciones del parámetro formal se reflejen sobre el parámetro real asociado. En este caso el parámetro deberá definirse como de entrada. Este tipo de parámetro se puede usar para aportar un dato ya definido a un subalgoritmo para que éste lo utilice. Por ejemplo el tipo de parámetro de la operación primitiva mostrar es de entrada. Parámetros de Salida. El parámetro real de la invocación NO tiene un valor definido. Al finalizar la acción SÍ interesa que las posibles modificaciones del parámetro formal se reflejen sobre el parámetro real asociado. En este caso el parámetro deberá definirse como de salida. Este tipo de parámetro se puede usar para que un subalgoritmo devuelva un dato definido en su interior. Por ejemplo el tipo de parámetro de la operación primitiva ingresar es de salida. Parámetros de Entrada/Salida. El parámetro real de la invocación SÍ tiene un valor definido, que además es el valor que tomará inicialmente el correspondiente parámetro formal. Al finalizar la acción SÍ interesa que las posibles modificaciones del parámetro formal se reflejen sobre el parámetro real asociado. En este caso el parámetro deberá definirse como de entrada/salida. Este tipo de parámetro se puede usar para que un subalgoritmo modifique un dato en su interior que ya tuviera valor previamente. E Podemos ver al subalgoritmo como un módulo que realiza un S Cliente Servidor servicio, es un módulo “servidor”. Los módulos que lo invocan son E/S módulos “clientes”, son los que solicitan y utilizan el servicio. La clasificación de los parámetros Entrada, Salida y Entrada/Salida es vista desde el servidor según le llegue información (entrada), devuelva información (salida) o ambas cosas (entrada-salida). La relación cliente-servidor puede verse como un contrato de servicio donde el cliente deberá cumplir las precondiciones para dicho servicio y el servidor deberá garantizar que se cumplan las poscondiciones. En la lista de parámetros formales se aclarará si es un parámetro de entrada, salida o entrada-salida. Cada lenguaje de programación tiene su forma de hacerlo, por ejemplo en Pascal se precede con la palabra clave VAR a aquellos parámetros que pueden ser de salida o entrada-salida. En el lenguaje Ada se utilizan las palabras in, out e in-out para ese fin. Nosotros utilizaremos E, S o E/S precediendo al parámetro formal o flechas entrantes, salientes o de ambos sentidos para aclarar que es de entrada, salida o entrada-salida. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 36 Algorítmica y Programación I Los argumentos reales correspondientes a parámetros de entrada, deben ser valores constantes o variables inicializadas (no serán modificadas), los correspondientes a entrada-salida deben ser variables inicializadas y los de salida deben ser variables sin importar si está inicializada o no. Tipos de acciones con nombre Los subalgoritmos, también llamados subrutinas o subprogramas, pueden ser dos tipos: Funciones y Procedimientos. Funciones Por Función se entenderá una acción con nombre similar al concepto de función matemática. La mayoría de los lenguajes de programación permiten definir nuevas funciones y utilizarlas de forma similar a las predefinidas. Se define como función a aquella acción con nombre que: o o o tenga 1 o más parámetros exclusivamente de entrada, tenga 1 solo valor exclusivamente de salida y tal que todos los parámetros de entrada sean necesarios y suficientes para determinar el valor de salida Un algoritmo de tipo función es un algoritmo auxiliar (subalgoritmo) que encapsula la forma en que se resuelve el proceso para obtener un resultado, cuyo valor es retornado cuando la función es invocada desde otro algoritmo. Al igual que en las predefinidas, posee un nombre y debe establecerse la cantidad y tipo de parámetros que recibe y el tipo de su resultado. El nombre elegido debe ser significativo, indicar qué hace. La definición de una función es similar a la de cualquier otro algoritmo: Tiene una cabecera, una declaración del ambiente y un cuerpo. La sintaxis de la cabecera es: Funcion <nombre> ( lista de parámetros formales con sus respectivos tipos ): tipo del resultado. Por ejemplo, la cabecera de la función predefinida seno(x) sería: Funcion seno( x: real): real. Los nombres elegidos para los parámetros formales deberán ser significativos para lo que están representando dentro de la función. Es la manera de darle un nombre y poder referirse a los argumentos con que será invocada. Por ejemplo, en y seno(PI) , el parámetro formal x se refiere al argumento actual PI. En el ambiente se detallan las variables o constantes auxiliares que pudieran necesitarse, y en el cuerpo, las sentencias necesarias para resolver el problema. Para poder indicar el valor que asume la función cuando retorna, algunos lenguajes de programación como C y java, utilizan la sentencia return (valor del resultado) y otros como Pascal, la asignación del valor del resultado sobre el nombre de la función: <nombre> valor del resultado. En este caso, el nombre de la función actúa como una variable en el lado izquierdo de la asignación. Nosotros utilizaremos esta última forma. La función debe devolver un valor sea cual sea el camino de ejecución seguido. No se permiten funciones que no devuelvan nada. Cuando se utiliza la acción Return supone la finalización de la ejecución de la función al alcanzar dicha sentencia, devolviendo el resultado de evaluar la expresión que acompaña al return, mientras que cuando se utiliza la asignación, como utilizaremos nosotros, la función finaliza cuando se llega al final. La declaración de una función es equivalente a la definición de una operación: los operandos son los parámetros de entrada y el resultado es el valor devuelto por la función. Por lo tanto una invocación a una función es una expresión válida, y puede formar parte de expresiones más complejas. La llamada a una función, siempre va a formar parte de una expresión, de cualquier expresión en la que en el punto en la que se llama a la función, pudiera ir colocado cualquier valor del tipo de datos que devuelve la función, esto se debe a que el valor que devuelve una función está asociado a su nombre. Qué sucede cuando se invoca a una función? 1. Al hacer la llamada y ceder el control a la función, se asocia (asigna el valor) de cada parámetro real a cada parámetro formal asociado, siempre por orden de aparición y de izquierda a derecha, Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 37 Algorítmica y Programación I por lo que siempre que no coincidan los tipos y el número de parámetros formales y reales, se produce un error. 2. Si todo ha ido bien, se ejecutan las acciones de la función hasta que finalice o lleguemos a una de tipo retorno <valor> que pondrá fin a la ejecución (si el mecanismo es por sentencia retorno) 3. Se le asocia al nombre de la función el valor retornado y se devuelve el control al subprograma que hizo la llamada pero sustituyendo el nombre de la función por el valor devuelto. Ejemplos de definición y uso de función Problema 1: Se desea diseñar una función para resolver la potencia entera de un número real. De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: Datos de entrada Número real del cual se quiere conocer su potencia: base Número entero que representa la potencia a la que se quiere elevar la base: exponente Datos de salida Resultado de la operación: potencia base exponente potencia Cliente Potencia La base y el exponente deberán ser comunicados desde el algoritmo que invoca mediante parámetros de entrada y el resultado de la operación será el valor que deberá tomar la función. Debemos elegir un nombre significativo para esta operación, y la llamaremos potencia. La manera de resolver una potencia, es multiplicando la base por sí misma tantas veces como indica el exponente. Por lo tanto utilizaremos una estructura de repetición y la más adecuada a este caso es el Desde. Si el exponente es negativo, se realiza el procedimiento como si fuera positivo y luego se invierte el resultado. La función queda definida como: Funcion potencia (E base: real,E exponente: entero): real exponente (* calcula la potencia entera base precondición : base = B y exponente = E poscondición: potencia = BE *) Var resultado: real i: entero Inicio resultado <- 1 Desde i <- 1 hasta abs(exponente) hacer resultado <- resultado * base Fin Desde Si (exponente < 0 ) entonces potencia <- 1 / resultado Sino potencia <- resultado Fin Si Fin Funcion Valor final que asume la función Un algoritmo que la utiliza podría tener una sentencia como alguna de las siguientes: Mostrar (potencia(3,2)) volCubo <- potencia(lado,3) En la primera se muestra el cuadrado de tres, base se asocia con el valor 3 y exponente con el valor 2. En la segunda, si lado es una variable real ya inicializada, base se asocia con el valor de lado y exponente con el valor 3. Problema 2: Se desea resolver el subproblema de obtener el factorial de un número. De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: nro factorial Cliente Factorial Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 38 Algorítmica y Programación I Datos de entrada Número del cual se quiere conocer el factorial: nro Datos de salida Resultado de la operación: factorial Sabemos que el factorial no está definido para números negativos, por lo que impondremos como precondición que nro sea mayor o igual que cero. El factorial de cero es uno y el factorial de un número n es el producto de los primeros n números naturales, para ello requerimos de un acumulador de producto. La generación y el producto de los n número naturales se puede realizar con una estructura de repetición Desde La función queda definida como: Funcion factorial (E nro: entero): entero (* calcula el factorial de nro precondición : nro = N y N >= 0 poscondición: factorial = N! *) Var resultado: entero i: entero Inicio resultado <- 1 Desde i <- 1 hasta nro hacer resultado <- resultado * i FinDesde factorial <- resultado FinFuncion Observemos que si el cliente de esta función no respetara la precondición, la función devolvería como resultado el valor 1, el cual es incorrecto. La garantía de que funcione correctamente es que se cumpla con la precondición. Ejemplo de utilización de esta función en otro algoritmo: Algoritmo UsaFactorial Var nro: entero Inicio Repetir Mostrar (“ingrese nro >=0 para calcular factorial y 0 para finalizar”) Ingresar (nro) Si nro > 0 entonces Mostrar ( nro, “! = ”, factorial(nro)) Sino Mostrar( “ingrese nuevamente, el nro no debe ser negativo”) FinSi Hasta nro = 0 Fin Problema 3: Se desea resolver el subproblema de obtener el nombre del mes correspondiente a su orden numérico. De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: Datos de entrada mes Número de mes: mes Datos de salida nombreMes Resultado de la operación: nombreMes Cliente nombreMes El número de mes es un número comprendido entre 1 y 12. Una solución es imponer como precondición que se cumpla esa condición y sólo preocuparnos por los meses correctos. Otra solución, es responder “Mes incorrecto” cuando mes no cumple esa condición, en este último caso, no habría precondición para el valor del mes. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 39 Algorítmica y Programación I La función queda definida como: Funcion nombreMes (E mes: entero): cadena (* devuelve el nombre del mes precondición : mes = M y M >= 1 y M <=12 poscondición: nombreMes {enero, febrero,...,diciembre} y mes>=1 y mes<=12) nombreMes = “” *) *) o Inicio nombreMes <- “” Segun mes hacer 1: nombreMes <- “enero” 2: nombreMes <- “febrero” 3: nombreMes <- “marzo” 4: nombreMes <- “abril” 5: nombreMes <- “mayo” 6: nombreMes <- “junio” 7: nombreMes <- “julio” 8: nombreMes <- “agosto” 9: nombreMes <- “setiembre” 10: nombreMes <- “octubre” 11: nombreMes <- “noviembre” 12: nombreMes <- “diciembre” FinSegun FinFuncion Recordemos que la función debe devolver un valor sea cual sea el camino de ejecución seguido, por eso inicializamos a nombreMes al principio y lo redefinimos dentro de la estructura de decisión de acuerdo con el mes en cuestión. Observemos que si el cliente de esta función no respetara la precondición, la función devolvería como resultado el valor “”, el cual no representa un mes. La garantía de que funcione correctamente es que se cumpla con la precondición. Una alternativa es la siguiente: Funcion nombreMes (E mes: entero): cadena (* devuelve el nombre del mes precondición : mes = M poscondición: (nombreMes {enero,...,diciembre} y mes>=1 y mes<=12) = “mes incorrecto” *) o nombreMes Inicio Segun mes hacer 1: nombreMes <- “enero” 2: nombreMes <- “febrero” 3: nombreMes <- “marzo” 4: nombreMes <- “abril” 5: nombreMes <- “mayo” 6: nombreMes <- “junio” 7: nombreMes <- “julio” 8: nombreMes <- “agosto” 9: nombreMes <- “setiembre” 10: nombreMes <- “octubre” 11: nombreMes <- “noviembre” 12: nombreMes <- “diciembre” Sino nombreMes = “mes incorrecto” FinSegun FinFuncion Problema 4: Se desea resolver el subproblema de obtener un valor entero que se solicita que ingrese el usuario y que cumpla con la condición de pertenecer a un rango determinado. Datos de entrada Rango de validación: tope1 y tope2 Msg tope1 tope2 enteroEnRango Cliente enteroEnRango Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 40 Algorítmica y Programación I Mensaje para el usuario: msg Datos de salida Resultado de la operación: enteroEnRango La función queda definida como: Funcion enteroEnRango (E msg: cadena, E tope1,tope2: entero): entero (* devuelve un valor entero dentro de un rango precondición : msg = M y tope1=T1 y tope2= T2 y T1 <=T2 poscondición: enteroEnRango [T1,T2] *) var nro: entero Inicio Repetir Mostrar (msg ) Ingresar (nro) hasta nro >= tope1 y nro <= tope2 enteroEnRango <- nro FinFuncion Problema 5: Se desea resolver el subproblema de conocer si el usuario confirma aceptar un mensaje. Es decir, se muestra un mensaje al usuario y se le solicita una respuesta por sí o no, devolviendo verdadero o falso, respectivamente. msg Datos de entrada: Mensaje que se solicita confirmación: msg Datos de salida: Resultado de la operación: confirma confirma Cliente confirma La función queda definida como: Funcion confirma (E msg: cadena): logico (* devuelve un valor logico verdadero si confirma precondición : msg = M poscondición: confirma {falso,verdadero} *) o falso, si no. var resp: caracter Inicio Repetir Mostrar (msg, “S/N” ) Ingresar (resp) hasta resp = ‘n’ o resp = ‘N’ o resp = ‘S’ o resp = ‘s’ confirma resp = ‘s’ o resp = ‘S’ Fin Funcion Procedimientos Cualquier otra acción con nombre que no se corresponda con la definición de función se declarará como un procedimiento. La mayoría de los lenguajes de programación permiten poder definir nuevas acciones y utilizarlas de forma similar a las predefinidas. Un algoritmo de tipo procedimiento es un algoritmo auxiliar (subalgoritmo) que encapsula la forma en que se resuelve una operación, proceso o acción. Las operaciones mostrar e ingresar que hemos estado utilizando son procedimientos predefinidos. Al igual que en las funciones el nombre elegido debiera ser significativo, indicar qué hace, y debe establecerse la cantidad y tipo de parámetros que recibe. A diferencia de las funciones, no retornan un valor cuando son invocados por lo tanto no se establece tipo de su resultado. Los procedimientos representan sentencias mientras que las funciones representan valores. La definición de un procedimiento es similar a la de cualquier otro algoritmo: Tiene una cabecera, una declaración del ambiente y un cuerpo. La sintaxis de la cabecera es: Procedimiento <nombre> ( lista de parámetros formales con sus respectivos tipos ). Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 41 Algorítmica y Programación I Por ejemplo, la cabecera del procedimiento línea(x1, y1, x2, y2) que dibuja una línea desde el punto de coordenadas (x1, y1) al punto (x2, y2) sería: Procedimiento línea(E x1, y1, x2, y2: real) Los nombres elegidos para los parámetros formales deberán ser significativos para lo que están representando dentro del procedimiento. Es la manera de darle un nombre y poder referirse a los argumentos con que será invocado. Por ejemplo, en línea(0.5,0.0,1.5,3.1), el parámetro formal x1 se refiere al argumento actual 0.5, y1 a 0.0, x2 a 1.5 e y2 a 3.1 En el ambiente se detallan las variables o constantes auxiliares que pudieran necesitarse, y en el cuerpo, las sentencias necesarias para resolver el problema. No hay sentencia especial ni valor de retorno, se retorna cuando se llega a su fin. Los parámetros pueden ser de entrada (sólo se utiliza su valor), entrada-salida (se utiliza su valor y su dirección para poder modificarlo) o de salida (no interesa su valor inicial, se requiere su dirección para poder asignarle valor). Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 42 Algorítmica y Programación I Diferencias entre funciones y procedimientos: 1. Una función devuelve un único valor y un procedimiento puede devolver 0, 1 o N. 2. Ninguno de los resultados devueltos por el procedimiento se asocian a su nombre como ocurre con la función. 3. Mientras que la llamada a una función forma siempre parte de una expresión, la llamada a un procedimiento es una instrucción. Qué sucede cuando se invoca a un procedimiento? 1. Se cede el control al procedimiento al que se llama y lo primero que se hace al cederle el control es sustituir cada parámetro formal de la definición por el parámetro actual o real de la llamada asociado a él. Esta asociación entre parámetros formales y reales se hace de izquierda a derecha por orden de colocación y para que se pueda producir esta asociación tienen que existir el mismo número de parámetros formales que reales, y además el tipo tiene que coincidir con el del parámetro formal asociado, sino se cumple alguna de estas condiciones hay un error. 2. Si la asociación ha sido correcta comienzan a ejecutarse las instrucciones del procedimiento hasta llegar a la última instrucción. Al finalizar se vuelven a asociar los parámetros formales que devuelven los resultados a los parámetros reales asociados en la llamada, es decir, de esta manera algunos de los parámetros reales de la llamada ya contendrán los resultados del procedimiento. 3. Finalmente se cede el control a la siguiente instrucción a la que se hace la llamada, pero teniendo en cuenta que en esta instrucción y en las siguientes puedo usar los parámetros reales en los que se devolvieron los resultados del procedimiento para trabajar con ellos. Ejemplos de definición y uso de procedimiento Problema 1: Se desea resolver el subproblema de reemplazar en un texto todas las apariciones de un carácter por otro. Por ejemplo, si el texto es “las casas” y se debe reemplazar la “a” por “o”, el texto que queda es “los cosos”. De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: Datos de entrada Car1 Texto a reemplazar: texto Carácter a buscar: car1 Carácter de reemplazo: car2 Datos de salida Car2 texto Cliente Reemplazo Texto final: texto Si bien hay un solo dato de salida, se trata de modificar el dato de entrada. Por lo tanto será un parámetro de entrada-salida y no puede resolverse con una función. No hay precondiciones especiales para este problema más que los parámetros de entrada o de entrada-salida vengan inicializados, porque si el texto fuera vacío, no habría nada para cambiar y devolvería el mismo texto vacío. Como texto, car1 y car2 son datos de entrada, deberán estar inicializadas. Para resolver el problema, se recorre el texto de principio a fin y por cada carácter del texto se lo compara con car1, si hay coincidencia se lo reemplaza por car2. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 43 Algorítmica y Programación I La operación queda definida como: Procedimiento Reemplazo (E/S texto: cadena, E car1,car2: carácter) (* reemplaza en texto precondición: texto = poscondición: texto = encuentra en T2 o (C1 *) todas las apariciones de car1 por car2 T1 y car1= C1 y car2 = C2 T2 y (C1 no se encuentra en T2 o C1 = C2) y (C2 se no se encuentra en T1 y T1 = T2)) Var i: entero Inicio Desde i<-1 hasta long(texto) hacer Si texto[i] = car1 entonces Texto[i] <- car2 FinSi FinDesde FinProc Ejemplo de utilización de este procedimiento en otro algoritmo: Algoritmo UsaReemplazo (* Recibir un texto y mostrarlo luego de reemplazar todas las vocales por guiones *) Const Guion= ‘-’ Observar que: Var texto es el parámetro real asociado al texto: cadena parámetro formal de entrada-salida y es Inicio una variable inicializada (por efecto de Mostrar (“ingrese texto”) la operación ingresar Ingresar (texto) la correspondencia con los parámetros Reemplazo (texto, ‘a’, Guion) de entrada car1 y car2 se realiza con Reemplazo (texto, ‘e’, Guion) parámetros reales constantes (por Reemplazo (texto, ‘i’, Guion) ende, inicializados). Reemplazo (texto, ‘o’, Guion) Reemplazo (texto, ‘u’, Guion) Mostrar (texto) Fin Problema 2: Se desea resolver el subproblema de conocer las veces que se repite un carácter determinado en un texto y el lugar o posición de su última aparición. Si el carácter no se encontrara, la cantidad será nula y la posición cero. De la lectura del problema encontramos los siguientes datos de entrada y datos de salida: texto Datos de entrada Texto donde buscar: texto Carácter a buscar: car1 Datos de salida Cantidad de veces que se encuentra: cant Posición de la última aparición: pos car1 cant pos Cliente averiguaCantPos El algoritmo que lo use deberá suministrar el texto y carácter a buscar y obtendrá como resultados la cantidad y la posición. Se requieren dos parámetros de entrada y dos parámetros de salida, por lo tanto no puede resolverse con una función y deberá hacerse con un procedimiento. No hay precondiciones especiales para este problema, y como texto y car1 son datos de entrada, deberán estar inicializadas. No importa el valor que puedan tener cant y pos, este subalgoritmo les proporcionará el valor. Para resolver el problema, primero se debe inicializar a pos y a cant en cero, luego se recorre el texto de principio a fin y por cada carácter del texto se lo compara con car1, si hay coincidencia se incrementa cant y se guarda su posición en pos. La operación queda definida como: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 44 Algorítmica y Programación I Procedimiento averiguaCantyPos (E texto: cadena, E car1: carácter, S cant, pos: entero) (* Cuenta todas las apariciones de car1 en texto y da la posición de su última aparición precondición: Texto = T y car1 = C poscondición: cant=N y pos=P y ((N>0 y P>0 y P <= long(T) y T[P]=C y C se encuentra en T Nveces) o (N=0 y P=0 y C no se encuentra en T)) *) Var i: entero Inicio cant <-0 pos <- 0 Desde i<-1 hasta long(texto) hacer Si texto[i] = car1 entonces cant <- cant +1 pos <- i FinSi FinDesde FinProc Observar que los parámetros formales de salida (cant y pos) son inicializados en el procedimiento por cualquiera de los caminos que siga el algoritmo. Ejemplo de utilización: Algoritmo UsaAveriguaCantyPos (* Recibir un texto y mostrar la cantidad de espacios en blanco y la posición del último espacio *) Observar que Los parámetros reales, texto y car, que se corresponden con parámetros formales de entrada, están inicializados (texto, por efecto de la operación ingresar y car por ser una constante). los parámetros reales cantidad y posición correspondientes con parámetros formales de salida son variables sin inicializar en el momento de la invocación y quedan inicializadas por el procedimiento, luego se utilizan sus valores. Const car= ‘ ’ Var texto: cadena cantidad, posicion:entero Inicio Mostrar (“ingrese texto”) Ingresar (texto) averiguaCantyPos (texto, car, cantidad, posicion) Si (cantidad > 0) entonces Mostrar (“hay ”, cantidad, “espacios y el último está en ”, posicion) Sino Mostrar(“No hay espacios en blanco”) FinSi Fin Recursión La recursión es una herramienta muy potente en algunas aplicaciones, sobre todo de cálculo. Se basa en expresar el resultado de un problema como operaciones aplicadas sobre una instancia reducida del mismo problema, hasta que se llega a un caso donde el problema queda bien definido. Este concepto es el mismo que se utiliza al demostrar inductivamente un problema. El uso de recursión es particularmente idóneo para la solución de aquellos problemas que pueden definirse en forma natural en términos recursivos. Para que un problema pueda tener solución recursiva es necesario: 1. El problema debe poder descomponerse en subproblemas del mismo tipo. 2. Debe haber uno o más casos en que la solución es directa o trivial. 3. Cada uno de los subproblemas debe ser de grado menor al que pertenece, y debe converger a la solución trivial. Es muy importante que el caso recursivo converja hacia la condición del caso trivial pues si no fuera así, el problema no tiene solución. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 45 Algorítmica y Programación I Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 46 Algorítmica y Programación I Tomemos el siguiente ejemplo de función recursiva: 1 si n=1 S(n-1) + n si n > 1 S(n) = Se puede observar que en la definición de S(n): 1. El problema se descompone en un subproblema del mismo tipo, al calcular S(n-1). 2. Existe un caso en que la solución es directa, cuando n=1. 3. Cada subproblema es de un grado menor y converge a la solución trivial, ya que en cada invocación recursiva n es menor y en un número finito de veces llegará al valor 1. Para computar el resultado de S(n) (en caso que sea mayor que 1), es necesario calcular el resultado de S(n-1) y así sucesivamente hasta llegar al caso base (n = 1). Es necesario que estos casos estén definidos para asegurar que la recursión termine. Cómo se calcula S(3)? S(3) S(3) = (llamada recursiva) S(2)+3 = (llamada recursiva) S(1)+2+3 = (llamada recursiva) (1+2)+3 = (3+3)= 6 (suma) Retorno de valores S(2) Llamada recursiva S(1) ¿Y si la definición de S(n) hubiese sido la siguiente? 1 si n=1 S(n+1) + n si n > 1 S(n) = S(3) = (llamada recursiva) S(4)+3= (llamada recursiva) S(5)+4+3 = (llamada recursiva) y no terminaría nunca !!! Un algoritmo recursivo incluye, por definición, instrucciones que consisten en ejecutar el mismo algoritmo, a las que se denominará “llamadas recursivas”, es decir, llamadas a sí mismo. La escritura de un procedimiento o función recursiva es similar a sus homónimos no-recursivos; sin embargo, para evitar que la recursión continúe indefinidamente, es preciso incluir una condición de terminación. Como primera observación, se hace notar que, en todo algoritmo recursivo ha de haber al menos una acción alternativa. El texto del algoritmo debe incluir invocaciones del propio algoritmo, por ser éste recursivo, pero no se ejecutan en todos los casos pues de lo contrario la recursividad no terminaría. Se distingue pues en esa alternativa dos tipos de ramas: los casos directos o triviales, que no provocan llamadas recursivas, y los casos recursivos o generales. ¿Qué debe tener un método recursivo? Condicional Caso base (no recursivo), en el ejemplo se da en n = 1 Caso recursivo, en el ejemplo s(n-1)+n, que converja o se aproxime hacia la condición del caso base, en el ejemplo n = 1. El algortimo de s(n) queda: Funcion s (E n:entero): entero (* precondicion: n=N y N >= 1 poscondicion: s = S *) Inicio Si n > 1 entonces s <- s(n –1) + n (* rama del caso recursivo *) Sino Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 47 Algorítmica y Programación I s <- 1 Fin Si Fin Funcion (* rama del caso base *) Recursión vs Repetición La recursión puede ser utilizada como una alternativa a la repetición o estructura repetitiva. Por ejemplo, se desea realizar la sumatoria de los números de una lista de enteros. La solución repetitiva consiste en inicializar el resultado en cero y repetir el proceso de sumar un valor al resultado hasta que la lista se termine. Esquemáticamente el algoritmo tiene este aspecto: Suma <- 0 Mientras hay números en la lista hacer Suma <- suma + nro Fin mientras Para la solución recursiva el caso trivial es que la lista esté vacía, y en ese caso el resultado es cero. El caso general o recursivo consiste en sumar el primer elemento de la lista y la sumatoria del resto de elementos, primero + la sumatoria del resto de la lista. Este caso general propone un subproblema de un grado menor que converge a la solución trivial, alcanzándose cuando el resto de la lista es vacía. La definición recursiva queda: 0 si lista es vacía Primero (lista) + Resto (lista) si lista no es vacía Sumatoria (lista) = Donde Primero(lista) devuelve el primer elemento de la lista y Resto(lista) devuelve la lista sin su primer elemento. Ejemplos de problemas recursivos: Factorial: El factorial de un número n no negativo es 1 si el n es cero y es el producto entre n y el factorial (n-1) si n>0. 1 factorial(n) si n = 0 n * factorial(n-1) si n > 0. El caso directo se da cuando el valor a calcular el factorial es cero. Cada subproblema consiste en calcular el factorial de un número menor, y en un número finito de invocaciones el subproblema convergerá al caso directo. Planteo del algoritmo: Datos de entrada Número del cual se quiere conocer el factorial: nro Datos de salida Resultado de la operación: factorial Restricciones Nro >= 0 nro factorial Cliente Funcion FactorialRecursiva (E nro:entero): entero (* precondicion: nro=N y N >= 0 poscondicion: Factorialrecursiva = N! *) Inicio Si nro > 0 entonces FactorialRecursiva <- FactorialRecursiva(nro –1) * nro Sino FactorialRecursiva <- 1 Fin Si Fin Funcion Factorial Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 48 Algorítmica y Programación I Como puede observarse una de las ramas de la alternativa atiende el caso directo o trivial que no provoca llamadas recursivas y la otra, los casos recursivos o generales. FactorialRecursiva(4) = (llamada recursiva) 4 * FactorialRecursiva(3) = (llamada recursiva) 4 * (3 * FactorialRecursiva(2) ) = (llamada recursiva) 4 * (3 * (2 * FactorialRecursiva(1) ) ) = (llamada recursiva) 4 * (3 * (2 * (1 * FactorialRecursiva(0) ) ) = (caso base) = 4 * (3 * (2 * (1 * 1)))) = 24 24 FactorialRecursiva(4)= 4 * FactorialRecursiva(3) = 3 * 6 2 Llamada recursiva FactorialRecursiva(2) = 2 * FactorialRecursiva(1) = 1 * Retorno de valores 1 FactorialRecursiva(0) = 1 1 Potencia de números enteros: Potencia(base,exp) 1 si exp = 0 base * potencia(base,exp-1) si exp > 0 1 / potencia(base,abs(exp)) si exp < 0 Datos de entrada Número entero del cual se quiere conocer su potencia: base Número entero que representa la potencia a la que se quiere elevar la base: exponente Datos de salida Resultado de la operación: potencia Funcion PotenciaRecursiva (E base, exp:entero): real (* precondicion: base=B y exp=E poscondicion: PotenciaRecursiva = BE *) base Inicio exponente Si exp = 0 entonces potencia PotenciaRecursiva <- 1 Sino Cliente Potencia Si exp > 0 entonces PotenciaRecursiva <- PotenciaRecursiva(base,exp –1) * base Sino PotenciaRecursiva <- 1 / PotenciaRecursiva(base, abs(exp)) Fin Si Fin Si Fin Funcion Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 49 Algorítmica y Programación I Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 50 Algorítmica y Programación I Invocación PotenciaRecursiva(3,–2) 1/9 PotenciaRecursiva(3,–2)= 1/ * Llamadas recursivas PotenciaRecursiva(3,2) = 3 * 9 Retorno de valores 3 PotenciaRecursiva (3,1) = 3 * * PotenciaRecursiva (3,0) = 1 1 Los conejos de Fibonacci Cierto matemático italiano de nombre Leonardo de Pisa, pero mejor conocido como Fibonacci, propuso el siguiente problema: Suponga que acabamos de comprar una pareja de conejos adultos. Al cabo de un mes, esa pareja tiene una pareja de conejitos (un conejo y una coneja). Un mes después, la primer pareja tiene otra pareja de conejitos (nuevamente, un conejo y una coneja) y, al mismo tiempo, sus primeros hijos se han vuelto adultos. Así que cada mes que pasa, cada pareja de conejos adultos tiene una pareja de conejitos, y cada pareja de conejos nacida el mes anterior se vuelve adulta. La pregunta es, ¿cuántas parejas de conejos adultos habrá al cabo de n meses? Para resolver este problema, llamemos Fn al número de parejas adultas al cabo de n meses. Si n es al menos 2, entonces Fn es igual a Fn-1 + Fn-2. ¿Por qué? Fn queda en términos de Fn-1 y Fn-2, que a su vez quedan en términos de Fn-2, Fn-3 y Fn-4, que a su vez... ¡Descompusimos el problema en subproblemas del mismo tipo! Ahora salimos del círculo vicioso recordando que al principio había una pareja de conejos adultos, la misma que había al final del primer mes, así que F0 = F1 = 1. ¡los casos bases! Hagamos un ejemplo: F4 = F3 + F2 = (F2 + F1) + (F1 + F0) = ((F1 + F0) + 1) + (1 + 1) = ((1 + 1) + 1 ) + 2 = (2 + 1) + 2 = 3 + 2 = 5. La sucesión de números F0 = 1, F1 = 1, F2 = 2, F3 = 3, F4 = 5, etc. recibe el nombre de sucesión de Fibonacci. Para calcular Fn se requiere: Datos de entrada Número del cual se quiere conocer Fn: nro Datos de salida Resultado de la operación: fibonacci Restricciones Nro >= 0 Funcion Fibonacci (E nro:entero): entero (* precondicion: nro=N y N >= 0 poscondicion: Fibonacci = F *) Inicio Si nro > 1 entonces Fibonacci <- Fibonacci (nro –1) + Fibonacci (nro-2) Sino Fibonacci <- 1 Fin Si Fin Funcion Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 51 Algorítmica y Programación I Las torres de Hanoi Una antigua leyenda dice que en cierto monasterio de Hanoi había tres postes y que en uno de ellos había 64 discos de tamaño decreciente, uno encima de otro y con el mayor hasta abajo. Los monjes del monasterio han estado trabajando sin cesar para llevar los discos desde su poste original hasta algún otro siguiendo una regla sencilla: solamente pueden mover un disco a la vez de un poste a otro, siempre y cuando queda arriba de uno mayor. Llamemos a los postes A, B y C (siendo A el poste original y C el poste al que queremos mover todos los discos). Si no hubiera disco no debo hacer nada (caso trivial). Si solamente hubiera un disco, la tarea hubiera sido muy sencilla: movamos el único disco del poste A al poste C (caso trivial). Si solamente hubieran dos discos, la tarea no hubiera sido mucho más difícil: basta mover el disco pequeño al poste B, después el disco grande al poste C y finalmente el disco pequeño al poste C. Si continuamos de esta manera, pronto nos daremos cuenta que la labor monumental de mover los 64 discos se reduce a la siguiente: movamos los 63 discos más pequeños del poste A al B, movamos el disco más grande del poste A al C y finalmente movamos los 63 discos más pequeños del poste B al C. ¿Cómo hacemos para mover los 63 discos? Lo haremos de la misma forma, excepto que renombrando los postes adecuadamente. De esta manera, hemos dividido la tarea de mover n discos en dos tareas menores. Por lo tanto, si hay discos, primero se mueven n-1 discos del poste izquierdo al poste medio (del A al B), luego se mueve el disco n del poste izquierdo al derecho (del A al C) y finalmente se mueve los n-1 disco del poste medio al derecho (del B al C). Observar que existen dos llamadas recursivas, una que mueve de A a B y otra que mueve de B a C. La acción de mover el El siguiente algoritmo recursivo mueve n discos de un poste original a un poste destino utilizando un tercer poste auxiliar: Procedimiento Hanoi(n, Original, Auxiliar, Destino) Inicio Si n es mayor que 0 entonces Hanoi(n-1, Original, Destino, Auxiliar) Mueve un disco de Original a Destino Hanoi(n-1, Auxiliar, Original, Destino) Fin si Fin Proc Observación Dentro del condicional se produce la recursión. Si n es 0 no hace nada. Por ejemplo, si n es igual a 4, nuestro algoritmo haría los siguientes movimientos (denotamos por AB la operación de mover un disco de A a B, etcétera): AB, AC, BC, AB, CA, CB, AB, AC, BC, BA, CA, BC, AB, AC, BC. Ambito: variables locales y globales ¿Qué es el ámbito de un identificador? El ámbito de un identificador (variables, constantes, funciones, procedimientos) es la parte del programa en la que se conoce y por tanto se puede usar un identificador. Según el ámbito hay 2 tipos de variables, locales y globales: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 52 Algorítmica y Programación I Local: Aquella que está declarada y definida dentro de un subprograma. Su ámbito coincidirá con el ámbito del subprograma en la que este definida. Esto quiere decir que la variable no tiene ningún significado, no se conoce y no se puede acceder a ella desde fuera del subprograma y que tiene una posición de memoria distinta a la de cualquier otra, incluso si es de una variable que tiene el mismo nombre pero que está definida fuera del subprograma. Las variables locales a un subprograma se definen en la parte de la definición de variables del mismo. Los parámetros formales de un subprograma se comportan dentro de él como si fueran también variables locales a él. Globales: Son las que están definidas a nivel del programa, es decir, su ámbito es el programa o algoritmo principal y todos los subprogramas que van junto con él. A esta variable podemos acceder desde cualquiera de los subprogramas y el programa principal, salvo que alguno de esos subprogramas tenga definida una variable local con el mismo nombre que la variable global, en este caso si utilizo el nombre de esa variable me referiré a la local, nunca a la global. Lugar en el que se definen las variables globales: En algunos lenguajes se define en el programa principal, y esa variable será global, en otros lenguajes se definen fuera del programa principal y fuera de cualquier otro subprograma (antes de empezar el programa principal). El problema de usar variables globales es que como todos los subprogramas las pueden modificar, puede ser que haya usos indebidos cuando un subprograma utiliza una variable global sin saber que otro la ha modificado, por esa razón el paso de información entre los subprogramas debiera realizarse sólo por parámetros. PP Var A,B Procedimientos anidados Algunos lenguajes de programación permiten definir procedimientos o funciones dentro de la definición de otro procedimiento o función. A esto se lo llama anidamiento o anidación. Si la anidación de procedimientos está permitida, aparecen otras situaciones a considerar en cuanto al ámbito. Se dice que una variable local se conoce en el procedimiento en el que está definida y en todos los procedimientos anidados que son los que componen el ámbito de dicho procedimiento. PP1 Var A,C PP2 Var x,y PP3 Var x, C Si en alguno de esos procedimientos anidados esté definida otra variable local con el mismo nombre, esta última tiene preferencia, ya que se considera el ámbito más restringido. Variable Ámbito Variable Ámbito A de PP PP x de PP2 PP2 B de PP PP, PP1, PP2, PP3 y de PP2 PP2 A de PP1 PP1, PP2, PP3 x de PP3 PP3 C de PP1 PP1, PP2 C de PP3 PP3 Efectos laterales Un efecto lateral es cualquier modificación que un subprograma (sea función o procedimiento), realiza en elementos situados fuera de él pero sin hacer esas modificaciones a través del paso de parámetros. Los efectos laterales siempre hay que evitarlos porque no somos conscientes de las modificaciones que pudieran realizarse y resulta difícil rastrear los errores. Para evitar los efectos laterales, la comunicación entre subprogramas debiera hacerse sólo a través del paso de parámetro. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 53 Algorítmica y Programación I Los efectos laterales normalmente se producen por el uso de variables globales o variables locales que abarcan varios procedimientos (esto solo es posible si hay anidación de subprogramas). Por lo tanto evitaremos su uso excepto que sea imprescindible. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 54 Algorítmica y Programación I Ejemplo: Var A:entero Algoritmo EJ Var B:entero Inicio B <- 1 A <- 2 PP(B) Mostrar (A) Fin En este ejemplo, A es una variable global que el algoritmo principal la inicializa en 2. Luego de llamar al procedimiento PP con argumento B, A sufrió un cambio de estado, sin embargo, en la lectura del algoritmo principal nada hace suponer que cambiaría. Sucede que el procedimiento PP la modifica accediendo globalmente. Procedimiento PP(E x:entero) Inicio A <- x+2 Fin Proc Estos efectos evitarse. laterales deben Métodos de paso de parámetros Existen distintas formas en que los lenguajes resuelven el paso de parámetros: haciendo copia de valores, pasando la dirección o camino de acceso al parámetro real, a través del nombre. Paso de parámetros por copia: o Por valor. o Por valor-resultado. o Por resultado. La característica fundamental de este método de paso de parámetros es que el parámetro formal siempre se considera que tiene asociada una dirección de memoria en la que está almacenado y que por tanto se comporta igual que una variable local del subprograma en que aparece. En este método lo que importa es el valor del parámetro actual. Por valor: Nos interesa el valor del parámetro actual a la entrada, para ello este valor se copia en la dirección de memoria del parámetro formal asociado. En este caso el parámetro real puede ser una constante, expresión o variable, y nunca se va a usar para devolver resultado a través de él. Si el parámetro actual fuera una variable y la modificásemos dentro del subprograma (algo que no deberíamos hacer), fuera del subprograma no tendría ninguna repercusión esta modificación, es decir, esa variable seguiría valiendo lo mismo en el programa desde el que se hace la llamada después y antes de hacerla. Los lenguajes utilizan este método para implementar el pasaje de parámetros de ENTRADA. Por valor-resultado: En el valor-resultado nos interesa el valor del parámetro actual tanto a la entrada como a la salida de la ejecución del subprograma. Esto quiere decir que si se cambia el valor del parámetro formal, cambiará también el valor de su parámetro real asociado, cosa que no ocurría antes, y esto supone por tanto que ahora el parámetro real tiene que tener asociada obligatoriamente una dirección de memoria, por lo que siempre tendrá que ser una variable (no una constante ni una expresión). Es un de las maneras de implementar el pasaje de parámetros de ENTRADA-SALIDA Por resultado: Nos interesa el valor del parámetro real solamente a la salida o fin de la ejecución del subprograma en que aparece. Esto significa que al hacer la llamada no se copia el valor del parámetro real en el parámetro formal asociado, sin embargo a la salida se copia el valor del parámetro formal en la dirección del parámetro real asociado, esto significa por tanto, que el parámetro real tiene que tener asociada una expresión que tiene que ser una variable (no puede ser una constante o una expresión). Es una manera de implementar el pasaje de parámetros de SALIDA Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 55 Algorítmica y Programación I Paso de parámetros por referencia La característica principal de este tipo de paso de parámetros es que el parámetro formal va a tener también asignada una dirección de memoria en la que se almacena, pero en esa dirección NO SE GUARDA SU VALOR, sino que se almacena la dirección de su parámetro real asociado, es decir, el parámetro formal apunta al parámetro real que tiene asociado y cualquier modificación que se efectúe sobre el parámetro formal tendrá una repercusión directa en el parámetro real asociado ya que lo que modificará será el valor almacenado en la dirección que indica el parámetro formal que es la de su parámetro real asociado. El proceso será por tanto el siguiente: o Al hacer la llamada al procedimiento en el parámetro formal que se pasa por referencia, se va a guardar la dirección del parámetro real asociado para que apunte a él. o Durante la ejecución cualquier referencia al parámetro formal se hará accediendo a la dirección apuntada por dicho parámetro, es decir, accediendo directamente al parámetro real asociado, por lo que cualquier cambio en el parámetro formal afectará directamente al parámetro real asociado. De esta manera habremos pasado el resultado. La mayoría de los lenguajes utiliza este método para implementar el pasaje de parámetros de ENTRADA-SALIDA y de SALIDA. Paso de parámetros por nombre En este caso, el parámetro formal se sustituye literalmente por el parámetro actual asociado. Esta sustitución literal del parámetro formal por el parámetro actual no se produce hasta que no se usa el parámetro formal. El paso de parámetro por nombre es lo que se parece más a la substitución de parámetros en una función matemática. Pasaje de parámetros en los distintos lenguajes En la práctica la mayor parte de los lenguajes utilizan el tipo de paso de parámetro por valor (para parámetros de entrada) y por referencia (para parámetros de entrada-salida o de salida). Los otros dos tipos de paso de parámetros por copia (por resultado y por valor-resultado), no se implementan normalmente porque los efectos son prácticamente iguales que el paso de parámetros por referencia. En Pascal para denotar que se utilizará pasaje por referencia se precede al parámetro formal con la palabra reservada Var mientras que el pasaje por valor no lleva indicación especial. En Visual Basic, las palabras ByVal y ByRef las que se utilizan para distinguir el pasaje. También es factible declarar a una de ellas por defecto y sólo aclarar cuando es la otra. En C, sólo existe el pasaje por valor. El pasaje por referencia debe hacerse trabajando directamente con punteros. En Ada, se indica con las palabras in, out e in-out para indicar por valor, resultado o valor-resultado. En Java, no se requiere distinguirlo: si se trata de un tipo simple es por valor, si se trata de un objeto es por referencia. Estructuras de datos Una estructura de datos es un conjunto de variables (probablemente de distintos tipos) relacionadas entre sí de diversas formas. Existe la posibilidad de definir y utilizar tipos estructurados, entre los que se encuentran los arreglos, registros, cadenas, archivos, etc. Todos los tipos estructurados son “moldes” para variables estructuradas, las cuales pueden contener más de un valor. Los tipos de datos estructurados pueden ser construidos a partir de tipos simples o de otros tipos estructurados, con lo que la variedad es enorme. Pueden clasificarse de acuerdo al tipo de datos que las forman, en homogéneas y heterogéneas. Una estructura de datos es homogénea si todos sus componentes son del mismo tipo. Una estructura de datos es heterogénea si sus componentes son de distinto tipo. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 56 Algorítmica y Programación I Otra clasificación es en función de la cantidad de almacenamiento utilizado por la estructura durante la ejecución del programa, dividiéndose en estáticas y dinámicas. Una estructura de datos se dice estática si la cantidad de elementos que contiene es fija, es decir, la cantidad de memoria no varía durante la ejecución de un programa. Una estructura de datos se dice dinámica si la cantidad de elementos que contiene y por ende, la cantidad de memoria puede variar durante la ejecución de un programa. Los lenguajes de programación brindan un conjunto de constructores de tipo estructurado, con los cuales el usuario puede crear o definir tipos para trabajar con estructuras de datos compuestas que representan los elementos del mundo real. Estos constructores son: o o o o o o Arreglo o Array para construir estructuras homogéneas estáticas accesibles por índice. Cadena o String para construir estructuras homogéneas estáticas de caracteres Registro o Record para construir estructuras heterogéneas estáticas accesibles por nombre de componente. Conjunto o Set para construir estructuras homogéneas estáticas de elementos simples con operaciones de unión, intersección, diferencia y pertenencia. Archivo o File para construir estructuras homogéneas o heterogéneas que crecen dinámicamente en medio externo. Puntero y registros autoreferenciados para construir estructuras dinámicas. Arreglos En ocasiones en un programa necesitamos agrupar variables que almacenen datos de un mismo tipo. Supongamos, por ejemplo, que deseamos registrar la temperatura promedio mensual durante un año en nuestra ciudad. Si utilizáramos los tipos de datos simples, necesitaríamos declarar 12 variables reales distintas, una por cada mes, para almacenar dicha información. Sería útil poder contar con alguna estructura que nos permita agrupar estos datos bajo un mismo nombre, y acceder individualmente a cada uno de ellos. Esta estructura se puede crear con un constructor de tipo denominado arreglo. Un arreglo es un conjunto finito y homogéneo de celdas direccionables. Se puede acceder directamente a cada una mediante su posición, la cual es determinada por el o los índices. En otras palabras, es una estructura de datos indexada, estática y homogénea. Los arreglos se clasifican en dos grupos: o Unidimensionales (llamados también vectores): poseen una única dimensión, es decir, cada posición es accesible mediante un único índice. En nuestro ejemplo de las temperaturas promedio, necesitaríamos indicar sólo el mes para obtener la temperatura promedio del mismo. o Multidimensionales (por ejemplo, las matrices): poseen más de una dimensión, cada elemento dentro de ellos se direcciona mediante dos o más índices. Las matrices son un caso particular de los arreglos multidimensionales, pues poseen 2 dimensiones. Ejemplo: se requiere registrar las temperaturas promedio mensuales de las ciudades capitales de provincias en los últimos 10 años. En este caso se requiere utilizar un arreglo de 3 dimensiones una para el mes, otra para el año y otra para la ciudad. Para definir un tipo de arreglo, debemos dar un nombre de tipo y asociarlo con la definición adecuada, la cual consiste en indicar el o los rangos de índice y el tipo de dato, según la siguiente plantilla: <nombreTipo> = arreglo [<rango>] de <tipo> El rango puede ser cualquier subrango de un tipo enumerado. El tipo del elemento debe ser de un tipo básico o de un tipo previamente definido (simple o estructurado). Algunos lenguajes utilizan la cantidad de elementos en lugar del rango, y en ese caso el índice solamente puede ser entero en un valor que va de 1 a esa cantidad (Lenguaje Basic), o desde 0 al tope –1 (Lenguaje C) Ejemplos: Const Max = 10 Tipo // definir tLista como arreglo de 10 enteros tLista= arreglo [1..Max] de enteros // definir tMatriz como una matriz cuadrada de 10 x 10 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 57 Algorítmica y Programación I tMatriz= arreglo [1..Max, 1..Max] de real // definir un tipo que sirva para contadores de caracteres mayusculas (* si utilizamos un indice de tipo carácter nos será más facil identificar el contador de cada carácter *) tCont= arreglo[ ‘A’.. ‘Z’] de enteros Var Lista: tLista Matriz: tMatriz ContadorLetra: tCont Si las variables ya estuvieran inicializadas, para acceder a un elemento de ellas, debemos acompañar al nombre de la variable con la indicación del índice o de los índices que representan la posición del dato. Lista 10 20 [1] 25 [2] 45 [3] 58 [4] Lista[1] = 10 y Lista[9] Matriz 12 [5] 21 [6] 36 [7] 98 [8] 50 [9] [10] > Lista[10] 1.0 2.0 2.5 4.5 5.8 1.2 2.1 3.6 9.8 5.0 [1] 5.0 1.2 2.1 0.4 0.6 0.8 1.0 1.2 1.6 2.0 [2] 7.0 1.5 2.0 1.4 1.5 2.1 3.2 8.5 2.2 1.0 [3] 9.0 1.4 5.0 0.4 0.8 1.2 0.4 1.2 0 1.2 [4] 8.0 1.9 5.4 5.1 2.2 0.2 1.1 1.6 1.4 1.4 [5] 4.5 1.7 2.1 2.2 3.5 2.1 1.6 1.2 1.1 2.1 [6] 5.6 1.0 1.3 1.5 5.1 6.2 2.5 3.2 4.2 2.1 [7] -1.1 1.0 -2.1 2.0 3.1 3.2 2.3 2.5 2.8 2.9 [8] 2.1 0 0.2 0.5 1.5 1.8 1.6 1.1 2.3 2.0 [9] 0 2.3 2.2 8.1 -9.1 -5.0 2.3 2.4 7.1 2.8 [10] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] Matriz[1,1] = 1.0 y Matriz[3,9] =2.2 ContadorLetra 10 20 25 10 20 2 4 1 5 2 [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] ... ... 5 0 12 5 0 0 0 1 [S] [T] [U] [V] [W] [X] [Y] [Z] ContadorLetra[‘A’] = 10 y ContadorLetra[‘Z’] = 1 Recorridos en el arreglo Cuando se desea recorrer un arreglo en forma completa, la mejor manera de hacerlo es utilizando una estructura de repetición del tipo Desde por cada índice del arreglo. Si el arreglo es multidimensional, las estructuras de repetición se anidarán y dependiendo del orden de anidamiento será el orden en que se visitarán las celdas. Si deseamos que Matriz sea recorrida por filas, el primer Desde se asociará con el primer índice (que indica la fila). Si deseamos que sea recorrida por columnas el "Desde" más exterior será el que se asociará con el segundo índice. Por ejemplo, el siguiente fragmento de código mostrará los valores de la primer fila primero, luego los de la segunda, y así hasta finalizar. Desde fila <- 1 hasta Max hacer Desde columna <-1 hasta Max hacer Mostrar (Matriz [fila,columna]) Fin Desde Fin Desde En cambio, el siguiente fragmento de código mostrará los valores de la primer columna primero, luego los de la segunda, y así hasta finalizar. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 58 Algorítmica y Programación I Desde columna <- 1 hasta Max hacer Desde fila <-1 hasta Max hacer Mostrar (Matriz [fila,columna]) Fin Desde Fin Desde Accesos aleatorios en el arreglo Cuando se desea acceder en forma aleatoria a posiciones del arreglo según el pedido del usuario, debemos solicitar la posición y validar que esté dentro del rango del índice antes de acceder al elemento. Si se trata de Matriz, debemos pedir el valor para cada índice y validar a cada uno. Definición del rango del índice En general el índice será numérico y comenzará desde 1, pero dependiendo del problema puede ser conveniente utilizar otro rango o tipo de dato para el índice. Cuando no se sabe cuántos elementos se van a requerir, se debe pensar en definir un arreglo lo suficientemente grande como para contener la cantidad máxima que pueden presentarse. Luego, en tiempo de ejecución se utilizará sólo una porción del arreglo si no se presentan todos los datos. Por ejemplo, deseo almacenar las notas del curso de Algorítmica y Programación, a razón de una por alumno. Como pretendo que el programa sirva a lo largo de los años y la cantidad de alumnos es variable año tras año, defino un arreglo con el máximo de alumnos que preveo puedo tener, por ejemplo 200. En un año en particular a lo mejor utilizo 100 y en otro año utilizo 120. En este caso, además del tope real del arreglo utilizado para su definición, debo llevar una variable con la cantidad real utilizada, la cual será menor o igual al tope, y servirá de extremo superior en el recorrido del arreglo. Cualquier acceso a una celda del mismo deberá estar entre 1 y esa cantidad. Llenado de datos de un arreglo El criterio para el ingreso de datos lo establece el programador, en función del modelo que esté programando. En general se preferirá un llenado secuencial, con una estructura de repetición Desde que pida los datos uno tras otro. Pero si por el tipo de problema se establece como más conveniente un recorrido aleatorio, debemos poder reconocer qué celdas han sido llenadas y cuáles no, para advertirle al usuario cuando intenta volver a llenar una celda que tiene datos o para recordarle que hay celdas sin datos. Para poder reconocer las celdas sin datos cargados, previo al proceso de llenado aleatorio, el arreglo debe ser inicializado con un valor determinado que sirva de indicador o que sea el valor por defecto que debe quedar en las celdas sin llenar. Ejemplo: Tengo que pasar las notas de los exámenes a un arreglo de notas donde cada índice representa un alumno en el orden que figura en la lista alfabética. Si los exámenes los tengo ordenados como la lista, la mejor manera de llenar las notas es con llenado secuencial: el algoritmo impone el índice de la celda que se va a llenar, utilizando como índice la variable contador de la estructura de repetición Desde. Si los exámenes no los tengo ordenados, sería más fácil para el usuario llenarlo en forma aleatoria, eligiendo el propio usuario la celda que va a llenar. En este caso puedo inicializar previamente el arreglo con –1, que no es un valor de nota posible, y si al finalizar la carga aún quedan celdas con –1 significará que falta pasar la nota de ese alumno, si en cambio, pretendo pasar la nota del alumno 5 y en la celda 5 hay un valor distinto de –1, significa que ya ingresé su nota, y el programa debiera advertirlo. Algoritmos básicos con arreglos Los ejemplos siguientes representan operaciones comunes que se presentan en problemas que requieren estructuras de datos como arreglos. Tomaremos como ejemplo al tipo tLista definido anteriormente como un arreglo de [1.. Max] de enteros, para arreglos unidimensionales y el tipo tMatriz definido anteriormente como un arreglo de [1.. Max,1..Max] de real, para arreglos multidimensionales. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 59 Algorítmica y Programación I Inicialización de un arreglo unidimensional Se trata de una operación que pretende llenar un rango de celdas consecutivas del arreglo con un valor fijo, recibido como parámetro. Primero, ultimo Datos de entrada Rango: primero y último del rango de celdas Valor de inicialización: val Datos de salida: Arreglo de datos: Lista val lista Cliente Inicializar Si bien hay un solo parámetro de salida, esta operación debe realizarse con un procedimiento porque las funciones sólo pueden devolver datos simples o cadenas. Entonces definiremos un procedimiento Inicializar con estos parámetros. Procedimiento Inicializar(S Lista:tLista, E primero, ultimo: entero, E val:entero) (*Precondición: primero=P y ultimo=F y val=V y P<=F y [P,F] rango(tLista) *) (*Poscondición: Lista= {Li} y Li = V i [P,F]*) Var i: entero Inicio Desde i<-primero hasta ultimo hacer Lista[i] <- val Fin Desde Fin Proc Llenado secuencial de un arreglo unidimensional Se trata de una operación que pretende llenar un rango de celdas consecutivas del arreglo con un valor ingresado por el usuario. Primero, Datos de entrada Rango: primero y fin del rango de celdas Datos de salida: Arreglo de datos: Lista ultimo Lista Cliente LlenarListaSec Si bien hay un solo parámetro de salida, esta operación debe realizarse con un procedimiento porque las funciones sólo pueden devolver datos simples o cadenas. Entonces definiremos un procedimiento LlenarListaSec con estos parámetros. Procedimiento LlenarListaSec(S Lista:tLista, E primero, ultimo: entero) (*Precondición: primero=P y ultimo=F y P<=F y [P,F] rango(tLista) *) (*Poscondición: Lista = {Li} i [P,F] *) Var i: entero Inicio Desde i<-primero hasta ultimo hacer Mostrar(“Ingresar valor”, i , “:”) Ingresar (Lista[i]) Fin Desde Fin Proc Consulta aleatoria de un arreglo unidimensional Se trata de una operación que pretende mostrar celdas a pedido del usuario dentro de un rango de celdas del arreglo. Primero, ultimo Datos de entrada Rango: primero y fin del rango de celdas Lista Cliente MostrarListaAlea Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 60 Algorítmica y Programación I Arreglo de datos: Lista No hay datos de salida, entonces definiremos un procedimiento MostrarListaAlea con estos parámetros. Procedimiento MostrarListaAlea(E Lista:tLista, E primero, ultimo: entero) (*Precondición: primero=P y ultimo=F y val=V y P<=F y [P,F] rango(tLista) y Lista = {Li} i [P,F] *) (*Poscondición: se mantienen las precondiciones *) Var pos: entero Inicio Repetir Pos <- enteroEnRango(“Ingrese posición”,primero,ultimo) Mostrar (Lista[pos]) Hasta confirma(“finaliza?”) Fin Proc Verificar si un arreglo unidimensional está ordenado en forma ascendente Se trata de una operación que pretende verificar que cada elemento que está en una posición i de la lista es menor o igual al que está en la posición i+1. Para tener la oportunidad de que el algoritmo sirva para verificar una sublista, se requiere conocer el principio y fin del rango de celdas. Primero, ultimo Datos de entrada Rango: primero y fin del rango de celdas a verificar Arreglo de datos: Lista Datos de salida: Resultado de la operación: estáOrdenado (lógico) Lista estáOrdenado Cliente estáOrdenado Como se requiere un único dato de salida de tipo simple que puede ser calculado en función de los datos de entrada, esta operación conviene realizarse como una función. Supondremos que la lista está ordenada y si en el proceso de verificación de que cada elemento i sea menor o igual al respectivo i+1 fallara, cortamos la verificación y devolvemos el valor falso. Funcion estáOrdenado(E lista:tLista, E primero, ultimo: entero): logico (*Precondición: primero=P y ultimo=F y y P<=F y [P,F] rango(tLista) y Lista = {Li} i [P,F] *) Poscondición: estáOrdenado = verdadero si Li<=Li+1 i [P,F-1] y falso si no*) Var i: entero verifica: logico Inicio verifica <- verdadero i <- primero Mientras i< ultimo y verifica hacer Si lista[i] > lista[i+1] entonces verifica <- falso Fin Si i <- i + 1 Fin Mientras estáOrdenado <- verifica Fin Funcion Llenado secuencial de un arreglo bidimensional Se trata de una operación que pretende llenar un rango de celdas consecutivas del arreglo bidimensional con un valor ingresado por el usuario. Debemos distinguir si queremos realizarlo por filas o por columnas, cambiará el orden en que anidamos las estructuras de repetición. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 61 Algorítmica y Programación I Datos de entrada Rango: primero y fin del rango de filas y primero y fin del rango de columnas Datos de salida: Arreglo de datos: Matriz Si bien hay un solo parámetro de salida, esta operación debe realizarse con un procedimiento porque las funciones sólo pueden devolver datos simples o cadenas. Entonces definiremos un procedimiento LlenarMatrizSecF para llenarla por filas y LlenarMatrizSecC para llenarla por columnas. Por razones de simplicidad, supondremos que el principio de las filas y de las columnas siempre es 1, por lo que al ser una constante conocida en tiempo de diseño, no requerimos que sea pasada como parámetro, de no hacer esta suposición, deberemos tener dos parámetros adicionales que informen el principio del rango de filas y el principio del rango de columnas. Procedimiento LlenarMatrizSecF(S matriz:tMatriz, E finF, finC: entero) (*Precondición: finF=F1 y finC=F2 y [1,F1] rango1(tMatriz) y [1,F2] rango2(tMatriz)*) (*Poscondición: matriz={ Mij } i [1,F1] y j [1,F2]*) Var finF, finC i,j: entero Inicio matriz Desde i<-1 hasta finF hacer Cliente LlenarMatrizSecF Desde j<-1 hasta finC hacer Mostrar (“Ingrese elemento ”,i, “,”,j, “:”) Ingresar (matriz[i,j]) Fin Desde Fin Desde Fin Proc Procedimiento LlenarMatrizSecC(S matriz:tMatriz, E finF, finC: entero) (*Precondición: finF=F1 y finC=F2 y [1,F1] rango1(tMatriz) y [1,F2] rango2(tMatriz)*) (*Poscondición: matriz={ Mij } i [1,F1] y j [1,F2]*) Var finF, finC i,j: entero Inicio matriz Desde j<-1 hasta finC hacer Cliente LlenarMatrizSecC Desde i<-1 hasta finF hacer Mostrar (“Ingrese elemento ”,i, “,”,j, “:”) Ingresar (matriz[i,j]) Fin Desde Fin Desde Fin Proc Consulta aleatoria de un arreglo bidimensional Se trata de una operación que pretende mostrar celdas a pedido del usuario dentro de una matriz. Datos de entrada Rango: principio y fin del rango de filas y principio y fin del rango de columnas Arreglo de datos: Matriz No hay datos de salida, entonces definiremos un procedimiento MostrarMatrizAlea con estos parámetros. Por razones de simplicidad, supondremos que el principio de las filas y de las columnas siempre es 1, por lo que al ser una constante conocida en tiempo de diseño, no requerimos que sea pasada como parámetro, de no hacer esta suposición, deberemos tener dos parámetros adicionales que informen el principio del rango de filas y el principio del rango de columnas. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 62 Algorítmica y Programación I Procedimiento MostrarMatrizAlea(E matriz:tMatriz, E finF, finC: entero) (*Precondición: finF=F1 y finC=F2 y [1,F1] rango1(tMatriz) y [1,F2] rango2(tMatriz)y matriz={ Mij } i [1,F1] y j [1,F2]*) (*Poscondición: se mantienen las precondiciones *) Var finF, finC posF, posC: entero Matriz Inicio Cliente MostrarMatrizAlea Repetir posF <- enteroEnRango(“Ingrese fila”,1,finF) posC <- enteroEnRango(“Ingrese columna”,1,finC) Mostrar (matriz[posF,posC]) Hasta confirma(“finaliza?”) Fin Proc Verificar si una matriz cuadrada es simétrica Se trata de una operación que pretende verificar que los elementos que están en posiciones i, j debajo de la diagonal principal, son iguales a los que están en posiciones j,i sobre la diagonal principal. La diagonal principal de una matriz cuadrada es aquella cuyos índices son iguales. Para tener la oportunidad de que el algoritmo sirva para verificar una submatriz, se requiere conocer el principio y fin del rango de celdas. Si asumiéramos que la submatriz siempre comienza desde la primer fila y columna, el principio sería una constante y no se requeriría como parámetro. Dada que es una matriz cuadrada, el rango de filas coincide con el rango de columnas. Datos de entrada Rango: principio y fin del rango de filas/columnas a verificar Arreglo de datos: Matriz Datos de salida: Resultado de la operación: esSimétrica (lógico) Primero, ultimo Matriz esSimetrica Cliente EsSimetrica Como se requiere un único dato de salida de tipo simple que puede ser calculado en función de los datos de entrada, esta operación conviene realizarse como una función. Supondremos que la matriz es simétrica y si en el proceso de verificación de que cada elemento i,j sea igual al respectivo j,i fallara, cortamos la verificación y devolvemos el valor falso. Funcion EsSimetrica(E matriz:tMatriz, E princ, ultimo: entero): logico Var Precondición: princ=P y ultimo=F y P<=F y [P,F] i, j: entero rango1(tMatriz) y rango1(tMatriz) = rango2(tMatriz) verifica: logico y matriz={Mij} i [P,F] y j [P,F]) Poscondición: (EsSimetrica = verdadero y (Mij=Mji Inicio verifica <- verdadero i [P,F] y j [P,F]) ) o EsSimetrica=falso i <- princ +1 Mientras i<= ultimo y verifica hacer j <- princ Mientras j < i y verifica hacer Si matriz[i,j] <> matriz[j,i] entonces verifica <- falso Fin Si j <- j + 1 Fin Mientras i <- i + 1 Fin Mientras EsSimetrica <- verifica Fin Func Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 63 Algorítmica y Programación I Ejemplos de problemas que requiere la utilización de arreglos. Problema 1: Recibir una lista de 100 valores enteros y mostrarla en orden inverso al ingresado. Para poder mostrar los valores en orden inverso, primero debo tenerlos almacenados y la forma de hacerlo es utilizando una estructura de datos de tipo arreglo unidimensional. Este algoritmo se puede resolver con dos procedimientos: uno para el ingreso secuencial y otro para mostrarlo en orden inverso. Para este último caso se puede utilizar la estructura de repetición desde el fin al principio con paso –1. Problema 2: Recibir una lista de 100 valores enteros y mostrarla ordenada en orden numérico ascendente. Otra vez, para poder mostrar los valores en orden ascendente, primero debo tenerlos almacenados utilizando una estructura de datos de tipo arreglo unidimensional. Este algoritmo se puede resolver con tres procedimientos: uno para el ingreso secuencial, otro para producir el ordenamiento y otro para mostrarlo. Problema 3: Se desea un programa que reciba un texto y muestre la cantidad de veces que se repite cada carácter en el texto. El texto será almacenado en una variable de tipo cadena pero para almacenar la cantidad de apariciones de cada carácter, necesitaremos un contador para cada uno. Por ello, la solución es tener un arreglo de contadores, uno por cada carácter posible, inicializado en cero. Recorriendo una vez la cadena, incrementaremos el contador correspondiente y al final sabremos cuánto se repitió cada carácter. Aquellos contadores que aún están en cero, será porque el carácter que representan no se encontraba en el texto. Problema 4: Se desea un programa que permita realizar producto de matrices. En este problema se requiere contar con un tipo de arreglo bidimensional lo suficientemente grande para contener cualquier matriz y tres variables de este tipo para almacenar las matrices que se multiplicarán y a la matriz resultado. Las dimensiones de las matrices serán conocidas en tiempo de ejecución, por lo tanto se requieren variables adicionales para las mismas. Dado que el producto de matrices requiere que las dimensiones de las matrices a multiplicar cumplan la condición que la matriz primera tenga tantas columnas como filas tiene la segunda, deberá verificarse esta situación antes de intentar ingresarlas para multiplicarlas. En este algoritmo se requerirán al menos tres procedimientos: uno para el ingreso secuencial el cual será invocado dos veces, una para cada matriz a multiplicar, otro para producir el producto y otro para mostrar el resultado, además de haber ingresado previamente las dimensiones de las matrices y verificar que cumplan la condición requerida. Cadenas de caracteres Una cadena es un arreglo de caracteres que se caracteriza por tener una longitud. La longitud de una cadena es el número de caracteres que posee. Cuando se define una cadena se establece un máximo de capacidad, pero al asignarle un valor puede ser que no se ocupe toda esa capacidad, la función longitud devuelve la cantidad realmente ocupada. Puede ser utilizado como un tipo básico, definiendo variables directamente de tipo cadena, en este caso, utiliza la máxima capacidad establecida en el lenguaje, la cual asumiremos que es de 255 caracteres. Si queremos crear tipos de cadenas con menor capacidad, las definiremos en la sección tipo, dando un nombre y limitando la cantidad, según la siguiente plantilla: <nombreTipo> = cadena [<tamaño>] Ejemplo: para definir un tipo de cadena para almacenar direcciones de hasta 50 caracteres: tDirección = cadena[50] Operaciones Existen varias operaciones predefinidas para su uso con cadenas. Concatenación: Dos cadenas se concatenan cuando se obtiene una tercera formada por los caracteres de la primera y los de la segunda. El operador de concatenación es el símbolo +. También se puede realizar con la función concatena(cad1, cad2). Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 64 Algorítmica y Programación I Ejemplo: Texto <- "Hoy " + "consulto" + " dudas" Texto2 <- concatena(texto, “ y luego estudio”) Texto será una variable de tipo cadena que queda inicializada con “Hoy consulto dudas” y Texto2 quedará con el valor “Hoy consulto dudas y luego estudio” Largo de una cadena: Long(cadena). Devuelve la longitud de la cadena. Ejemplos: Long("Juan") -> 4 Long("Día del estudiante") -> 18 Conversión de un número a una cadena: str(número). Devuelve una cadena que contiene el número que le fue pasado como parámetro a la función, pero en formato alfanumérico. Ejemplos: str(108) -> "108" str(27.05) -> "27.05" Conversión de una cadena a número: val(cadena). Devuelve el valor numérico de la cadena. Si la cadena contiene caracteres no numéricos, la función devuelve 0. Ejemplo: val("8.73") -> 8.73 val("23") -> 23 val("2x3") -> 0 Extraer una subcadena: Subcadena(cadena,posición,long) Devuelve una cadena formada por “long” cantidad de caracteres tomados de “cadena” a partir de “posición”. Si la longitud es mayor de la cantidad de caracteres que restan, devuelve los que puede. Ejemplo: Subcadena("Programación",1,8) -> "Programa" Subcadena("Estado del arte",7,3) -> "del" Buscar una cadena dentro de otra: Buscar(cadenaFuente,cadena). Devuelve la posición a partir de la cual se encuentra “cadena” dentro de “cadenaFuente”. Si “cadena” no es una subcadena de “cadenaFuente”, la función devuelve el valor 0. Ejemplo: Buscar("Algoritmica y programación","programa") -> 15 Buscar("Algoritmica y programación","o") -> 4 Buscar("Algoritmica y programación","lenguaje") -> 0 Reemplazar una cadena dentro de otra: Reemplazar(cadenaFuente, cadBusca, cadReemplazo). Devuelve la cadena que resulta de sustituir todas las apariciones de cadBusca en cadenaFuente por cadReemplazo. Ejemplo: Reemplazar(“Estudio poco”,”poco”, “mucho”) -> “Estudio mucho” Reemplazar (“Algoritmica”, “lenguaje”, “programa") -> “Algoritmica” Insertar una cadena dentro de otra a partir de una cierta posición: insertar( cadenaFuente, posición, cadena). Devuelve la cadena que resulta de insertar “cadena” a partir de “posición” en “cadenaFuente”. Ejemplo: Insertar( “Luis Rodriguez”,6,"Maria ") -> “Luis Maria Rodriguez” Comparación de Cadenas: Las cadenas pueden compararse haciendo uso de los operadores de comparación. Internamente se comparan posición a posición. Dos cadenas serán iguales si todos los caracteres coinciden y tienen igual longitud. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 65 Algorítmica y Programación I Registros Un registro es una estructura de datos que permite agrupar valores de tipo no homogéneo, los cuales pueden identificarse a través del nombre del registro y el nombre del elemento en cuestión. Por ejemplo si quisiéramos agrupar la información de un empleado, como el nro. de legajo, categoría y sueldo, no podríamos hacerlo mediante un arreglo pues los datos no son homogéneos. En su lugar definimos un registro con la siguiente plantilla: <nombreTipo> = Registro campo1: <tipo> campo2: <tipo> ... campoN: <tipo> Fin registro Para el ejemplo del empleado: tipo tEmpleado = Registro legajo: entero categoría: caracter Legajo Categoria sueldo sueldo: real Empleado Fin registro var Empleado : tEmpleado Para acceder a un elemento en particular, lo hacemos refiriéndonos con la variable de tipo registro, seguida de un punto y el nombre del campo: Empleado.Legajo <- 125 Empleado.categoria <- ‘A’ Empleado.sueldo <- 1807.5 125 A 1807.50 Legajo Categorí a sueldo Empleado El tipo de datos de los elementos de un registro puede ser cualquier tipo conocido o previamente definido, a excepción de uno de tipo archivo. Un campo puede ser de tipo arreglo previamente definido o de otro tipo registro previamente definido, por ejemplo si nos interesara agrupar los datos de un alumno en una estructura registro con la información de su nombre, matricula, fecha de inscripción y lista de cinco notas: Const Max=5 Tipo tNotas = arreglo [1..Max] de entero tFecha = Registro dia, mes, año: entero Fin registro tNombre = cadena[50] tAlumno = Registro nombre: tNombre matricula: entero fecha: tFecha notas: tNotas Fin registro var alumno : tAlumno Para ingresar los datos del alumno haríamos: Mostrar (“ingrese Nombre”) Ingresar(alumno.nombre) Mostrar (“ingrese matricula”) nombre matricula fecha notas Alumno Juan Perez 1234 nombre 12 | 6 | 04 5 7 9 8 6 dia mes año [1] [2] [3] [4 ] [5] matricula fecha notas Alumno Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 66 Algorítmica y Programación I Ingresar (alumno.matricula) ObtenerFechaValida(“Fecha inscripcion”,alumno.fecha) Desde i <- 1 hasta Max hacer Mostrar (“Ingrese nota ”, i) Ingresar (alumno.nota[i]) Fin Desde Si queremos tener los datos de todos los alumnos de un curso, debiéramos definir un arreglo cuyos elementos sean de tipo tAlumno: Const MaxAl=200 MaxNota=5 Tipo tNotas = arreglo [1..MaxNota] de entero tFecha = Registro dia, mes, año: entero [1] Fin registro [2] tNombre = cadena[50] [3] ... tAlumno = Registro [cantAl] nombre: tNombre .... matricula: entero [199] fecha: tFecha [200] notas: tNotas Fin registro Juan Perez Ana Garcia Jorge Adad Luis Garcia Pedro Gol 1234 1256 2145 2550 3210 12 3 04 12 5 03 21 5 04 25 2 03 32 1 04 5 6 5 8 9 7 8 6 5 3 nombre matricula fecha notas 9 8 5 4 9 8 9 6 8 7 6 9 8 7 9 ListaAl tListaAl = arreglo [1..MaxAl] de tAlumno var listaAl : tListaAl al, i, cantAl: entero Para mostrar los datos de los alumnos de un curso haríamos: cantAl <- enteroEnRango(“Cantidad de alumnos”,1,MaxAL) ... Desde al <-1 hasta cantAl hacer Mostrar (“Nombre: ”,listaAl[al].nombre) Mostrar (“matricula: ”,listaAl[al].matricula) Mostrar (“Año Inscripción: ”, listaAl[al].fecha.año) Desde i <- 1 hasta MaxNota hacer Mostrar (“nota ”, i, “=”,listaAl[al].notas[i]) Fin Desde Fin Desde Observar: listaAl es un arreglo por lo que se requiere del índice para acceder a un elemento; listaAl[al] es un registro por lo que se requiere del punto y el nombre del campo para acceder a un miembro; lista[al].fecha es un registro por lo que requerimos del punto y el nombre del campo para acceder al año; lista[al].fecha.año es un entero y se puede mostrar directamente; lista[al].notas es un arreglo y lista[al].notas[i] es un entero que representa la nota i del alumno al. Registros con variante Supongamos que deseamos almacenar la información de clientes de un banco: nro. cliente, nombre, tipo de cliente, crédito y que de acuerdo al tipo de cliente, hay datos adicionales o especiales necesarios para un tipo pero no para otro. Por ejemplo, los clientes de tipo corporativo requieren la información de cantidad de miembros y tipo de sociedad que los clientes de tipo individual el número de seguro social. Hay campos comunes y campos especiales. Podríamos definir un registro con la totalidad de los campos, algunos de los cuales estarían vacíos para los clientes individuales y otros lo estarían para los Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 67 Algorítmica y Programación I corporativos. Es posible definir una variante de registro que minimice la cantidad de huecos, y dando el aspecto que el registro varía su formato según el contenido de alguno de sus campos. Para ello, en la definición del registro, hacemos: <nombreTipo> = Registro campo1: <tipo> campo2: <tipo> ... campoN: <tipo> Según campoD hacer variante1: <definiciones de campos d1> variante2: <definiciones de campos d1> Fin Segun Fin registro donde campoD es el campo por el cual se discrimina la conformación del registro y variante1, variante2 son los valores que puede tomar campoD. Las definiciones de campos d1 o d2, serán las necesarias para el problema, en la forma <nombreCampo>: <tipo> Los campos de la variante se acceden de igual forma que los restantes, a través del nombre del registro y del campo. El sistema guarda almacenamiento para la parte común más el tamaño de la variante más grande, de esta forma cada variante tendrá lugar suficiente para sus campos. En tiempo de ejecución, y según el valor que contenga el campo discriminante, sabrá qué campos son los que realmente están almacenados. Ejemplo: Tipo tTipoCliente tSociedad tNombre tCliente = = = = (corporativo, individual) (anonima,respLim, ...) Cadena[50] Registro nroCli: entero nombre: tNombre tipoCli: tTipoCliente credito: puntoFijo(2) Según tipoCli hacer corporativo: cantMiembros: entero tipoSoc: tSociedad individual: NroSS: entero Fin Segun Fin registro Procedimiento mostrarReg(E cliente: tCliente) Inicio Mostrar( “Cliente nro.:”, cliente.nroCli) Mostrar( “Nombre:”, cliente.nombre) Si cliente.tipoCli = corporativo entonces Mostrar( “tipo corporativo”) Mostrar( “cantidad Miembros:”, cliente.cantMiembros) Sino Mostrar( “tipo individual”) Mostrar( “Nro SS:”, cliente.nroSS) Fin Si Mostrar( “Crédito:”, cliente.credito) Fin Proc Formato del Registro NroCli Nombre TipoCli Credito ¿? Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 68 Algorítmica y Programación I Con Variante 1 321 NroCli Juan Perez Nombre Corporativo 2500,56 TipoCli Credito 5 cantMiembros Con variante 2 256 NroCli Luis Gracia Nombre Individual TipoCli 2563 nroSS 1200.50 Credito anonima tipoSoc Conjuntos Es una colección homogénea de elementos, sin repetición y sin relación de orden entre ellos. Desde el punto de vista informático, los elementos son todos de un mismo tipo simple y enumerativo: carácter, entero o enumerado creado por el usuario. Para definirlo es necesario indicar cuál es el tipo base. La cantidad de elementos, es matemáticamente infinita, y cada lenguaje la acota a un número máximo de elementos, que en Pascal es 255. La definición de tipos conjunto se realizan con la siguiente plantilla: <nombreTipo> = conjunto de <tipo_base> Ejemplo: Tipo TCjtoLetras = conjunto de carácter Var MisLetras: tCjtoLetras Construcción de un conjunto El conjunto se construye escribiendo los elementos individuales consecutivamente, encerrados entre corchetes y separados por comas: [elem1, elem2, ..., elemN] También puede hacerse, indicando el subrango de elementos, en lugar de hacerlo por extensión. Así por ejemplo, para asignar a misLetras el conjunto de las vocales haría: MisLetras <- [‘a’, ‘e’, ‘i’, ‘o’, ‘u’] Y para asignar a misLetras el conjunto de caracteres minúscula haría: MisLetras <- [‘a’..‘z’] El conjunto vacío Se representa por corchete de apertura seguido del corchete de cierre: [] es el conjunto vacío o nulo. Operaciones sobre datos de tipo conjunto Operación Operador Tipo de Resultado Ejemplo Unión + Conjunto [1,2] + [2,0,8] -> [1,2,0,8] Intersección * Conjunto [1,2] * [2,0,8] -> [2] Diferencia - Conjunto [1,2]-[2,0,8] -> [1] Pertenencia en Lógico 1 en [1,2] -> verdadero Igualdad = Lógico [1,2] = [2,0,8] -> falso Distinto <> Lógico [1,2] <> [2,0,8] -> verdadero Incluye a >= Lógico [1,3] >= [2] -> falso Incluido en <= Lógico [1,2] <= [1,2,0 -> verdadero Cantidad de Elementos # entero #[1,2] -> 2 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 69 Algorítmica y Programación I Archivos La noción de Archivo está relacionada con los conceptos de: o o o almacenamiento permanente de datos Partición de grandes volúmenes de información en unidades más pequeñas que pueden ser almacenadas en memoria principal, y procesadas por un programa. Almacenamiento de datos independientemente de la ejecución de un programa. Un archivo es un conjunto de datos relacionados almacenados en un medio externo (a la memoria central). Estos datos están estructurados en una colección de entidades elementales llamadas genéricamente registros. Cada registro se puede identificar por un campo o una combinación de campos que lo haga único. Esta identificación se reconoce con el nombre de clave. Por ejemplo, en un archivo de datos personales el campo del documento puede ser un campo clave. Estructuras de datos en memoria principal Ventajas o o Pequeño tiempo de acceso El tiempo necesario para acceder a los datos es independiente de la posición en que se encuentra. Desventajas o o o Falta de persistencia: Los datos existen mientras se encuentra en ejecución el módulo en que fueron definidos. Al terminar, o al interrumpirse la energía, los datos desaparecen. Capacidad bastante limitada. Grandes volúmenes de información pueden no caber en memoria principal. Para solucionar estas dificultades se necesitan los dispositivos secundarios como discos, cintas, etc., donde se almacenará la información que podrá ser recuperada para su tratamiento posterior. Clasificación de archivos Existen muchas maneras de clasificar los archivos, en particular nos interesa distinguir la manera en que un programa accede al archivo: Archivo de entrada: El programa sólo puede recuperar la información pero no puede modificarla ni agregar datos. Un programa fuente es un archivo de entrada para un compilador. Un documento es un archivo de entrada para un listado. Archivo de salida: El programa sólo puede escribir. Un archivo de código objeto es un archivo de salida de un programa compilador. Un documento es un archivo de salida de un programa editor de texto. Archivo de entrada/salida: El programa puede leer y escribir. Puede crearse en una fase del programa y modificarse en otra. Organización de archivos. La organización de archivos se refiere a la forma en que están estructurados los datos en un archivo: Organización secuencial: Los registros son almacenados consecutivamente, de tal modo que cada registro excepto el primero tiene otro que le precede, y, cada registro, excepto el último tiene otro que le sigue. El orden físico con que fueron grabados es el orden de lectura o de acceso a los mismos. principio registro 1 registro 2 registro n –1 fin de archivo registro n Para acceder a un registro n, se tiene que acceder primero a los n-1 registros anteriores. Todos los dispositivos de almacenamiento masivo permiten esta organización, algunos, como las lectoras y grabadoras de cinta magnéticas sólo permiten la organización secuencial. Los archivos de texto son de organización secuencial. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 70 Algorítmica y Programación I Organización secuencial con índice: Los registros están en posiciones consecutivas pero se puede acceder mediante un camino muy corto casi directo, a través de un índice, semejante al índice alfabético de un libro. Esta organización requiere de dos archivos, el de datos, igual que en la organización secuencial, y el de índices que es una tabla con dos campos: uno que contiene el campo clave que identifica al registro, y el otro que tiene la dirección en que el registro se encuentra realmente. El archivo de índices se mantiene ordenado respecto de la clave, lo cual permite recuperar los datos en orden al campo clave y facilita la tarea de recuperar un dato en particular. Área de Índices clave posición 15 23 56 80 100 2 0 4 3 1 Área de Datos clave resto de los datos 23 100 15 80 56 Organización relativa: El orden físico no tiene por qué corresponderse con el orden lógico. Los datos se sitúan en el archivo y se accede a ellos directamente mediante su posición, o lugar relativo que ocupan. La relación entre el registro y la posición se determina por alguna fórmula. Se puede acceder a un registro sin necesidad de acceder a todos los anteriores. El archivo puede llegar a contener “huecos”. Modos de acceso a los registros de un archivo Acceso secuencial: para acceder a un registro debo acceder primero todos los registros anteriores. Los archivos de texto tienen organización y acceso secuencial. Todos los archivos almacenados en dispositivos secuenciales como unidades de cinta, sólo pueden accederse secuencialmente. Acceso directo: puedo acceder a un registro directamente sin necesidad de pasar por todos los anteriores. El archivo debe estar almacenado en un dispositivo direccionable. En una organización secuencial con registros de igual tamaño, puede llegar a darse un acceso directo. En la organización relativa, el acceso es directo. La organización secuencial con índice permite un acceso directo al archivo de datos, previa ubicación de la clave a recuperar, en general, se accede secuencialmente al de claves y directamente al de datos. Operaciones sobre archivos o Creación / acceso o Actualización o o o Inserción de registros modificación de registros supresión de registros Como consecuencia de la actualización el archivo cambia su contenido. Si el archivo sólo permite acceso secuencial o es de sólo entrada, la actualización se logra creando un nuevo archivo a partir del existente. Para suprimir un registro se necesita crear un nuevo archivo con todos los registros excepto los que se desean eliminar. Para evitar esto, se suele trabajar con el criterio de bajas lógicas, donde un campo del registro indica si está activo o no. De esta manera, si el registro que se recupera tiene la marca de baja (o de no estar activo), puede ser ignorado. o o Recuperación: el archivo mantiene su contenido o Consulta de uno o más registros. o Listado de un grupo o de todos los registros. Mantenimiento o estructuración o reorganización Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 71 Algorítmica y Programación I Entorno de programación mínimo para trabajar con archivos secuenciales. Un entorno de programación deberá proveer alguna forma de estructura de datos para archivos y un conjunto de operaciones elementales que permitan como mínimo: o o o crear un archivo vacío acceder a un archivo existente recuperar o leer un registro del archivo o o o grabar o escribir un registro en el archivo saber si se llegó al fin de archivo. cerrar la conexión con el archivo. Con seguridad, un lenguaje que me permita trabajar con archivos proveerá esas operaciones y posiblemente algunas más. Los parámetros, nombre y forma de las operaciones dependerán de cada lenguaje. Archivos de texto Son archivos secuenciales cuyos elementos son caracteres organizados en líneas de texto. Para facilitar su manejo, los lenguajes cuentan con operaciones y tipos de datos especiales. Para ello vamos a definir un nuevo tipo que llamaremos tTexto y que permitirá identificar variables para referirse a archivos de texto. Los elementos de un archivo de texto son caracteres, y por lo tanto puedo utilizar variables de tipo carácter o de tipo cadena. Var miArchivo: tTexto (* variable de memoria con que voy a referenciar a mi archivo *) s: cadena/ caracter (* variable de memoria con que voy a referenciar un elemento*) No debemos confundir la variable archivo con el archivo de datos. La primera es una estructura de datos que tiene la información necesaria para vincularse y acceder al archivo físico que contiene los datos. Operaciones que permiten conectarse con el dispositivo externo, creando un nuevo archivo o utilizando un archivo existente Crear(varArch, nombArch): Crea un archivo vacío con el nombre indicado en nombArch (de tipo cadena) e inicializa la variable varArch (de tipo archivo tTexto) con la información necesaria para poder vincularse con el archivo físico externo. Si existe un archivo con el mismo nombre lo destruye, lo pone a cero. varArch es un parámetro de salida y nombArch es un parámetro de entrada. La operación crear inicializa a varArch con la información necesaria. Ej: Crear (miArchivo, "Memorias.txt”) Abrir (varArch, nombArch) Abre el archivo con el nombre indicado en nombArch, se posiciona al principio del archivo e inicializa la variable varArch con la información necesaria para poder vincularse con el archivo físico externo. Si no existe un archivo con ese nombre da un error. Ej: Abrir (miArchivo, "Memorias.txt”) Anexar (varArch, nombArch) Si existe un archivo con el nombre indicado en nombArch, lo abre y se posiciona al final del archivo, si no existe el archivo, lo crea. En cualquiera de los dos casos inicializa la variable varArch con la información necesaria para poder vincularse con el archivo físico externo y queda posicionado al final del archivo, de modo tal que puedan agregarse datos. Ej: Anexar (miArchivo, "Memorias.dat”) En los tres casos, varArch es un parámetro de salida que queda con la información necesaria para poder vincularse con el archivo físico externo (descriptor del archivo, información de estado, si utiliza o no buffer, posición dentro del archivo, etc.) y nomArch es un parámetro de entrada, es el nombre del archivo existente o a crear. Operaciones que permiten recuperar un elemento Leer (varArch, s) Recupera un caracter si el parámetro s es de tipo caracter o los caracteres de una línea que quepan en s si s es una cadena. Avanza el puntero del archivo al comienzo del próximo caracter no leído. Querer leer después del último caracter da error. En general se utiliza con un s de tipo caracter. Ej: Leer(miArchivo, s) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 72 Algorítmica y Programación I LeerLN (varArch, s) Recupera la línea de texto apuntada en ese momento del archivo referenciado a través de varArch, lo copia sobre el segundo parámetro (que tiene que ser de tipo cadena o de tipo caracter) y avanza el puntero del archivo al comienzo de la siguiente línea. Los caracteres que no se pueden almacenar en el parámetro s se ignoran. Querer leer después del último caracter da error. En general se utiliza con un s de tipo cadena suficientemente grande como para almacenar la línea de texto. Ej: LeerLN (miArchivo, s) En los dos casos, varArch es un parámetro de entrada-salida: tiene la información necesaria para poder vincularse con el archivo físico externo (descriptor del archivo, información de estado, si utiliza o no buffer, posición dentro del archivo, etc.) y actualiza su estado luego de acceder y recuperar un elemento del archivo físico, y s es un parámetro de salida que quedará inicializado con el valor del elemento recuperado. Operaciones que permiten agregar un elemento al archivo Escribir (varArch, s) Graba o escribe el contenido de s en la posición actual del archivo referenciado a través de varArch, avanza el puntero al final de lo escrito (quedando siempre en el final). Ej: Escribir (miArchivo, s) EscribirLN (varArch, s) Graba o escribe el contenido de s más un caracter de nueva línea. Avanza el puntero al final de lo escrito. Ej: EscribirLN (miArchivo, s) En los dos casos, varArch es un parámetro de entrada-salida: tiene la información necesaria para poder vincularse con el archivo físico externo (descriptor del archivo, información de estado, si utiliza o no buffer, posición dentro del archivo, etc.) y actualiza su estado luego de acceder y agregar un elemento al archivo físico, y s es un parámetro de entrada con el valor del elemento a agregar. Operaciones de consulta de estado esFinLinea (varArch) Retoma verdadero Si se llegó a un final de línea, falso sino. Ej: Si esFinLinea(miArchivo) entonces ... Fin Si esFinArchivo (varArch) o eof(varArch) Retoma verdadero Si se llegó al final del archivo, falso sino. Se lo consulta normalmente antes de leer. Ej: Si no esFinArchivo(miArchivo) entonces Leer (miArchivo, s) Fin Si Operaciones de cierre Cerrar (varArch) Completa las operaciones que pudieron quedar pendientes de actualizar en el archivo físico y desestablece la conexión entre la variable de memoria y el dispositivo. Cualquier operación sobre ese archivo que se hiciera después de cerrar, produce error (a excepción de abrir o anexar). Ej: Cerrar(miArchivo) Ejemplos de algoritmos con archivo de texto Agregar datos a un archivo existente Datos de entrada: Nombre del archivo existente al que se le agregará datos. Datos de salida: Los cambios se producen sobre el archivo. No hay datos de salida mediante los parámetros. nombre Cliente AgregarDatos Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 73 Algorítmica y Programación I Nota: El algoritmo abre el archivo para anexar y al finalizar lo cierra. Procedimiento AgregarDatos(E nombre:cadena) (* agrega datos al archivo nombre, si el archivo no existe lo crea*) (* precondición: nombre = N poscondición: existe un archivo de nombre N y N tiene 1 o más lineas de texto *) var arch:tTexto s: cadena Inicio Anexar(arch,nombre) Mostrar (“Ingrese lineas de texto”) Repetir Ingresar(s) EscribirLN( arch,s) hasta confirma(“Finaliza?”) Cerrar(arch) Fin Proc nomb1, nomb2 Anexar los datos de un archivo a otro Cliente copiarDatos Datos de entrada: Nombre del archivo existente del que se toman los datos: nomb1. Nombre del archivo sobre el que se anexan los datos: nomb2. Datos de salida: Los cambios se producen sobre el archivo nomb2. No hay datos de salida mediante los parámetros. Nota: El algoritmo abre los archivos y al finalizar los cierra. El archivo nomb1 debe existir. Procedimiento CopiaDatos(E nomb1, nomb2:cadena) (* copia los datos del archivo1 al final del archivo2 Precondición: nomb1 = N1 y nomb2= N2 y el archivo N1 debe existir Poscondición: el archivo N2 contiene sus datos originales más los datos del archivo N1 *) var arch1,arch2:tTexto c: caracter Inicio Abrir(arch1,nomb1) Anexar(arch2,nomb2) Mientras ( no eof(arch1) hacer Leer (arch1, c) Escribir (arch2,c) Fin Mientras Cerrar(arch1) Cerrar(arch2) Fin Proc Archivos de un determinado tipo de dato Contamos con el constructor archivo para crear nuestros propios tipos de archivos, y de manera similar al constructor arreglo le indicamos de qué tipo son sus elementos. Ejemplo: tipo tReg= registro (*descripción del tipo del elemento *) x,y: real h: real Fin registro Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 74 Algorítmica y Programación I tArch= archivo de tReg Var miArchivo: tArch (*descripción del tipo de archivo *) Operaciones que permiten conectarse con el dispositivo externo, creando un nuevo archivo o utilizando un archivo existente Crear(varArch, nombArch): Crea un archivo vacío con el nombre indicado en nombArch (de tipo cadena) e inicializa la variable varArch (de tipo archivo) con la información necesaria para poder vincularse con el archivo físico externo. Si existe un archivo con el mismo nombre lo destruye, lo pone a cero. Es la misma que para archivos de texto. Ej: Crear (miArchivo, "Mediciones. dat”) Abrir (varArch, nombArch) Abre el archivo con el nombre indicado en nombArch, se posiciona al principio del archivo e inicializa la variable varArch con la información necesaria para poder vincularse con el archivo físico externo. Si no existe un archivo con ese nombre da un error. Es la misma que para archivos de texto. Ej: Abrir (miArchivo, "Mediciones. dat”) En los dos casos, varArch es un parámetro de salida que queda con la información necesaria para poder vincularse con el archivo físico externo (descriptor del archivo, información de estado, si utiliza buffer, posición dentro del archivo, etc.) y nomArch es un parámetro de entrada, es el nombre del archivo. Operación que permite recuperar un elemento Leer (varArch, reg) Recupera el registro apuntando en ese momento del archivo referenciado a través de varArch, lo copia sobre el segundo parámetro (que tiene que ser del mismo tipo que los elementos del archivo) y avanza el puntero del archivo al siguiente elemento. Querer leer después del último registro produce error. Ej: Leer (miArchivo, reg) VarArch es un parámetro de entrada-salida: tiene la información necesaria para poder vincularse con el archivo físico externo (descriptor del archivo, información de estado, si utiliza o no buffer, posición dentro del archivo, etc.) y actualiza su estado luego de acceder y recuperar un elemento del archivo físico, y reg es un parámetro de salida que quedará inicializado con el valor del elemento recuperado. Operación que permiten agregar un elemento al archivo Escribir (varArch, reg) Graba o escribe el contenido de reg en la posición actual del archivo referenciado a través de varArch y avanza el puntero del archivo al siguiente elemento o posición. Ej: Escribir (miArchivo, reg) Operaciones de consulta de estado esFinArchivo (varArch) o eof(varArch) Retoma verdadero Si se llegó al final del archivo, falso sino. Se lo consulta normalmente antes de leer. Ej: Si no esFinArchivo(miArchivo) entonces Leer (miArchivo, reg) Fin Si Operaciones de cierre Cerrar (varArch) Completa las operaciones que pudieron quedar pendientes de actualizar en el archivo físico y desestablece la conexión entre la variable de memoria y el dispositivo. Cualquier operación sobre ese archivo que se hiciera después de cerrar, produce error (a excepción de abrir). Ej: Cerrar(miArchivo) Operaciones que permiten acceso directo Aunque estén organizados en forma secuencial, dado que sus elementos son todos de un mismo tipo, y por lo tanto del mismo tamaño, es posible calcular donde se encuentra el i-ésimo elemento. Para permitir acceso directo a una posición del archivo, sin tener que recorrer las anteriores, en general los lenguajes proveen por lo menos las siguientes primitivas, que se suman a las descriptas anteriormente, y no están disponibles para archivos de texto: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 75 Algorítmica y Programación I Posicionarse (varArch, pos) Ubica el puntero del archivo en la posición pos relativa al principio. Ej: Posicionarse (miArchivo, 2) (*se posiciona al final del segundo registro o comienzo del tercero, ya que las posiciones se cuentan desde 0.*) DóndeEstoy(varArch): entero Devuelve la posición relativa en que se encuentra ubicado el puntero del archivo. Tamaño(varArch): entero Devuelve la cantidad de elementos que tiene el archivo referenciado por varArch. Otras operaciones útiles para cualquier tipo de archivo ExisteArchivo(nombre):lógico. Retoma verdadero si existe un archivo con ese nombre. Nombre puede tener el path completo. Ej: Si ExisteArchivo(s) entonces abrir (miArchivo, s) Fin Si BorrarArchivo(nombre): Elimina el archivo con ese nombre. El archivo debe estar cerrado. RenombrarArchivo(nombre1, nombre2): Cambia el nombre del archivo nombre1 con nombre2 Ejemplos de algoritmos con archivos de datos de un tipo determinado tipos tReg= registro (*descripción del tipo del elemento *) clave: entero ... Fin registro tArch= archivo de tReg (*descripción del tipo de archivo *) Agregar datos a un archivo existente Datos de entrada: Nombre del archivo existente al que se le agregará datos. Datos de salida: Los cambios se producen sobre el archivo. No hay datos de salida mediante los parámetros. Nota: El archivo debe existir. El algoritmo abre y cierra el archivo. Procedimiento AgregarDatos(E nombre:cadena) (* agrega datos al final del archivo nombre Precondición: nombre= N y Debe existir un archivo N Poscondicion El archivo N tiene uno o más registros*) var nombre arch: tArch reg: tReg Inicio Abrir(arch, nombre) Cliente Posicionarse(arch, tamaño(arch)) Mostrar (“Ingreso de datos”) Repetir LlenarReg(reg) //llena reg con datos ingresados por el usuario Escribir (arch, reg) hasta confirma(“¿desea terminar?”) Cerrar(arch) Fin Proc agregarDatos Copiar datos de un archivo existente sobre el final de otro. Datos de entrada: Nombre de los archivos: nomb1 y nomb2 (los datos de nomb1 se copian al final de los datos de nomb2). nomb1, nomb2 Cliente copiarDatos Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 76 Algorítmica y Programación I Datos de salida: Los cambios se producen sobre el archivo nomb2. No hay datos de salida mediante los parámetros. Nota: Los archivos deben existir. El algoritmo abre y cierra cada archivo. Procedimiento copiaDatos(E nomb1,nomb2:cadena) (* copia los datos del archivo1 al final del archivo2 Precond: nomb1=N1 y nomb2=N2 y deben existir los archivos N1 y N2 Poscond: tamaño de N2 >= tamaño de N1 y los datos de N1 están en N2 *) var arch1,arch2: tArch reg: tReg Inicio Abrir(arch1,nomb1) //es necesario que existan ambos arch Abrir(arch2,nomb2) Posicionarse(arch2, tamaño(arch2)) Mientras ( no eof(arch1) ) hacer Leer (arch1, reg) Escribir (arch2, reg) Fin Mientras Cerrar(arch1) Cerrar(arch2) Fin Proc Crear un archivo con los datos de un archivo existente que cumplen una cierta condición Crear un archivo nomb2 con los datos del archivo nomb1 que cumplen la condición, nroEmp > 1000 Datos de entrada: Nombre del archivo origen: nomb1 y nomb1, nombre del archivo a crear: nomb2. Valor de la condición para seleccionar qué registros se grabarán en nomb2. nomb2, tope Datos de salida: Los cambios se producen sobre el Cliente seleccionaDatos archivo nomb2. No hay datos de salida mediante los parámetros. Nota: El archivo nomb1 debe existir. El algoritmo abre nomb1 y crea nomb2. Al finalizar cierra cada archivo. Procedimiento seleccionaDatos(E nomb1,nomb2:cadena, E tope:entero) (* Crea un archivo nomb2 con los datos del archivo nomb1 que cumplen la condición, nroEmp > tope . Precondición: nomb1=N1 y nomb2=N2 y el archivo N1 debe existir y tope = T >0 Poscondicion: El archivo N2 tiene 0 o más registros y el nroEmp de cada registro es > T*) var arch1,arch2: tArch reg: tReg Inicio Abrir (arch1,nomb1) Crear (arch2,nomb2) Mientras ( no eof(arch1) ) hacer Leer (arch1, reg) Si reg.nroEmp > tope entonces Escribir (arch2, reg) Fin Si Fin Mientras Cerrar(arch1) Cerrar(arch2) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 77 Algorítmica y Programación I Fin Proc Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 78 Algorítmica y Programación I Modificar datos de un archivo Modificar el precio de cada registro del archivo nomb1, multiplicándolo por cierto factor. Datos de entrada: Nombre del archivo origen: nomb1. nomb1 Factor por el que se incrementa el precio. factor Datos de salida: Los cambios se producen sobre el archivo nomb1. No hay datos de salida mediante los Cliente modificaDatos parámetros. Nota: El archivo nomb1 debe existir, y al finalizar queda modificado. El algoritmo abre y cierra al archivo nomb1. Procedimiento ModificaDatos(E nomb1:cadena, E factor: real) (* Modifica el precio de cada registro del archivo nomb1, multiplicandolo por el factor Precondición: nomb1=N1 el archivo N1 debe existir Poscondicion: El archivo N1 es modificado: el precio de cada registro es llevado al valor precio * factor *) var arch: tArch reg: tReg pos: entero Inicio Abrir (arch,nomb1) Mientras ( no eof(arch) ) hacer posdondeEstoy(arch) Leer (arch, reg) reg.precioreg.precio * factor (* modifico reg *) Posicionarse(arch, pos) Escribir (arch, reg) Fin Mientras Cerrar(arch) Fin Proc Insertar un registro en orden de su clave Se dispone de un archivo ya abierto y ordenado por el campo clave y de un registro con datos que se pretende insertar en orden. Datos de entrada: archivo abierto: arch. Registro de datos a insertar: regNuevo Datos de salida: archivo abierto: arch. Nota: arch debe estar abierto y ordenado por su campo clave. La clave del registro regNuevo no debe existir previamente en arch. regNuevo arch Cliente insertaReg Se deberá hacer lugar corriendo hacia el final los registros cuya clave sea superior. Puede darse que el registro deba insertarse al final, en medio o al principio. Nos ubicaremos al final del archivo, leeremos el último registro y lo comparamos con el nuevo, si nuevo es mayor, grabamos y finalizamos, si no, grabamos el recién leído en la posición siguiente y leemos el anterior y comparamos, repitiendo el proceso hasta que o encontramos que nuevo es mayor o que no queda uno anterior. En tal caso, grabamos en la posición siguiente de la que acabamos de recuperar. Usaremos una bandera encontrePos para indicar si encontré o no la posición donde insertarlo. Procedimiento InsertaReg(E/S arch:tArch, E regNuevo: tReg) (* Inserta regNuevo en arch, respetando el orden de la clave Precondición: arch=F y F está abierto y ordenado y regNuevo=N y N.clave no pertenece a F Poscondicion: El archivo F está ordenado y N.clave pertenece a F *) var Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 79 Algorítmica y Programación I reg: tReg pos: entero encontrePos: logico Inicio Pos <- tamañoArch(arch) –1 // posición del ultimo registro EncontrePos <- falso Mientras ( pos >= 0 y no encontrePos ) hacer Posicionarse(arch, pos) Leer (arch, reg) Si reg.clave > regNuevo.clave entonces Escribir (arch, reg) pos <- pos - 1 Sino encontrePos <- verdadero Fin Si Fin Mientras Posicionarse(arch, pos + 1) Escribir (arch, regNuevo) Fin Proc Actualización de un archivo Es el proceso por el cual se realizan altas, bajas o modificaciones al archivo. Cada registro del archivo es identificado de una manera única por una clave. La clave es un campo o conjuntos de campos cuyo valor o combinación de valores es única. La clave puede ser impuesta por la aplicación, por ejemplo el nro. de documento de una persona, o creada con algún mecanismo de generación automática (auto numerado y no repetible) Un alta implica la inserción de un nuevo registro, por ello la clave del nuevo registro no puede encontrarse entre las claves de los registros existentes. Una baja es la eliminación de un registro para lo cual se lo debe ubicar a través de su clave. Una modificación implica un cambio en algún campo del registro, para ello se ubica el registro a modificar a través de su clave. Para dar de baja o modificar un registro es necesario que la clave que lo identifica se encuentre entre las claves de los registros existentes. Existen dos maneras de actualizar un archivo: Creando un nuevo archivo: parto del archivo maestro con la información original y otro archivo ABM con indicaciones de las operaciones a realizar: A=alta, B=baja, M=modificación y los datos asociados a la operación. Ambos archivos deben estar ordenados por su clave. El nuevo archivo contendrá: o o o todos los datos del maestro cuyas claves no figuran en el ABM, todos los datos del ABM que son altas y cuyas claves no figuran en el maestro, y todos los registros del ABM que están indicados como modificación y cuyas claves figuraban en el maestro. Maestro ABM M.clave Actualización Nuevo Maestro ? ABM.clave < Precondiciones: Maestro y ABM están ordenados por campo clave. Dentro de cada archivo, no hay registros repetidos con igual clave. grabo el registro M y leo del maestro = operación ? > ‘A’: error, grabo de M y leo de ambos archivos ‘B’: leo de ambos archivos (no grabo ninguno) ‘M’: grabo el registro de ABM. Leo de ambos. operación ? ‘A’: grabo el registro de ABM. Leo de ABM. ‘B’,’M’: error y leo de ABM Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 80 Algorítmica y Programación I Son casos de error las siguientes situaciones: o Un alta con clave existente en el maestro o Una baja con clave inexistente en el maestro o Una modificación con clave inexistente en el maestro El proceso se realiza en forma semejante al correspondiente a una fusión: mientras hay elementos a procesar en ambos archivos, comparo sus claves y tomo decisiones de cual incorporar en el archivo resultante, teniendo en cuenta lo descripto anteriormente en cuanto a lo que debe contener y a los casos de error. Cuando quedan elementos de un solo archivo, si son del maestro, los agrego a todos, pero si son del ABM, sólo agrego aquellos que están marcados como altas, y registro como error a los marcados como baja o modificación. Modificando el mismo archivo: En este caso es necesario trabajar con bajas lógicas: el registro de datos, además de los campos necesarios para la información asociada al problema, deberá contener un campo adicional de tipo lógico que sirva para indicar si está o no activo el registro. V: el registro está activo clave Restantes datos activo: lógico F: está dado de baja Para dar un alta deberá comprobarse que la clave del nuevo registro no figura en el archivo y luego inserta el registro en orden (corriendo hacia el final todos los registros mayores que su clave si es que estaban ordenados). Para dar una baja, se ubica el registro cuya clave se quiere dar de baja, se lo recupera, se modifica el campo de indicación de activo a no activo y se lo vuelve a grabar en el mismo lugar. Para hacer una modificación, se ubica (por su clave) y se recupera el registro que se desea modificar, se realiza la modificación y se lo vuelve a grabar en el mismo lugar. Son casos de error las siguientes situaciones: o Un alta con clave existente en el maestro: existe y está activa. Si existe y está inactiva, no es un error, pues se trata de una baja lógica y por lo tanto el registro y su clave puede reutilizarse para el alta. En este caso, no se inserta corriendo los elementos, sino que se sobrescribe y se lo activa. o Una baja con clave inexistente en el maestro: No existe o existe y está inactiva. o Una modificación con clave inexistente en el maestro: No existe o existe y está inactiva. Cuando la clave es auto generada, y no hay ninguna otra restricción para agregar un elemento al archivo, el proceso de alta no debiera producir error por clave repetida, a menos que falle el mecanismo de autogeneración. Un mecanismo podría ser generar el número siguiente a la clave más alta. En este caso, el nuevo registro quedaría ubicado siempre al final del archivo. Ejemplo de actualización en línea Dispondremos de operaciones para dar altas, bajas, hacer consultas o modificaciones que reciben el nombre del archivo y se encargan de abrirlo, procesarlo y cerrarlo. Supondremos que el registro tiene un campo lógico llamado activo, que indica si está en actividad o dado de baja. Asumiremos que contamos con una operación BuscarClave (arch, clave, pos, reg) que busca la clave en el archivo arch ya abierto y si la encuentra devuelve su posición en pos y el registro que la contiene en reg, si no devuelve el valor –1 en pos para indicar que no la encontró. Procedimiento Alta (E nomb: Cadena) Var Precondición: nomb=N y el archivo N reg: tReg existe y está ordenado por su clave arch: tArch PosCondición: el archivo N tiene 1 o pos: entero más datos ordenados por su clave Inicio Abrir(arch, nomb) repetir PedirClave( reg.clave) // pide datos del campo clave BuscarClave(arch, reg.clave, pos, reg) Si pos >= 0 entonces //fue encontrado un registro con esa clave Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 81 Algorítmica y Programación I Si reg.activo entonces // ya existe, es un error dar de alta Mostrar(“ERROR alta....”) Sino // reutilizo el registro para volver a incorporalo PedirRestoDatos(reg) Posicionarse(arch, pos) Escribir(arch, reg) Fin Si Sino // no se encontraba esa clave, lo inserto PedirRestoDatos(reg) InsertaReg(arch, reg) Fin Si Hasta confirma(“Finaliza?”) Cerrar(arch) Fin Proc Procedimiento Baja (E nomb: Cadena) Var Precondición: nomb=N y el archivo N reg: tReg existe y está ordenado por su clave arch: tArch PosCondición: el archivo N tiene 1 o pos: entero menos datos ordenados por su clave Inicio Abrir(arch, nomb) repetir PedirClave( reg.clave) // pide datos del campo clave BuscarClave(arch, reg.clave, pos, reg) Si pos >= 0 entonces //fue encontrado un registro con esa clave Si no reg.activo entonces //no activo, es un error dar de baja Mostrar(“ERROR baja....”) Sino // marco como baja si confirma eliminarlo MostrarReg(reg) Si confirma(“Lo elimina?”) entonces reg.activo <- falso Posicionarse(arch, pos) Escribir(arch, reg) Fin Si Fin Si Sino // no se encontraba esa clave, error dar de baja Mostrar(“ERROR baja....”) Fin Si Hasta confirma(“Finaliza?”) Cerrar(arch) Fin Proc Procedimiento Consulta (E nomb: Cadena) Var Precondición: nomb=N y el archivo N reg: tReg existe y está ordenado por su clave arch: tArch PosCondición: el archivo N tiene los pos: entero mismos datos ordenados por su clave Inicio Abrir(arch, nomb) repetir PedirClave( reg.clave) // pide datos del campo clave BuscarClave(arch, reg.clave, pos, reg) Si pos >= 0 entonces //fue encontrado un registro con esa clave Si no reg.activo entonces //no activo, es un error consultarlo Mostrar(“ERROR consulta....”) Sino // lo muestro MostrarReg(reg) Fin Si Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 82 Algorítmica y Programación I Sino // no se encontraba esa clave, error consulta Mostrar(“ERROR consulta....”) Fin Si Hasta confirma(“Finaliza?”) Cerrar(arch) Fin Proc Procedimiento Modificacion (E nomb: Cadena) (* Precond: nomb=N y el archivo N existe y está ordenado por su clave Poscond: el archivo N mantiene ordenados los datos por su clave*) Var reg: tReg arch: tArch pos: entero Inicio Abrir(arch, nomb) repetir PedirClave( reg.clave) // pide datos del campo clave BuscarClave(arch, reg.clave, pos, reg) Si pos >= 0 entonces //fue encontrado un registro con esa clave Si no reg.activo entonces //no activo, es un error modificar Mostrar(“ERROR modif....”) Sino // lo muestro MostrarReg(reg) PedirDatosModificacion(Reg) Posicionarse(arch, pos) Escribir(arch, reg) Fin Si Sino // no se encontraba esa clave, error consulta Mostrar(“ERROR modif....”) Fin Si Hasta confirma(“Finaliza?”) Cerrar(arch) Fin Proc Cada cierto tiempo habrá que reorganizar el archivo, eliminando físicamente los registros dados de baja. Para ello es necesario recorrer el archivo y copiar en uno nuevo sólo los registros que están activos. Procedimiento Reorganizar (E nomb: Cadena) (* Precond: nomb=N y el archivo N existe Poscond: cantidadBajas(archivo Nomb) = 0 *) Var Reg: tReg Arch1, arch2: tArch Inicio Abrir(arch1, nomb) Crear(arch2, “Auxiliar”) Mientras no eof(arch1) hacer Leer(arch1, reg) Si reg.activo entonces Escribir(arch2, reg) Fin Si Fin Mientras Cerrar(arch1) Cerrar(arch2) Eliminar(arch1) Renombrar(arch2,nomb) Fin Proc Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 83 Algorítmica y Programación I Ordenamiento, búsqueda e intercalación Son operaciones básicas en el campo de la documentación y en la que según señalan las estadísticas, las computadoras emplean la mayor cantidad de tiempo. La ordenación es la operación de organizar un conjunto de datos en un orden dado, creciente o decreciente en datos numéricos o alfabéticos. Hay varios métodos para ordenar un conjunto de datos. La búsqueda de información es una tarea muy frecuente. Si los datos están ordenados, buscar un elemento en ellos resulta más sencillo y rápido. Si no lo están, habrá que recorrer todos para darse cuenta que no se encuentra el elemento. La intercalación o mezcla es la operación de unir dos conjuntos de datos ordenados en uno sólo. Métodos de ordenamiento o Ordenación interna: se realiza en memoria interna de la computadora de gran velocidad y acceso aleatorio. Se tienen los datos en un arreglo. o Ordenación externa: se realiza en medios de almacenamiento externos como cintas o disco. Estos dispositivos son más lentos en la operaciones de entrada salida pero pueden contener mayor cantidad de información. Los datos están en un archivo. Datos de entrada: lista de datos a ordenar: lista. Rango de los elementos a ordenar: principio y final Principio, final Lista Datos de salida: lista de datos ordenada: lista. Cliente ordenar Método de selección https://es.wikipedia.org/wiki/Ordenamiento_por_selecci%C3%B3n#/media/File:Selection-SortAnimation.gif Se basa en buscar el menor elemento e intercambiarlo con el de la primera posición. Luego repetir el proceso desde el segundo elemento, luego desde el tercero, y así hasta que sólo quede uno. Ejemplo: [1] Lista 20 [2] 32 [3] 12 [4] 15 [5] 25 [6] 16 [7] 30 [8] 10 [9] 18 [10] 28 Iteración 1 10 32 12 15 25 16 30 20 18 28 10 Iteración 2 10 12 32 15 25 16 30 20 18 28 12 Iteración 3 10 12 15 32 25 16 30 20 18 28 15 Iteración 4 10 12 15 16 25 32 30 20 18 28 16 Iteración 5 10 12 15 16 18 32 30 20 25 28 18 Iteración 6 10 12 15 16 18 20 30 32 25 28 20 Iteración 7 10 12 15 16 18 20 25 32 30 28 25 Iteración 8 10 12 15 16 18 20 25 28 30 32 28 Iteración 9 10 12 15 16 18 20 25 28 30 32 30 mínimo En la primera iteración encontramos que el elemento de la posición 8 es el menor y lo intercambiamos con el que se encuentra en la primer a posición. En la segunda iteración, consideramos la lista a partir de la posición 2 y encontramos que el elemento de la posición 3 es el menor y lo intercambiamos con el que se encuentra en la segunda a posición. Así seguimos hasta que en la novena iteración nos quedaron acomodados los dos últimos valores. Necesitamos de una estructura de repetición que recorra del principio al final de la lista para ubicar el menor en cada pasada. Y de otra estructura de repetición que repita el proceso N-1 veces. Puesto que Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 84 Algorítmica y Programación I en cada pasada sabemos cuántos elementos consultar, podemos realizarlo utilizando dos estructuras de repetición anidadas basadas en contador: “estructura Desde” Procedimiento OrdSeleccion(E/S Lista:tLista, E principio, final: entero) Var Precondición: principio=P i, p, menor: entero y final=F y P<=F y [P,F] Inicio Desde p<- principio hasta final -1 hacer Rango(tLista) y Lista= Menor <- p {Li} i [P,F] Desde i<- p +1 hasta final hacer Poscondición: Lista={Vi} Si lista[i] < lista[menor] entonces y {Vi}=Permutación({Li}) Menor <- i y Vi<=Vi+1 i [P,F]*) Fin Si Fin Desde Intercambio(lista[p], lista[menor]) Fin Desde Fin Proc Método de intercambio o burbuja https://es.wikipedia.org/wiki/Ordenamiento_de_burbuja#/media/File:Bubble-sort-example-300px.gif Se comparan dos posiciones consecutivas, primera y segunda, si están en orden se mantienen como están, en caso contrario se intercambian. Luego se comparan segunda con tercera y así hasta finalizar. Como resultado de toda esta comparación, el elemento mayor se habrá corrido hacia el final quedando en su posición correcta. Se repite el proceso hasta que no haya más intercambios o que el último intercambio haya sido con el primer elemento. [1] Lista 20 [2] 32 [3] 12 [4] 15 [5] 25 [6] 16 [7] 30 [8] 10 [9] 18 Iteración 1 20 12 15 25 16 30 10 18 28 [10] 28 Último intercambio 32 9 Iteración 2 12 15 20 16 25 10 18 28 30 32 8 Iteración 3 12 15 16 20 10 18 25 28 30 32 6 Iteración 4 12 15 16 10 18 20 25 28 30 32 5 Iteración 5 12 15 10 16 18 20 25 28 30 32 3 Iteración 6 12 10 15 16 18 20 25 28 30 32 2 Iteración 7 10 12 15 16 18 20 25 28 30 32 1 En la primer iteración el valor 32 se fue corriendo hacia el final, resultando que el último intercambio se realizó estando en la posición 9, con lo que sólo se aseguró de ubicar correctamente ese único elemento. En la segunda iteración se fueron corriendo los valores 20, 25 y 30, quedando con la certeza de haber acomodado un elemento. En la tercer iteración quedaron acomodados los valores 25, 28 ya que el último intercambio se hizo cuando el 25 estaba en la posición 6. Procedimiento Burbuja(E/S Lista:tLista, E principio, final: entero) Var Precondición: principio=P i, ultI, tope: entero y final=F y P<=F y [P,F] ordenado: logico Rango(tLista) y Lista= Inicio {Li} i [P,F] ultI <- final Poscondición: Lista={Vi} y Repetir {Vi}=Permutación({Li}) y ordenado <- verdadero Vi<=Vi+1 i [P,F]*) tope <- UltI - 1 Desde i<- principio hasta tope hacer Si lista[i] > lista[i+1] entonces Intercambio(lista[i], lista[i+1]) ultI <- i ordenado <- falso Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 85 Algorítmica y Programación I Fin Si Fin Desde Hasta ordenado Fin Proc Método de inserción o de la baraja https://es.wikipedia.org/wiki/Ordenamiento_por_inserci%C3%B3n#/media/File:Insertion-sort-example300px.gif Consiste en insertar un elemento en el arreglo en la parte ya ordenada y comenzar de nuevo con los elementos restantes. Se basa en comparaciones y desplazamientos sucesivos. Cada elemento del vector es insertado en el lugar correspondiente respecto a los otros elementos ya ordenados. Se parte suponiendo que se tiene una lista ordenada con un solo elemento (el primero) y se trata de colocar en orden al segundo elemento: se compara el segundo elemento con el primero, si están en orden se mantienen como están, en caso contrario se desplaza el primero un lugar hacia la derecha para poder colocar el segundo en su lugar. Luego, se compara el tercer elemento con los ya ordenados para buscar la posición que le corresponda respecto a los mismos. Si fuese necesario, se desplazan los elementos posteriores hacia la derecha para liberar la posición del vector donde se insertará el elemento considerado. Se repite esta operación con todos los elementos restantes. A continuación aplicaremos el método a nuestro vector: [1] Lista 20 [2] 32 [3] 12 [4] 15 [5] 25 [6] 16 [7] 30 [8] 10 [9] 18 [10] 28 Elemento a ubicar Sector ordenado [1] Lista 20 [2] 32 [3] 12 [4] 15 [5] 25 [6] 16 [7] 30 [8] 10 [9] 18 [10] 28 Iteración 1 20 32 12 15 25 16 30 10 18 28 32 1..2 Iteración 2 12 20 32 15 25 16 30 10 18 28 12 1..3 Iteración 3 12 15 20 32 25 16 30 10 18 28 15 1..4 Iteración 4 12 15 20 25 32 16 30 10 18 28 25 1..5 Iteración 5 12 15 16 20 25 32 30 10 18 28 16 1..6 Iteración 6 12 15 16 20 25 30 32 10 18 28 30 1..7 Iteración 7 10 12 15 16 20 25 30 32 18 28 10 1..8 Iteración 8 10 12 15 16 18 20 25 30 32 28 18 1..9 Iteración 9 10 12 15 16 18 20 25 28 30 32 28 1..10 1..1 En la primera iteración se comparan el primero y segundo elemento, como están en orden, no hay intercambio. En la segunda iteración se considera al tercer elemento y al compararlo con los dos elementos ya ordenados, se encuentra que debe ubicarse en la primer posición del vector. Se desplazan un lugar a la derecha los elementos que se encuentran en las dos primeras posiciones para liberar la posición 1, donde se ubica el 12. Y así en cada iteración tomamos un nuevo elemento comparándolo con los anteriores que están ordenados entre sí. Procedimiento OrdInsercion(E/S Lista:tLista, E principio, final: entero) (* Precond: principio=P y final=F y P<=F y [P,F] rango(tLista) y Lista= {Li} i [P,F] Poscond: Lista={Vi} y {Vi}=Permutación({Li}) y Vi<=Vi+1 i [P,F]*) Var i, j, aux: entero Inicio Desde i<- principio +1 hasta final hacer aux <- lista[i] j <- i-1 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 86 Algorítmica y Programación I Mientras j>=principio y aux < lista[j] hacer lista[j+1] <- lista[j] j <- j-1 Fin Mientras lista[j+1] <- aux Fin Desde Fin Proc Método de ordenamiento rápido o quicksort https://es.wikipedia.org/wiki/Algoritmo_de_ordenamiento#/media/File:Sorting_quicksort_anim.gif Se basa en el hecho que es más rápido y fácil de ordenar dos listas pequeñas que una lista grande. Consiste en dividir el arreglo en dos partes, una con los elementos menores a un valor de separación, llamado pivote, y otra con los elementos mayores. Así el arreglo queda divido en tres partes: 1. El elemento de separación o pivote. 2. Subvector VI con los elementos menores o iguales. 3. subvector VD que contiene los elementos superiores. Para cada subvector se vuelve aplicar el mismo método hasta que las sublistas tengan dos elementos los cuales se comparan directamente. La solución recursiva de este método consiste en dividir el vector en dos partes, de modo tal que el pivote quede en la posición definitiva y todos los elementos menores de un lado y los mayores del otro lado. Luego aplicar el método para el subvector de la izquierda (desde principio hasta posición del pivote –1) y para el subvector de la derecha (desde la posición del pivote +1 y el final). La recursión finaliza cuando el principio es mayor que el final del subvector. Procedimiento quicksort(E/S Lista:tLista, E principio, final: entero) (* Precond: principio=P y final=F y P<=F y [P,F] rango(tLista) y Lista= {Li} i [P,F] Poscond: Lista={Vi} y {Vi}=Permutación({Li}) y Vi<=Vi+1 i [P,F]*) Var posPivote: entero Divide la lista en dos partes, Inicio desde principio a posPivote – 1, Si principio < final entonces son todos menores a los que Particion(lista, principio, final, posPivote) están de posPivote +1 al final. Quicksort(lista,principio, posPivote-1) El elemento en posPivote está en el lugar correcto. Quicksort(lista, posPivote+1, final) Fin Si Fin Proc Ordenamiento en archivos Mantener los datos ordenados simplifica la tarea de búsqueda. También se requiere para confeccionar listados en los cuales los datos deben figurar en un determinado orden. Soluciones: o o Llevar el conjunto de datos a memoria (en un arreglo) y ordenarlo con cualquiera de los métodos de ordenamiento de listas en memoria. Esto no siempre es posible porque: la capacidad de la memoria es limitada los arreglos deben tener un tamaño fijado antes de compilar. Los archivos pueden crecer dinámicamente. Llevar a memoria sólo el campo que se quiere ordenar y su posición relativa en el archivo (armar el arreglo con la posición (pos) y el campo clave). Ordenar el arreglo. Recorrer el arreglo ordenadamente, accediendo directamente al registro en el archivo correspondiente al campo pos del elemento del arreglo. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 87 Algorítmica y Programación I o Aunque esta solución ocupa menos memoria que la anterior, tiene las mismas limitaciones que ella. Llevar a memoria sólo un subconjunto de registros por vez. Ordenarlo y guardarlo en un archivo intermedio. Luego por cada dos grupos ordenados, fusionarlos en uno sólo. Repetir el proceso de fusión hasta que no queden más subgrupos. Este método consta de una fase de ordenamiento y otra de intercalación. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 88 Algorítmica y Programación I Análisis de los algoritmos de ordenamiento, eficiencia. Por algoritmo "más eficiente" usualmente nos referimos al más rápido. Debido a que los requerimientos de tiempo son usualmente un factor importante cuando se trata de decidir si un algoritmo es lo suficientemente eficiente para ser útil en la práctica.. La eficiencia de un algoritmo de ordenamiento entonces, se mide en función del tiempo que insume la cantidad de comparaciones que deben realizarse para ordenar la lista. Algoritmo Nombre original Complejidad Ordenamiento por selección Selection sort O(n²) Ordenamiento de intercambio o burbuja Bubblesort O(n²) Ordenamiento por inserción o de la baraja Insertion sort O(n²) Ordenamiento rápido Promedio: O(n log n), peor caso: O(n²) Quicksort Métodos de Búsqueda En muchas aplicaciones es necesario buscar un elemento dentro de una determinada estructura de datos (vector, matriz, archivo, etc.). Puede resultar que en la búsqueda encontremos dicho elemento, pudiendo indicar dónde, o que no lo encontremos. La operación de búsqueda puede realizarse con una función que devuelva la posición donde se encuentra el elemento o el valor –1 si no se halla. Datos de entrada: lista y rango donde buscar y el valor a encontrar. Datos de salida: posición donde se encuentra Primero, ultimo Lista Valor posición De acuerdo a las características de la búsqueda, podemos diferenciar dos métodos: búsqueda lineal y búsqueda binaria. Cliente Búsqueda Búsqueda lineal Consiste en recorrer secuencialmente la estructura de datos a partir del primer elemento y comparar cada elemento con el buscado hasta encontrarlo o hasta que no queden elementos para comparar. Si ocurre este último caso, la búsqueda ha terminado sin éxito. Búsqueda lineal sobre un vector no ordenado Funcion BuscSec (E Lista: tlista, E principio, entero) : entero Var i, pos:entero Inicio pos -1 i principio Mientras (pos = -1 y (i final) hacer Si lista[i] = elemento entonces pos <- i Sino i <- i+1 Fin Si Fin Mientras BuscSec <- pos Fin Func Búsqueda lineal sobre un vector ordenado final: entero, E elemento: Precondición: principio=P y final=F y elemento=E y P<=F y [P,F] rango(tLista) y Lista= {Li} i [P,F] PosCondición: (BuscSec = -1 y E no pertenece a {Li}) Precondición: o principio=P y final=F y (BuscSec = N N y [P,F] elemento=E y y P<=F y LN = E) [P,F] rango(tLista) y Lista= {Li} y Li<=Li+1 i [P,F] PosCondición: (BuscSecOrd = -1 y E no pertenece a {Li}) o (BuscSecOrd = N y N [P,F] y LN = E) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 89 Algorítmica y Programación I La eficiencia de la búsqueda lineal puede mejorar si se realiza sobre un vector ordenado, porque sólo será necesario recorrer el vector hasta la posición donde se encuentre el elemento buscado o uno mayor a éste. Funcion BuscSecOrd (E Lista: tlista, E principio, final: entero, E elemento: entero) : entero Var i, pos:entero Inicio pos -1 i principio Mientras (pos = -1 y (i final) y lista[i] < = elemento hacer Si lista[i] = elemento entonces pos <- i Sino i <- i+1 Fin Si Fin Mientras BuscSecOrd <- pos Fin Func Búsqueda binaria Este método requiere que la estructura de datos sobre la que se realizará la búsqueda se encuentre ordenada. Compara el elemento buscado con el elemento central de la estructura. Si el elemento buscado coincide con el elemento central, se termina la búsqueda. Si el elemento buscado es menor que el central, se considera la primera mitad de la estructura, y se ubica el elemento central de dicha mitad, y se lo compara con el buscado. Se repite este proceso hasta que se encuentra el elemento buscado o hasta que no queden más elementos por comparar. Generalmente es mucho más rápido que la búsqueda lineal. Búsqueda binaria sobre un vector ordenado Funcion BuscBinaria (E Lista: tlista, E principio, final: entero, E elemento: entero): entero (* Precondición: principio=P y final=F y elemento=E y P<=F y [P,F] rango(tLista) y Lista= {Li} y Li<=Li+1 i [P,F] PosCondición: (BuscBinaria = -1 y E no pertenece a {Li}) o (BuscBinaria = N y N [P,F] y LN = E) *) Var P,f,m,pos:entero Inicio Inicialmente, el punto medio M es la posición pos -1 central del vector. Si el elemento buscado es mayor que el correspondiente a la posición p principio M, se fija P como la posición siguiente a M f final (se descarta la primer mitad del vector). Si Mientras (pos = -1 y p <= f) hacer por el contrario, el elemento buscado es m <- (p + f) div 2 menor que el de la posición M, se toma como Si lista[m] = elemento entonces F la posición anterior a M, desechándose la pos <- m segunda mitad del vector. El nuevo punto Sino medio M es la posición central del subvector Si lista[m] > elemento entonces comprendido entre las posiciones P y F . f <- m-1 Si al comparar el elemento de la posición M Sino con el buscado encontramos que ambos p<- m+1 coinciden, ha finalizado la búsqueda de Fin Si manera exitosa. Cuando la posición inicial P Fin Si es mayor que la final F significa que el Fin Mientras elemento buscado no se encuentra dentro BuscBinaria <- pos del vector. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 90 Algorítmica y Programación I Fin Func Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 91 Algorítmica y Programación I Búsqueda en archivos Los algoritmos de búsqueda en archivo son los mismos que para arreglos. La diferencia consiste en la forma de obtener un elemento. En el archivo se requiere recuperar (leer) un elemento antes de poder comparar. Cuando se busca un elemento se lo hace por el valor de su campo clave. La operación puede ser más útil si además de devolver la posición en que se encuentra el registro en el archivo, se devuelve también el registro recuperado. Datos de entrada: archivo donde buscar: arch y el valor de clave a encontrar: dato. Datos de salida: posición donde se encuentra y el registro encontrado con todos los datos En este caso, debemos resolverlo con un procedimiento con dos parámetros de salida: la posición y el registro. Cliente arch dato Pos Reg BúsquedaArch Procedimiento BuscarNoOrd(E/S arch: tArch, E dato: entero, S reg:treg, S pos: entero) (* Precond: arch=A y dato=D y A está abierto PosCond: (pos=-1 y dato no pertenece A) o (pos>=0 y pos <= tamaño(A)-1 y reg=R y R.clave=D y posicion(R)=pos ) *) Const NoEsta = -1 (* valor para no encontrado *) Inicio Posicionarse(arch,0) (*posiciono al principio*) pos NoEsta Mientras ( no eof(arch) y (pos= NoEsta) ) hacer Leer (arch, reg) Si reg.clave = dato entonces pos DondeEstoy(arch) -1 Fin Si Fin Mientras Fin Proc Procedimiento BuscarBin(E/S arch: tArch, E dato: entero, S reg:treg, S pos: entero) (* Precond: arch=A y dato=D y A está abierto y ordenado por su clave Poscond: (pos=-1 y dato no pertenece A) o (pos>=0 y pos<=tamaño(A)-1 y reg=R y R.clave=D y posicion(R)=pos *) Const NoEsta = -1 (* valor para no encontrado *) var p,f,m: entero Inicio p 0 f Tamaño (arch) – 1 pos noEsta Mientras (p <= f ) y (pos = noEsta) hacer m (p + f) /2 Posicionarse(arch, m) Leer (arch, reg) Si reg.clave = dato entonces pos m Sino Si reg.clave < dato entonces p m + 1 Sino f m - 1 Fin Si Fin Si Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 92 Algorítmica y Programación I Fin Mientras Fin Proc Análisis de los algoritmos de búsqueda La eficiencia de un algoritmo de búsqueda se mide en función de la cantidad de comparaciones que deben realizarse cuando un elemento a buscar no se encuentra en la lista. Es una medida del tiempo que se tarda, a mayor tiempo, menos eficiente. Para la búsqueda secuencial con datos no ordenados la cantidad de comparaciones es igual a la cantidad de elementos de la lista. Si la lista tiene N elementos, el tiempo de búsqueda es proporcional a N. Para la búsqueda secuencial con datos ordenados la cantidad de comparaciones varía entre un mínimo de uno (si el elemento a buscar es menor que el primero) y un máximo de N (si el elemento a buscar es mayor que el último). En promedio podemos decir que el tiempo de búsqueda es proporcional a N/2. Para la búsqueda binaria el tiempo no es lineal, sino que cuanto más datos tiene la lista, el tiempo crece en menor proporción ya que constantemente la lista es dividida en dos y por cada partición, sólo se hace una comparación. El tiempo de búsqueda es proporcional log2 (N) Promedio de comparaciones para distintos N N 10 100 1000 1.000.000 Sec. No ordenado 10 100 1000 1.000.000 Sec. ordenado 5 50 500 500.000 Binario 4 7 10 20 Método Se puede ver que la búsqueda binaria es un método muy eficiente pero requiere que los datos estén ordenados. Intercalación La intercalación, mezcla o fusión es la operación de unir dos conjuntos de datos ordenados en uno sólo y que se mantenga el orden. Una solución podría ser poner todos los elementos del primer conjunto, a continuación, todos los del segundo y ordenarlos, pero este algoritmo es muy ineficiente ya que no aprovecha el conocimiento de que cada conjunto de datos está ordenado. Un proceso de intercalación eficiente consiste en comparar un elemento de cada lista (comenzando con el primero en cada una), colocar el más pequeño en la nueva lista y tomar el siguiente elemento al que ya fue ubicado. Este proceso se continúa mientras haya elementos en ambas listas para ser ubicados. Cuando una de las listas se termina, se colocan todos los restantes de la otra lista. Podemos tomar consideraciones especiales cuando ambas listas poseen elementos de igual clave. Según el modelo del problema, podemos copiar ambos, no copiar ninguno, elegir uno de ellos por alguna condición o cálculo. Seudocódigo genérico obtener el primer elemento de cada conjunto, lista o archivo (C1, C2) Posicionarse al comienzo del tercer conjunto (C3) Mientras tengo elementos a procesar en los dos (en C1 y en C2) hacer Comparo los elementos. Si son iguales entonces Proceso de acuerdo con el modelo del problema obtengo el siguiente registro de ambos Sino guardo el menor de ellos y obtengo el siguiente registro del que fuera menor FinSi Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 93 Algorítmica y Programación I me posiciono en la próxima posición de C3 Fin Mientras Guardo en C3 los elementos que faltan procesar del conjunto que quedó. Ejemplo de intercalación de listas numéricas Intercalar dos listas de enteros ordenadas en forma ascendente. En caso de elementos iguales entre las listas, poner ambos. Lista1, Lista2 N1, N2 Datos de entrada: las dos listas (lista1 y lista2) y sus respectivos topes, N1 y N2 (asumimos que el rango es de 1 al Lista3 tope). Datos de salida: la nueva lista ordenada con los elementos Cliente Intercalación de ambas: lista3. Si no se pusieran todos los elementos, se requiere también la cantidad de datos colocados en la lista3 como dato de salida. Se requiere que la lista de salida sea lo suficientemente grande como para contener ambas listas. Si las tres son del mismo tipo, no podrán venir ambas listas llenas, sino que la suma de elementos de ambas deberá ser a lo sumo igual a la capacidad física de la lista (Max), de lo contrario la lista de salida tendrá que tener una capacidad doble. Procedimiento Intercalación(E L1,L2:tlista; E n1,n2: entero;S L3:tlista) (* precond: L1[i] < L1[i+1] i [1,n1] y L2[i] < L2[i+1] i [1,n2] y [1,n1+n2] rango(tLista) poscond: L3[i] <= L3[i+1] i [1,n1+n2] *) var Para acceder a un elemento requiero de un índice por i, j, k, t: entero cada lista los cuales deberemos inicializarlos en 1 para inicio posicionarnos en el principio. i <- 1 j <- 1 k <- 1 Mientras (i <= n1 ) y (j <= n2) hacer Si L1[i] < L2[j] entonces Que haya elementos a procesar en una L3[k] <- L1[i] lista significa que su índice será menor o i <- i + 1 igual que su tope. Sino L3[k] <- L[j] j <- j + 1 FinSi k <- k +1 FinMientras Desde t <- i hasta n1 hacer L3[k] <- L1[t] k <- k +1 FinDesde Desde t <- j hasta n2 hacer L3[k] <- L2 [t] k <- k +1 FinDesde Fin Proc Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 94 Algorítmica y Programación I Fusión de archivos Muchas veces nos encontramos con el problema de fusionar dos conjuntos de datos en uno sólo. Los archivos están ordenados por un campo clave, y se desea reunir la información en un nuevo archivo, donde los datos de igual clave figuren una sola vez, quizá con la información de uno de ellos o realizando alguna operación con la información de ambos (por ejemplo, si se reúnen dos archivos de stock, la cantidad de artículos de igual clave será la suma de ambos). El algoritmo es similar al de fusión o intercalación en arreglos. Precondiciones: Arch1 y arch2 están ordenados por campo Arch1 Arch2 clave. Dentro de cada archivo, no hay registros repetidos con igual clave. A1.clave ? A2.clave Fusión < grabo el registro A1 y leo de arch1 = De acuerdo con el problema, decido cual grabar y leo de ambos archivos Nuevo Arch > grabo el registro A2 y leo de arch2 Comparación del algoritmo de fusión en arreglos y en archivos. Ejemplo: Fusionar los stocks de dos sucursales. Los datos de los artículos constan de dos campos: el nro. de artículo que identifica al registro (clave) y la cantidad que hay de ese artículo en el stock. En la fusión, se obtiene un único stock con todos los artículos de ambas sucursales. Los artículos comunes a ambas, aparecen una única vez con una cantidad que es la suma de ambas. El algoritmo de fusión para este problema consiste en: obtener el primer elemento de cada conjunto, lista o archivo (C1, C2) Posicionarse al comienzo del tercer conjunto (C3) Mientras tengo elementos a procesar en los dos (en C1 y en C2) hacer Comparo los elementos. Si son iguales entonces guardo uno de ellos con una cantidad que sea la suma de ambos obtengo el siguiente registro de ambos Sino guardo el menor de ellos y obtengo el siguiente registro del que fuera menor FinSi me posiciono en la próxima posición de C3 Fin Mientras Guardo en C3 los elementos que faltan procesar del conjunto que quedó. En el arreglo se accede a un elemento a través del índice y debemos verificar que este sea menor o igual que el tope. Para obtener otro elemento debemos incrementar el índice. Se sabe que aún quedan elementos sin procesar si el índice al que estamos accediendo es menor o igual que el tope. En el archivo, se accede a un elemento mediante la operación de leer y antes de leer tenemos que asegurarnos que no se haya llegado al final del archivo. Dos leer consecutivos recuperan diferentes registros ya que el indicador interno después de una lectura o escritura se mueve para que el próximo acceso se haga en el siguiente registro. La función finArchivo(arch) devuelve Verdadero cuando no quedan más registros sin leer. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 95 Algorítmica y Programación I La condición del mientras: “tengo elementos a procesar en los dos”, se resuelve en arreglos con: “(i <= tope1) y (j <= tope2)”. ¿Será equivalente a decir en archivos “no finArchivo(arch1) y no finArchivo(arch2) ”? Para algunos problemas esta condición es equivalente pero para otros no. En el algoritmo de búsqueda secuencial reemplazamos el i <= f en la solución para arreglos por no finArchivo (arch) en la solución de archivos. Veamos si podemos hacerlo en el problema de fusión: Imaginemos que los archivos a intercalar tiene los artículos: {1, 5, 9} y {3, 4}. En el ciclo mientras tendrán que haber sido grabados los valores {1, 3, 4} quedando { 5, 9} para grabarse al final. arch1 1 5 arch2 3 4 9 arch3 Antes de entrar al mientras se requiere recuperar los primeros elementos: 1 y 3. Los punteros internos apuntan al 5 y 4 respectivamente. No es eof en ninguno de los dos por lo que se entra al mientras. 1 5 3 4 9 1 De la comparación, se graba el 1 y se lee el 5. Los punteros internos apuntan al 9 y 4 respectivamente. No es eof en ninguno de los dos y se entra al mientras. 1 5 3 4 1 3 9 De la comparación, se graba el 3 y se lee el 4. Los punteros internos apuntan al 9 y eof respectivamente. Como es eof(arch2) no entraría al mientras y sin embargo el registro con valor 4 aún no ha sido procesado. En el caso de la fusión, los registros son leídos en un ciclo y procesados en el siguiente, por eso la condición de fin de archivo es cierta pero el registro aún no ha sido procesado. Para solucionar el problema se puede pensar en una variable lógica (bandera) que detecte si hay o no registro a procesar. Esta bandera es actualizada en cada lectura de registro: es verdadera si pudo leer y falsa si no. Una operación leerSiPuedo puede encargarse de obtener el registro y actualizar la bandera. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 96 Algorítmica y Programación I De esta manera el algoritmo para archivos queda: tipo tReg = registro nroArt: entero cant: entero Fin registro tArch = archivo de tReg Procedimiento LeerSiPuedo(E/S arch: tArch, S reg:tReg; S hay: logico) Inicio si eof(arch) entonces hayfalso sino Leer(arch, reg) hayverdadero finsi fin proc Procedimiento Fusión( E s1,s2,s3: cadena) (* precondicion: Los archivos s1 y s2 existen y sus datos estan ordenados en forma ascendente por su clave poscondición: el archivo s3 está ordenado por su clave y todas las claves de s1 y de s2 se encuentran en s3 una sola vez *) var hay1, hay2: logico arch1, arch2,arch3: tArch reg1,reg2: tReg inicio Abrir(arch1, s1) Abrir(arch2, s2) Crear(arch3, s3) LeerSiPuedo(arch1,reg1,hay1) LeerSiPuedo(arch2,reg2,hay2) Mientras (hay1 ) y (hay2) hacer Si reg1.nroArt < reg2.nroArt entonces Escribir(arch3, reg1) LeerSiPuedo(arch1,reg1,hay1) Sino Si reg1.nroArt = reg2.nroArt entonces reg1.cant <- reg1.cant + reg2.cant Escribir(arch3, reg1) LeerSiPuedo(arch1,reg1,hay1) LeerSiPuedo(arch2,reg2,hay2) Sino Escribir(arch3, reg2) LeerSiPuedo(arch2,reg2,hay2) Fin Si Fin Si Fin Mientras Mientras (hay1 ) hacer Escribir(arch3, reg1) LeerSiPuedo(arch1,reg1,hay1) Fin Mientras Mientras (hay2) hacer Escribir(arch3, reg2) LeerSiPuedo(arch2,reg2,hay2) Fin Mientras Fin Proc Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 97 Algorítmica y Programación I Cortes de control Desde los primeros algoritmos que hemos escrito, podemos observar que en alguna de sus partes aparece una selección dicotómica que bifurca el proceso. Esa selección produce un corte en la secuencia, a partir de una condición que controla por cuál de las opciones debe seguir. Este corte de control puede hacerse mediante una marca (bandera) o comparando variables de control. El corte de control es un concepto práctico y se produce al alterar la secuencia mediante la evaluación de una condición. Si bien en todos los algoritmos que utilizan estructuras de decisión hay cortes de control, generalmente esta denominación es utilizada para referirse a listados con subtotales y a fusión de archivos con más de un nivel de corte de control. En estos casos, el corte de control se produce al alterar la secuencia del cuerpo de un proceso repetitivo para hacer una tarea especial (habiéndose dado una condición específica). Si se da la condición para el corte, entonces se realiza la tarea especial, por ejemplo imprimir el subtotal; y luego, se haya dado o no, se realiza la tarea cotidiana, como imprimir el registro. Aparece una estructura de decisión dentro de una estructura de repetición. Puede haber varios niveles de corte. Por ejemplo, se tiene un archivo con la información de los alumnos de la universidad, ordenados por facultad y dentro de ella, por carrera y se desea imprimir la información del archivo, incluyendo subtotales de cantidad de alumnos por carrera y por facultad. Al finalizar una carrera se imprime el subtotal de carrera. Al finalizar una facultad se imprime los subtotales por Facultad. En cualquier caso, el hecho que cambie la facultad, hace que se deba imprimir subtotales de carrera y de facultad. El corte de control toma la siguiente forma: ... { secuencia previa al corte de control} Si cambia facultad entonces imprimir subtotales de carrera y de facultad Sino Si cambia sólo carrera entonces imprimir subtotales de carrera Fin Si Fin Si ... { continua la secuencia} Listados - Consideraciones Generales o Los listados deben dividirse en hojas que tengan en cuenta el largo del papel (o líneas visibles en la pantalla). o En cada hoja del listado se distinguen tres zonas: encabezado, cuerpo y pie de página. o Cada hoja debiera estar numerada. El número puede aparecer en el encabezado o en el pie de página. o En el encabezado se indica el título general y si los datos son listados en columnas, también deben aparecer los rótulos de ellas. Puede llevar más información como la fecha, el número de hoja, y cualquier otra necesaria para el problema. Hoja 2 El cuerpo es la parte alumnos regulares Hoja 1 más repetitiva: allí mbre Carrera Listado de alumnos regulares aparece la información Encabezado Quevedo Ing. Civil Nro. Nombre Carrera de cada elemento a Perez A.P.U. 1 Juan Perez A.P.U. listar, ocupando uno o Alca A.P.U. 2 Vilma Gómez A.P.U. más renglones por ... ....................... ............... Cuerpo 3 Silvia Garcia Ing. Civil elemento. En algunos Suarez A.P.U. ... ....................... ............... casos, cada cierto 21 Ana Silva A.P.U. grupo de datos, se Pie de Pág muestran subtotales. o Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 98 Algorítmica y Programación I o El pie de página puede tener información o estar vacío: son un grupo de líneas en blanco al final de cada hoja. Si el listado es por pantalla, en la zona reservada para pie de página se suele poner directivas para el usuario, como "presione una tecla para continuar", de modo de mantener la pantalla tanto tiempo como el usuario lo requiera, antes de pasar a la siguiente hoja o pantalla de información. o El listado debe resultar claro para quien lo mire, su aspecto debe ser prolijo. Estructura de un algoritmo de listado El algoritmo consiste en tres partes: o Tareas iniciales (Por Ej. inicialización de variables, abrir archivo o posicionarse en el primer elemento a listar, encabezado de la primer hoja) o Bucle: Mientras hay elementos para listar, si corresponde, lo listo. o Tareas de finalización (por ejemplo, cerrar archivo, imprimir subtotales pendientes y totales finales, pie de página de la última página, etc.) Preguntas frecuentes: Se deben listar todos los elementos? Depende del problema. Si por ejemplo, se está trabajando con un archivo con bajas lógicas (un campo especial del registro indica si está vigente o no), una vez que tengo el registro y antes de imprimirlo verificaré la condición. Esta verificación debe hacerse antes que cualquier otra cosa, pues si no corresponde imprimirse, no verifico si se terminó la hoja ni si debo hacer subtotales. Dónde y cómo se analiza si se completó una hoja? Se necesitará llevar la cuenta de cuantas líneas se van imprimiendo y antes de imprimir preguntarse si hay lugar en la hoja, si no lo hay, realizar el pie de página, saltar a una nueva hoja e imprimir el encabezado. El pie de página para el caso de listados por pantalla, puede consistir en ubicarse en la penúltima línea de la pantalla, mostrar un mensaje como "Oprima Enter para continuar", y quedar en espera del ingreso del Enter Linea El encabezado puede recibir como parámetros las líneas y las hoja hojas (suponiendo que la numeración de páginas se haga en el encabezado). En este caso, las tareas que haría son: Listado Encabezado incrementar las hojas, imprimir las líneas de encabezado, y poner en el valor inicial a líneas. Es apropiado que sea el encabezado el encargado de inicializar las líneas, ya que sabe cuántas líneas utilizó para el mismo. Dónde y cómo se analiza si tengo que imprimir subtotales? La impresión de un subtotal depende de lo que sucede entre un campo del registro actual respecto del anterior. Si por ejemplo, tengo los datos de los alumnos ordenados por carrera y deseo incluir subtotales de cantidad de alumnos por carrera, sabré que tendré que imprimir el subtotal si la carrera del registro actual es diferente a la carrera del registro anterior. Nuevamente, antes de imprimir un registro, verificaré si antes debo imprimir algún subtotal. Para esto debo conservar los datos necesarios del registro anterior. Si los registros estaban en un arreglo L, el registro actual será L[i] y el anterior L[i-1]. Si están en un archivo, debiera utilizar una variable auxiliar para conservar los datos del anterior. Comparar el registro actual con el anterior es posible para todos, menos para el primero, ya que el primero no tiene anterior. Cómo soluciono el caso del primero? Podemos plantear dos soluciones: Solución 1: Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 99 Algorítmica y Programación I Utilizo una bandera: por ejemplo, si está encendida es el primero, si está apagada, no. Antes de empezar la enciendo y una vez que la utilizo (dentro del bucle), la apago. Si esPrimero entonces anterior<- actual esPrimero<-falso Sino me fijo si debo realizar subtotales. Fin Si Solución 2: Antes de empezar, inicializo la variable anterior con los valores del primer registro. De esa manera al procesar el primer registro encuentra que es igual al anterior y no realiza subtotales. Esta última solución es mejor que la anterior, ya que la solución 1 pregunta a cada registro si es el primero, y esto va a ser cierto sólo una vez (la primera). Para evitar la pregunta constante, la solución dos propone que se considere como anterior del primero al mismo registro, y esto lo realiza antes de entrar al bucle. Cuando esta solución se aplica a un conjunto de datos en archivo, debe tenerse en cuenta que para obtener un registro debe leerse del archivo y que la operación leer, recupera un registro y deja al archivo preparado para que la próxima operación se haga sobre el registro siguiente. Esto implica que para inicializar el anterior con el valor del primer registro a listar, se deberá leer del archivo hasta encontrar el primer registro a listar y luego reposicionarse en la posición que se lo encontró, para que ese primer registro sea procesado como cualquiera de los restantes. Cómo se analiza el caso de diferentes niveles de subtotales? Suponiendo que el ordenamiento fuera universidad, y a igualdad de universidad, por facultad y dentro de ella por carrera; un cambio en la universidad del elemento anterior y el actual, debe arrastrar a los demás, es decir, debo imprimir subtotal por carrera, por facultad y por universidad, independientemente que la carrera no haya cambiado (dos universidades podrían tener una misma identificación de carrera o facultad, aunque sus identificaciones fueran iguales, son carreras distintas ya que la identificación completa es universidad-facultad-carrera). En base a este análisis, la estructura de decisión deberá comenzar preguntando si cambió la universidad, si fuera así, imprimir subtotales de las tres (carrera, facultad, universidad), sino si cambió la facultad, imprimir subtotales de carrera y de facultad, sino si cambió la carrera imprimir subtotales de carrera. Qué debe hacerse cuando se detecta que corresponde imprimir subtotales? Las impresión de un subtotal implica: Subtotal1 Subtototal2 linea 1. Imprimir el subtotal (subtotal1) 2. acumular para el subtotal de siguiente nivel (subtotal2) Listado Subtotal 3. poner el subtotal que se acaba de imprimir en cero (subtotal1) 4. incrementar líneas. Observación: el paso 1 debe hacerse siempre antes que los demás y el paso 2 siempre antes que el paso 3. Incrementar líneas podría hacerse en cualquier orden. Es necesario fijarse si tengo lugar en la hoja antes de imprimir un total o subtotal? En principio, sí. Pero esto daría lugar a que puedan quedar hojas que comienzan con subtotales y donde la información del grupo al que se refiere, está en una hoja anterior. Si pretendemos que esto no suceda, y que el subtotal quede siempre en una hoja donde al menos hay un elemento que corresponde al grupo para el que se realiza el subtotal, se debe tomar alguna precaución en la estimación del tope (cantidad de líneas máximas): tomar un tope algo menor que la máxima cantidad real de líneas que puede tener el cuerpo del listado, de manera que, si toca imprimir Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 100 Algorítmica y Programación I subtotales, haya lugar. Habiendo hecho esto, los subtotales se imprimen antes del corte de control por hoja llena, sin necesidad de preguntar si hay lugar. Formato genérico de listados con subtotales //Tareas iniciales Poner en cero los acumuladores y contadores (hojas, subotales, totales, etc.) Ubicarse en el primer registro a listar (abrir el archivo si es necesario) Inicializar adecuadamente los valores de registro anterior. Encabezado (lineas, hoja) //Bucle Mientras hay elementos a listar hacer Tomar un elemento (actual) Si corresponde listarlo entonces Si corresponden subtotales (anterior <> actual) entonces Realizar los subtotales correspondientes anterior <- actual Fin Si Si lineas > tope entonces PieDePagina Encabezado(lineas, hojas) Fin Si Imprimir datos del elemento actual Incrementar lineas Acumular para subtotales Fin Si Fin Mientras // Tareas finales Realizar los subtotales pendientes imprimir total general pie de página cerrar archivo //si corresponde Ejemplos Listado 1: Listar por pantalla, todos los registros del archivo Alumnos, imprimiendo al final la cantidad total de alumnos. El programa que llama al listado, le pasa el nombre del archivo: listado1("C:Alumnos.dat"). Se definen los tipos de datos del problema: Tipos tNombre = cadena[50] tReg= registro Nro: entero Nombre: tNombre Carrera: entero ... fin registro tArch = archivo de tReg Para realizar el pie de página y el encabezado, se definen procedimientos que se encargan de las tareas correspondientes. El procedimiento listado1 recibe el nombre del archivo a listar, se encarga de abrirlo al principio y cerrarlo al final. Como precondición, el archivo debe existir. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 101 Algorítmica y Programación I Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 102 Algorítmica y Programación I procedimiento Listado1(E nombArch: cadena) (*Precondición: nombArch=N y el archivo N existe *) Const Tope=20 Var Arch: tArch Reg: tReg Lineas, hojas: entero Cant: entero Inicio Cant<-0 hojas<-0 Abrir(arch,nombArch) Encabezado(lineas, hojas) Mientras no eof(arch) hacer Leer(arch, reg) Si lineas > tope entonces PieDePagina Encabezado(lineas, hojas) Finsi Mostrar (reg.nro, reg.nombre, reg.carrera) Lineas <- lineas +1 Cant <- cant +1 Fin mientras Mostrar ("Cantidad de alumnos", cant) Cerrar (arch) Fin proc proc Encabezado(S lin: entero,E/S hojas: entero) Inicio LimpiarPantalla Hoja<- hoja +1 Mostrar (" Listado de alumnos Hoja", hoja) Mostrar ("Nro. Alumno Carrera") Mostrar ("_______________________") Lineas <- 3 Fin proc Procedimiento PieDePagina Var s:cadena Inicio Mostrar ("Oprima ENTER para continuar") ingresar s Fin proc Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 103 Algorítmica y Programación I Listado 2: Listar por pantalla, todos los alumnos regulares del archivo Alumnos, con subtotales de cantidad de alumnos por carrera y facultad. El archivo está ordenado por facultad, carrera y legajo. Tipo tNombre = cadena[50] tReg2= registro Facultad: entero Carrera: entero Legajo: entero Nombre: tNombre Regular: logico FinRegistro tArch2 = archivo de tReg2 El programa que llama al listado, le pasa el nombre del archivo: listado2("C:Alumnos.dat") El archivo debe existir. Para que los subtotales reflejen adecuadamente las cantidades, el archivo debe estar ordenado por facultad, carrera y legajo. Procedimiento Listado2(E nombArch: cadena) (*Precondición: nombArch=N y el archivo N existe y está ordenado por facultad, carrera y legajo *) Const Tope=20 Var arch: tArch2 ant, reg: tReg2 lineas, hojas: entero cant, cCarr, cFac: entero Inicio Cant <- 0 hojas <- 0 cCarr <- 0 cFac <- 0 Abrir(arch,nombArch) InicAnterior(arch, ant) Encabezado(lineas, hojas) Mientras no eof(arch) hacer Leer(arch, reg) Si reg.regular entonces Si reg.facultad <> ant.facultad entonces Subtotal("total carrera", cCarr, cFac, lineas) Subtotal("total Facultad", cFac, cant, lineas) Sino Si reg.carrera <> ant.carrera entonces Subtotal("total carrera", cCarr, cFac, lineas) Fin Si Fin Si ant <- reg Si lineas > tope entonces PieDePagina Encabezado(lineas, hojas) Fin Si Mostrar (reg.nro, reg.nombre, reg.carrera) lineas <- lineas +1 cCarr <- cCarr +1 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 104 Algorítmica y Programación I Fin Si Fin Mientras Subtotal("total carrera", cCarr, cFac, lineas) Subtotal("total Facultad", cFac, cant, lineas) Mostrar ("Cantidad de alumnos", cant) Cerrar (arch) Fin Proc Procedimiento InicAnterior(E/S arch: tArch2, S ant: tReg2) (*recupera en Ant, a partir de la posición en que se encuentra, el primer registro que corresponda a un alumno regular. Deja el archivo posicionado en el registro recuperado Precondición: arch= A y A está abierto y posicionado en el lugar a partir del cual quiero obtener el registro. Poscondición: reg= R y R.regular=verdadero y y arch= A’ y A’ está abierto y posicionado en el lugar donde encontré el registro R *) ant var regular:logico arch pos:entero Inicio Listado InicAnterior pos <- dondeEstoy(arch) //inicializo pos por si no entro al mientras regular <- falso Mientras no eof(arch) y no regular hacer Pos <- dondeEstoy(arch) Leer(arch, ant) regular <- ant.regular Fin Mientras Posicionarse(arch, pos) Fin Proc Procedimiento Subtotal(E msg:cadena, E/S subTot1, subTot2, lin: entero) (* Muestro el msg seguido de subTot1, acumulo suTot1 sobre subTot2, pongo a cero subTot1 e incremento lineas Precondición: msg=M y subTot1=T1 y subTot2=T2 y lin=L Poscondición: subTot1=0 y subTot2=T2+T1 y lin=L +1 Msg *) Inicio Mostrar (msg, subTot1) subTot2 <- subTot2 + subTot1 subTot1 <- 0 lin <- lin + 1 Fin Proc Subtotal1 Subtototal2 linea Listado Procedimiento Encabezado(S lin: entero,E/S hoja: entero) Inicio Linea LimpiarPantalla hoja Hoja<- hoja +1 Mostrar (" Listado de alumnos Listado regulares Hoja", hoja) Mostrar ("Nro. Alumno Carrera Facultad") Mostrar ("______________________________________") Lin <- 3 Fin Proc Procedimiento PieDePagina Subtotal Encabezado Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 105 Algorítmica y Programación I Var s:cadena Inicio Mostrar ("Oprima ENTER para continuar") Ingresar (s) Fin Proc Estructuras de datos dinámicas La ventaja de las estructuras dinámicas de datos es que no hay que definir el tamaño antes de usarla, sino que la voy utilizando según la necesito. Los elementos que forman esta estructura se relacionan entre sí mediante campos enlace o puntero y en general no están situados en forma contigua en memoria. A cada uno de esos elementos lo llamamos nodo de la estructura. Un puntero es un dato cuyo contenido es una dirección de memoria y esa dirección de memoria corresponde a la dirección del dato apuntado. Según el tipo de datos de la variable apuntada variará el tipo de puntero. A través de una variable de tipo puntero podemos establecer la conexión o enlace entre los elementos que van a formar la estructura, y según se realizan estos enlaces vamos a tener diferentes tipos de estructuras (listas enlazadas, árboles, grafos). Las estructuras dinámicas pueden ser lineales o no lineales. Son lineales si desde un elemento se puede acceder solamente a otro y no lineales, si puedo acceder a varios. Estructura lineal Ejemplos: Listas, colas, pilas Estructura no lineal Ejemplos: árbol, grafo Declaración de los punteros en distintos lenguajes En C: <tipo> *<var_p> En Pascal: <var_tPuntero>: ^<tipo> En pseudocódigo: <var_tPuntero>: puntero a <tipo> Ejemplo: Ejemplo: Ejemplo: int *p P: ^integer P: puntero a entero Acceder a un campo de un registro a través de un puntero En C: P nombre En Pascal: p^.nombre En pseudocódigo: p^.nombre Gestión de la memoria dinámica Siempre que se hace una inserción en una estructura dinámica, tenemos que pedir tantos bytes de memoria dinámica como ocupa el nodo de la estructura dinámica, y cuando borramos un elemento, Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 106 Algorítmica y Programación I tendremos que liberar la memoria ocupada por ese elemento. Para conseguir este manejo, todos los lenguajes de programación tienen dos instrucciones que permiten reservar y liberar memoria dinámica. En Pascal : new , dispose En C : malloc , free En C++ : new , delete La instrucción de reserva de memoria llevará un único argumento, que será el tipo de datos para el que se hace la reserva de memoria, y según el tamaño de ese tipo, esa instrucción sabrá el número de bytes que tiene que reservar, devuelve es un puntero al comienzo de la zona que se ha reservado. Este valor devuelto será el que tenemos que asignar a una variable de tipo puntero al tipo de datos para el que se hace la reserva. La función liberar memoria lleva un único argumento que es un puntero. Libera la memoria que ocupa el dato apuntado por ese puntero. Var p: puntero a <tipo> P <- reservo_mem (<tipo>) Lib_mem (<var_puntero>) El valor nil Sirve para indicar que una variable puntero no tiene asignada dirección, es decir apunta a nada. Guando se solicita memoria y no hay disponible, la función de gestión devuelve el valor Nil. La operación liberar memoria, pone en nil el puntero que se pasa por parámetro. Ejemplo de lista dinámica Tipo TPuntNodo = puntero a tNodo TNodo = Registro Info: entero Sig: TPuntNodo Fin registro lista Var Lista: tPuntNodo Lista es una variable estática que servirá para tener acceso al comienzo de una lista dinámica, que irá cambiando de tamaño en la medida que se agreguen o quiten elementos. Vamos a definir algunas operaciones para trabajar con la lista: CrearLista( lista) crea la lista vacía Procedimiento CrearLista( S Lista:tPuntNodo) (* Poscondicion: Lista = Nil *) Inicio Lista <- nil Fin Largo(lista): devuelve la cantidad de elementos de la lista. Funcion Largo( E Lista:tPuntNodo): entero (* Precondicion Lista= L poscondicion: Largo = N y cantidad de elementos de L es N*) var p:tPuntNodo cant: entero Inicio p <-Lista cant <-0 Mientras p <> nil hacer Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 107 Algorítmica y Programación I cant <- cant +1 p <- p^.sig Fin Mientras Largo <- cant Fin Insertar(lista, elem): inserta un elemento con la información elem en el orden al campo info. Si la lista está vacía o es menor que el primero, inserta al principio, con lo cual lista pasa a apuntar el nuevo elemento y éste al que apuntaba lista. Si no es así, debo encontrar el elemento anterior y hacer que ese apunte al nuevo y el nuevo al que apuntaba aquel. Procedimiento Insertar( E/S Lista:tPuntNodo, E: elem) (* Precondicion Lista=L y largo(Lista) = N y elem=E Poscondicion: (largo(Lista) = N +1 y E pertenece a L ) o no hubo memoria *) Var ant, p, nuevo:tPuntNodo encontrado: logico Inicio nuevo <- reservo_mem(tNodo) Si nuevo <>nil entonces nuevo^.info <- elem // completo el campo info con el valor recibido Si lista = nil entonces Enganchar(lista,nuevo) Sino Si lista^.info > elem entonces Enganchar(lista,nuevo) Si no p<- Lista // Apunto al primer elemento Repetir ant <- p // guardo en ant el actual p <- P^.sig // avanzo el puntero del actual Hasta p=nil o p^.info > elem hacer Enganchar(ant,nuevo) Fin Si Fin Si Fin Si Fin Proc Procedimiento Enganchar( E/S ant, nuevo:tPuntNodo) Inicio nuevo^.sig <- ant // hago que el siguiente al nuevo elemento sea el que estaba primero ant <- nuevo // hago que al nuevo elemento Fin Proc QuitarPrimero(lista) quita el primer nodo de la lista si puede. Procedimiento QuitarPrimero( E/S Lista:tPuntNodo) (* Precondicion Lista=L y largo(Lista) = N Poscondicion: (largo(Lista) = N -1 o Lista=L *) Var p:tPuntNodo Inicio Si lista <>nil entonces p <- lista // resguardo la direccion a liberar Lista <- lista^.sig // hago quela lista apunte al siguiente elemento Lib_mem(p) // libero la memoria ocupada Fin Si Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 108 Algorítmica y Programación I Fin Proc Ejemplo de secuencia de código: CrearLista(Lista) Lista Insertar(Lista, 90) Lista Insertar(Lista, 60) 90 Lista QuitarPrimero(Lista) 90 60 Lista 90 60 Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 109 Algorítmica y Programación I Lenguaje Pascal Pascal fue diseñado alrededor de 1970 por Niclaus Wirth, quien lo diseñó para ser un lenguaje cómodo para la enseñanza de los fundamentos de la programación. Como un lenguaje de programación educativo, Pascal ha mostrado gran capacidad y ha sido ampliamente adaptado para programación de sistemas y aplicaciones. Es un lenguaje simple, compacto, fácil de aprender en poco tiempo, provee soporte efectivo para programación estructurada y con una gran variedad de tipos de datos. Estructura de un Programa en Pascal Los programas Pascal tienen definidas dos partes: declarativa y ejecutiva. En la primera debe aparecer todo lo que se usará en la segunda, de lo contrario se detecta como desconocido y no podrá ejecutarse. En la parte declarativa se enuncian procedimientos, funciones, variables, constantes y nuevos tipos de datos estructurados. Algunas implementaciones de pascal utilizan unidades, las cuales deben declararse también antes de poder ser usadas. Una unidad (Unit) es un conjunto de constantes, tipos de datos variables, procedimientos y funciones. Cada unidad es como un programa independiente Pascal o bien una librería de declaraciones que se pueden poner en un programa y que permiten que éste se pueda dividir y compilar independientemente. Contiene uno o más procedimientos, funciones constantes definidas y a veces otros elementos. Se puede compilar, probar y depurar una unidad independientemente de un programa principal. Una vez que una unidad ha sido compilada y depurada, está lista para ser usada sin necesidad de volver a compilar y los procedimientos, funciones y constantes definidos en esa unidad pueden ser utilizados por cualquier futuro programa que se escriba sin tener que ser declarados individualmente en el mismo, basta con declarar que se utiliza dicha unidad. PROGRAM <identificador> USES <declaración de unidades> Graph, Crt; // indica que se utilizará las unidades Graph y Crt CONST <definiciones de constantes> MAX = 100; TYPE <declaración de tipos de datos definidos por el usuario> tLista = ARRAY [1..MAX] OF INTEGER; VAR <definiciones de variables> lista: tLista; <definiciones de procedimientos y o funciones> PROCEDURE <nombreProc>(<lista_parámetros_formales>) <declaraciones> BEGIN <lista de acciones> END; FUNCTION <nombreFun>(<lista_parámetros_formales>): <tipo_Resultado> <declaraciones> BEGIN <lista de acciones> // incluyendo una del tipo <nombreFun>:= resultado> END; BEGIN <lista de acciones> END. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 110 Algorítmica y Programación I Los identificadores se eligen siguiendo las mismas reglas establecidas para nuestro seudocódigo: Comienzan con una letra, pueden tener letras, dígitos o el carácter subrayador y no se distinguen mayúscula de minúscula. Todas las sentencias finalizan con punto y coma. Los bloques de sentencias o sentencia compuesta comienzan con BEGIN y finalizan con END. El cuerpo del programa principal termina con END seguido de un punto, mientras que el cuerpo de una función o de un procedimiento con END seguido de un punto y coma. Si una función o un procedimiento no requieren parámetros, no se colocan los paréntesis. El pasaje de parámetros es por valor (no requiere indicación especial) y por referencia (se precede al parámetro formal con la palabra VAR). La lista se conforma de la misma manera que la hacemos en el pseudocódigo, utilizando parámetros por referencia para los de salida, entrada-salida y arreglos. La devolución del resultado de la función se hace como en el pseudocódigo, asignando al nombre de la función el valor a retornar. Los comentarios en Pascal se encierran entre llaves o secuencias paréntesis-asterisco: { esto es un comentario Pascal} (* esto también es un comentario Pascal *) El mínimo programa Pascal consistiría en: PROGRAM vacio BEGIN END. Pascal permite anidamiento de definiciones de funciones y procedimientos: dentro del ambiente de declaraciones de una función o procedimiento, pueden definirse funciones o procedimientos, además de las constantes, tipos o variables, los cuales serán sólo conocidos por el módulo que contenga la definición. Paralelo entre el seudocódigo y Pascal Aspecto Pseudocódigo Pascal Asignación <- := Relacionales =, <>, >, <, >=, <= =, <>, >, <, >=, <= Operadores lógicos No, y, o NOT Operadores aritméticos + Tipos de datos: Entero Real PuntoFijo Lógico Carácter Cadena Arreglo [1..Max] de real Registro <campo>: <tipo> Fin registro Conjunto de <tipo> Archivo de <tipo> Puntero de <tipo> - * / div mod + - AND * OR / DIV MOD INTEGER; REAL; --(NO EXISTE) BOOLEAN; CHARACTER; STRING; ARRAY [1..Max] OF REAL; RECORD <campo>: <tipo>; END; SET OF <TIPO> FILE OF <tipo>; ^<tipo> Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 111 Algorítmica y Programación I Aspecto Pseudocódigo Pascal Operaciones de entrada salida Ingresar (edad) READ (edad) o READLN (edad) Mostrar (“Edad=”,edad) WRITE (“Edad=”,edad) o WRITELN(“Edad=”,edad) Si <condición> entonces <sentencia> Fin si IF <condición> THEN <sentencia>; Decisión simple Pascal espera sentencia simple, si hay más de una deben formarse una sentencia compuesta, encerrándolas entre marcas BEGIN - END ! Decisión doble Si <condición> entonces <sentencia1> ... <sentenciaN> Fin si IF <condición> THEN BEGIN <sentencia1>; ... <sentenciaN>; END; Si <condición> entonces <sentenciaV> Sino <sentenciaF> Fin si IF <condición> THEN <sentenciaV> ELSE <sentenciaF>; Según <Expresión> hacer <cte1>: <accion1> ... <cteN>: <accionN> Fin Según CASE <Expresión> OF <cte1>: <accion1>; ... <cteN>: <accionN>; END; Según <Expresión> hacer <cte1>: <accion1> ... <cteN>: <accionN> Sino <accionD> Fin Según CASE <Expresión> OF <cte1>: <accion1>; ... <cteN>: <accionN> ELSE <accionD>; END; Mientras <condición> hacer <sentencia> Fin Mientras WHILE <condición> DO <sentencia>; Pascal espera sentencia simple, si Mientras <condición> hacer <sentencia1> hay más de una deben ... formarse una sentencia <sentenciaN> compuesta, Fin Mientras encerrándolas entre marcas BEGIN - END WHILE <condición> DO BEGIN <sentencia1>; ... <sentenciaN>; END; Iteración Condicional con postesteo REPEAT Observar que la sentencia previa al ELSE NO termina en punto y coma. ! Decisión Múltiple ! Observar que la sentencia previa al ELSE NO termina en punto y coma. Iteración Condicional con pretesteo ! Repetir <sentencia1> ... <sentenciaN> hasta <condición> <sentencia1>; ... <sentenciaN>; UNTIL <condición>; Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 112 Algorítmica y Programación I Aspecto Pseudocódigo Pascal Iteración con contador Para i<- 1 hasta 10 hacer <sentencia> Fin para FOR i:= 1 TO 10 DO <sentencia>; Pascal espera sentencia simple, si Para i<- 10 hasta 1 con hay más de una deben paso=-1 hacer formarse una sentencia <sentencia> compuesta, Fin para encerrándolas entre marcas BEGIN - END Para i<- 2 hasta 10 con paso= 2 hacer <sentencia> Fin para ! FOR i:= 10 DOWNTO 1 DO <sentencia>; No se puede realizar en Pascal pasos que no sean 1 o –1. Debiera realizarse con un WHILE Precedencia de operadores en Pascal La precedencia de los operadores varía de un lenguaje a otro y es muy importante conocerla para evitar errores cuando se utiliza una expresión compuesta en la que intervienen distintas operaciones. El orden de precedencia en Pascal es: Orden Operación 1 Paréntesis () 2 Operadores unarios not, 3 Operadores multiplicativos *, /, div, mod, and 4 Operadores aditivos +, -, or 5 Operadores relacionales =, <>, <, >, <=, >=, in Esto significa que si quisiéramos expresar si 3 + 2 es mayor que 5 – 2 y 6 es menor que 7, debemos necesariamente utilizar algunos paréntesis pues sino daría error en la compilación: 3 + 2 >= 5 – 2 AND 6 < 7 -> La primer operación que intenta resolver es 2 AND 6 y se produce un error de tipo pues el AND requiere operandos lógicos y tiene operandos enteros. Para este caso, necesariamente debemos utilizar paréntesis para que la operación AND sea la última en resolverse. (3 + 2 >= 5 – 2) AND (6 < 7) -> La primer operación que intenta resolver es el primer paréntesis, y dentro de él la suma y resta, por lo restaría evaluar 5 >= 3 que da verdadero. Luego realiza el segundo paréntesis dando también verdadero. Y finalmente el verdadero AND verdadero que produce como resultado final verdadero. Otros ejemplos: Ejemplo Resultado Comentario 3+2*5 13 Se realiza primero el producto (3 + 2) * 5 25 Se realiza primero el paréntesis 3<2+5 Verdadero Se realiza primero la suma 3 < 2 + 5 AND 5 > 7 Error Se realiza primero el AND pero los operandos son numéricos y no puede resolverlo (3 < 2 + 5) AND (5 > 7) Falso Se realiza primero cada paréntesis y luego el AND. En el primer paréntesis se realiza primero la suma y luego el relacional, dando verdadero, el segundo paréntesis da falso y finalmente el AND da falso. Falso OR NOT Verdadero Falso Realiza primero el NOT Verdadero y luego el Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 113 Algorítmica y Programación I OR. En este caso no se necesita el paréntesis. NOT Falso AND Verdadero NOT (Falso AND Verdadero) ! Verdadero Realiza primero el NOT Falso y luego el AND. En este caso si se utilizara paréntesis, cambiaría la expresión pero casualmente daría el mismo resultado. Primero hace el AND que le da Falso y luego el NOT que lo convierte a Verdadero ¡No olvidarse de encerrar en paréntesis los operandos lógicos!!!. En Pascal generalmente resulta necesario poner en paréntesis las expresiones de los operandos lógicos. Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 114 Algorítmica y Programación I Compiladores de Pascal Varios compiladores de Pascal están disponibles para el uso del público en general, entre ellos podemos mencionar a: o Compilador GNU Pascal (GPC), escrito en C, basado en GNU Compiler Collection (GCC). Se distribuye bajo licencia GPL. o Free Pascal está escrito en Pascal (el compilador está creado usando Free Pascal), es un compilador estable y potente. También distribuido libremente bajo la licencia GPL. Este sistema permite mezclar código Turbo Pascal con código Delphi, y soporta muchas plataformas y sistemas operativos. o Turbo Pascal fue el compilador Pascal dominante para PCs durante los años 1980 y hasta principios de los años 1990, muy popular debido a sus magníficas extensiones y tiempos de compilación sumamente cortos. Actualmente, versiones viejas de Turbo Pascal (hasta la 7.0) están disponibles para descargarlo gratuito desde el sitio de Borland (es necesario registrarse). o Dev Pascal es un entorno integrado de desarrollo para crear programas en Pascal basados en Windows o en consola MS-DOS, utilizando compiladores de Pascal gratis o de código abierto. También puede manejar un depurador interno, que podrás descargar por separado en su misma página web. o Delphi es un producto tipo RAD (Rapid Application Development) de Borland. Utiliza el lenguaje de programación Delphi, descendiente de Pascal, para crear aplicaciones para la plataforma Windows. Las últimas versiones soportan compilación en la plataforma .NET. o Turbo Pascal proporciona siete unidades estándar para el uso del programador: System, Graph, DOS, Crt, Printer, Turbo3 y Graph3. Las cinco primeras sirven para escribir sus programas y las dos últimas para mantener compatibilidad con programas y archivos de datos creados con la versión 3.0 de Turbo Pascal. Las siete unidades están almacenadas en el archivo TURBO/.TPL (librería de programas residente propia del programa Turbo Pascal). La versión 7.0 introdujo dos nuevas unidades WinDos y Strings. Archivos en Turbo Pascal y en Dev Pascal El tipo de dato archivo suele variar la forma de implementación respecto de la definición estándar del lenguaje Pascal, ofreciendo mejores características. Tanto en dev Pascal como en Turbo Pascal existen tres tipos de archivos: de texto (cada elemento es un caracter), tipados (typed), los elementos son todos del mismo tipo, y sin tipo (untyped), los elementos pueden tener cualquier formato. El primero sólo admite acceso secuencial, mientras que los otros dos admiten tanto acceso secuencial como directo. El constructor File permite crear tipos de datos y variables para trabajar con archivos. La palabra reservada text representa al tipo de archivo de texto. Ejemplos: Type tRegAlum= Record Nro: integer; Nombre: tNombre; Carrera: Integer; ... end; tArchAlum = File of tRegAlum; { defino un tipo de archivo tipado) Var Alumnos: tArchAlum; {defino una variable para trabajar con un archivo tipado} MisNotas: text; {defino una variable para trabajar con archivo de texto} MiArchivoSinTipo: File; {defino una variable para trabajar con un archivo sin tipo} Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 115 Algorítmica y Programación I Operaciones para archivos en Turbo Pascal / Dev Pascal Las operaciones de abrir, crear y anexar vistas en pseudocódigo se resuelven en dos instrucciones pascal: una, común a los tres modos, establece la relación entre el nombre físico y la variable archivo: Assign(varArch, nombArch) Esta instrucción no da error, ya que no controla si el archivo existe o no. El equivalente a la operación específica de crear, se da con la operación rewrite(varArch), previamente hubo que haber hecho el assign. Rewrite requiere como parámetro la variable de tipo archivo ya vinculada con el nombre físico, crea el archivo vacío: si existía lo destruye. El equivalente a la operación específica de abrir, se da con la operación reset(varArch), previamente hubo que haber hecho el assign. Reset requiere como parámetro la variable de tipo archivo ya vinculada con el nombre físico, el archivo debe existir y se posiciona al comienzo. Si no existe el archivo da un error. El equivalente a la operación específica de anexar, sólo válida para archivos de texto, se da con la operación append(varArch), previamente hubo que haber hecho el assgn. Append requiere como parámetro la variable de tipo archivo ya vinculada con el nombre físico, si el archivo no existe, da error y si existía lo abre y se posiciona al final. Las restantes operaciones vistas en pseudocódigo tienen un equivalente directo en TurboPascal y en dev Pascal, con idéntico comportamiento. Operaciones para archivos de texto en TurboPascal y Dev Pascal En pseudocódigo En TurboPascal / Dev pascal Var arch: tTexto s: caracter Var arch: text s: char Crear(arch, nombArch) Assign(arch, nombArch) Rewrite(arch) Abrir(arch, nombArch) Assign(arch, nombArch) Reset(arch) Anexar(arch, nombArch) Assign(arch, nombArch) Append(arch) Leer (arch, s) Read (arch, s) LeerLN(arch, s) Readln(arch, s) Escribir (arch, s) Write (arch, s) EscribirLN(arch, s) Writeln(arch, s) esFinLinea (arch) Eoln(arch) esFinArchivo (arch) Eof(arch) Operaciones para archivos tipados en TurboPascal y Dev Pascal En pseudocódigo Tipos TReg = registro .... FinRegistro TArch = archivo de tReg Var Arch: tArch reg: tReg En TurboPascal / Dev pascal Type TReg = record .... end TArch = File of tReg Var Arch: tArch reg: tReg Crear(arch, nombArch) Assign(arch, nombArch) rewrite(arch) Abrir(arch, nombArch) Assign(arch, nombArch) reset(arch) Leer (arch, reg) read (arch, reg) Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 116 Algorítmica y Programación I En pseudocódigo En TurboPascal / Dev pascal Escribir (arch, reg) Write (arch, reg) esFinArchivo (arch) Eof(arch) tamañoArchivo (arch) FileSize(arch) dondeEstoy (arch) FilePos(arch) Posicionarse (arch, pos) seek(arch, pos) Manejo de errores de entrada salida en TurboPascal y Dev Pascal Tanto Turbo Pascal como Dev Pascal admiten directivas al compilador para activar o no determinados controles de errores. Normalmente el compilador tiene activado el control de errores de entrada salida, por lo que al abrir un archivo que no existe, da un error y finaliza la ejecución. Lo mismo sucede si se ingresa un punto o cualquier carácter no numérico cuando se espera por un valor entero. Esta directiva se activa con el comentario al comienzo de una línea: {$I+} y se desactiva con {$I-} Al producirse un error, turbo pascal guarda en una variable global, de nombre IOResult el código de error. Si desactivamos el control de errores antes de realizar la instrucción que puede provocar error e inmediatamente después consultamos por el valor IOResult, si este es distinto de cero indica que hubo error. Así podremos tomar una acción correctiva, si fuera necesario. No debemos olvidarnos de activar el control de errores nuevamente. Ejemplos: Para consultar si un archivo existe, y así evitar errores queriéndolo usar: Crearemos en TurboPascal una función que retorne verdadero si el archivo existe y falso si no. Function existeArch(nombre:string):boolean; Var F:File; Begin Assign(f, nombre); (* vincula f con el nombre de archivo a abrir*) {$I-} (* desactivo el control de errores de E/S*) reset(f); (* intento abrirlo *) close(f); (* intento cerrarlo*) existeArch:= IOResult = 0; (* Si no hubo error => existe el archivo*) {$I+} (* activo el control de errores *) End; Ejemplo de uso: If existeArch("Alumnos.dat") then { abrirlo} else {crearlo} Para obtener un valor entero de la entrada estándar: Crearemos en TurboPascal una función que retorne el valor entero (y rechace valores no enteros). Function obtenerEntero(msg:string):integer; Var n: integer; Begin {$I-} Repeat writeln (msg); read(n); until IOResult = 0; {$I+} obtenerEntero:= n; End; Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 117 Algorítmica y Programación I Otra forma, haciendo uso de la operación val: El procedimiento Val (E Cad: cadena, S Num: entero o real, S CódigoError: entero) intenta convertir la cadena Cad en un valor entero o real según sea el tipo del parámetro Num. Si la conversión se pudo hacer devuelve su valor en Num y cero como de CódigoError, de lo contrario devuelve un valor distinto de cero como código de error y Num carece de significado. Haciendo uso de val, podemos reescribir la función de la siguiente manera: Function obtenerEntero(msg:string):integer; Var Cod, n: integer; s: string; Begin Repeat writeln (msg); read(s); val(s, n, Cod) until Cod = 0; obtenerEntero:= n; End; Cátedra: Algorítmica y Programación I Profesor: Dr. Diego Andrés Firmenich Hoja 118 Algorítmica y Programación I Bibliografía Algoritmos y Estructuras de datos, Nicklaus Wirth, Prentice Hall, 1986 Algoritmos, Datos y Programas, con Aplicaciones en Pascal, Delphi y Visual Da Vinci, De Giusti et Al, Prentice Hall, 2001 An introduction and problem solving with Pascal, Schneider, M.; Perlman,D.; Weingart,S. , Wiley, 1978. Cómo plantear y resolver problemas, Polya,G. , Ed. Trillas, México,1970. Estructuras de Datos y Algoritmos, Alfred Aho, John Hopcroft y Jeffrey Ullman. Addison Wesley Publishing Company. EUA. 1988. Fundamentos de informática, lógica, resolución de problemas, programas y computadoras. Autores Allen B. Tucker, W. James Bradley, Robert D. Cupper, David K. Garnick Editorial McGraw-Hill Año 1994 Fundamentos de Programación, Algoritmos y Estructuras de Datos. Luis Joyanes Aguilar, Mc Graw Hill, 2008 Introducción a la Programación y a las Estructuras de datos, A. Gioia-S. Braunstein, Eudeba, 1986 Introduction a la programmation. T.L Algorithmique et Languages, Biondi,J.; Clavel,G. , 2da. Edition. Masson, 1984. Introduction to Pascal, Welsh,J.; Elder,J. , Prentice Hall, 1982. Metodología de la programación, Luis Joyanes Aguilar, Mc. Graw Hill,1992 Pascal, Dale,N,; Orshalick , Mc Graw Hill, 1986. Pascal. User manual and report, Jensen,K.; Wirth,N. , Springer - Verlag. Problem solving and computer programming, Grogono,P.; Nelson,Sh , Addison-Wesley, 1982. Programación en Pascal, 4ª ED., Luís Joyanes Aguilar, Editorial McGraw-Hill, 2006 Programación en Pascal, Grogono, P. , 1986 Addison Wesley. Una Introducción a la Programación. Un Enfoque Algorítmico, J. García Molina y otros, Thomson, 2005