Informática I Ing. Z. Luna Este apunte es sólo una ayuda o complemento para el estudio de la asignatura. Se ha intentado incorporar muchos ejemplos prácticos y bastantes ejemplos de aplicación a la ingeniería, si bien el curso es del primer año, primer semestre de la carrera, por lo cual los alumnos cuentan con muy pocos conocimientos que tengan que ver con el ciclo profesional. Este apunte no reemplaza a las clases (Teoría, Práctica, Laboratorio), sólo complementa. Las Unidades 1, 2 y 3 son un compendio de varios apuntes de docentes de la cátedra. 1 UNIDAD 1.- FUNDAMENTOS DE LA INFORMÁTICA. ¿Qué es la Informática? Conceptos de algoritmo, procesador, acciones primitivas y no primitivas. Algoritmos y programas. Problemas algorítmicos. Características de los algoritmos: legibilidad, eficiencia y corrección. Datos e Información. Representación interna de la información. Sistemas de numeración. El sistema decimal. El sistema binario o digital. Justificación de su uso. Sistema hexadecimal. Conversiones entre sistemas. Representación interna de números enteros: Técnica de Complemento a 2. Representación interna de números reales: punto flotante. Representación interna de datos alfanuméricos. Código ASCII. Informática: tiene su raíz en el francés informatique y es la designación castellana del inglés Computer Science. Según nuestro diccionario se define como el conjunto de conocimientos científicos y técnicas que hacen posible el tratamiento automático de la información por medio de computadoras. Qué se entiende por información? Datos e información son equivalentes? Datos: como tales se entiende el conjunto de símbolos usados para representar un valor numérico, un hecho, una idea o un objeto. Individualmente los datos tienen un significado puntual. Como ejemplo podemos mencionar el número de CUIL que cada empleado debe tener para sus aportes jubilatorios, un número de teléfono, la edad de unas persona, etc. Información: Por tal se entiende un conjunto de datos procesados, organizados, es decir significativos. La información implica tanto un conjunto de datos como su interrelación. Dependiendo de esta última el mismo conjunto de datos suministra diferente información. Por ejemplo, imaginemos los datos originados en un seguimiento de los alumnos de este curso de su carrera universitaria y profesional: − Nombre − Edad − Carrera universitaria − Promedio académico − Salario profesional Representación de la información Habiendo definido el concepto de información el problema es cómo representarla para poder manejarla en forma automática. Es posible idear muchas formas de hacerlo, pero la clasificación básica nos lleva a una distinción entre técnicas analógicas y digitales. Veamos la diferencia: Representación analógica: Cuando una magnitud física varía continuamente para representar la información tenemos una representación analógica. Por ejemplo, el voltaje en función de la presión producida por la voz en un micrófono. Representación digital: En este caso la información es divida en trozos y cada trozo se representa numéricamente. Lo que se maneja al final es un conjunto de números. La cantidad de trozos en que se divide lo que se quiere representar está relacionado con la calidad de la representación. Un ejemplo actual de esto lo constituyen las cámaras digitales (1, 2, 3.2, 4.1 o más Mega Pixels por foto). En las computadoras modernas toda la información está almacenada digitalmente, desde los números al texto pasando por el audio o el video. Números - sistema binario (conveniencia y simplicidad) - el sistema binario al igual que el decimal es un sistema posicional 2 Decimal 0 1 2 3 Binario 0 1 10 11 Texto - está representado por un código numérico. Cada caracter (letras mayúsculas y minúsculas, signos de puntuación signos especiales como #, @, & etc.) tienen asociado un valor numérico. - Estos valores numéricos son arbitrarios - Código ASCII (American Standard Code for Information Interchange) Caracter / Código Caracter / Código @ 64 $ 36 ……………………………………………………. Conceptos básicos Caracter / Código / 47 Caracter / Código á 160 Un dígito binario se denomina bit (contracción de binary digit). El conjunto de 8 bits se denomina byte. Es interesante saber cuál es el mayor número que se puede representar con un cierto número de bits. Puesto que con un bit se puede representar dos posibilidades, con 2 bits tendremos 4 posibilidades (2x2) y en general tendremos 2N, véase la lista siguiente: Bits Posibilidades 1 21=2 2 22=4 3 23=8 4 24=16 5 25=32 6 26=64 7 27=128 8 (1 byte) 28=256 ... … 2 byte 216=65536 4 byte 232=4294967296 8 byte 264=18446744073709551616 Múltiplos: Kilobyte, MegaByte, Gigabyte, etc. ceptos básicos Definiciones Especificación: se denomina al proceso por el cual se analiza y determina en forma clara y concreta el objetivo que se desea. Abstracción: es proceso por el cual se analiza el mundo real para interpretar los aspectos esenciales de un problema y expresarlo en términos precisos 3 Modelización: se denomina al conjunto que implica abstraer un problema del mundo real y simplificar su expresión, tratando de encontrar los aspectos principales que se pueden resolver (requerimientos), los datos que se han de procesar y el contexto del problema. Lenguaje de programación: es el conjunto de instrucciones permitidas y definidas por sus reglas sintácticas y su valor semántico, para expresar la solución a un problema Programa: es un conjunto de instrucciones ejecutables sobre una computadora, que permite cumplir una función específica. Etapas en la resolución de problemas con computadora − Análisis del problema, se analiza el problema en su contexto del mundo real. Deben obtenerse los requerimientos del usuario. El resultado de este análisis es un modelo preciso del ambiente del problema y del objetivo a resolver. Un componente importante de este modelo son los datos a utilizar y las transformaciones de los mismos que llevan al objetivo. − Diseño de una solución, a partir del modelo se debe definir una estructura de sistema de hardware y software que lo resuelva. El primer paso en el diseño de la solución es la modularización del problema, es decir la descomposición del mismo en partes que tendrán una función bien definida y datos propios (locales). A su vez, debe establecerse la comunicación entre los diferentes módulos del sistema propuesto. − Especificación de algoritmos, la elección del algoritmo adecuado para la función del módulo es muy importante para la eficiencia posterior del sistema en su conjunto. − Escritura del programa, un algoritmo es una especificación simbólica que se debe convertir en un programa real en un lenguaje de programación concreto. El programa escrito en un lenguaje de programación determinado debe traducirse, a su vez, al lenguaje de la máquina. Esta traducción (que es realizada por programas especializados) permite detectar y corregir los errores sintácticos que se comentan en la escritura del programa. − Verificación, de forma de ver si el programa conduce al resultado deseado con los datos representativos del mundo real. Algoritmos Un algoritmo es un conjunto finito, ordenado de reglas o instrucciones bien definidas tal que siguiéndolas paso a paso se obtiene la respuesta a un problema dado. Un algoritmo debe: − Estar compuesto de acciones bien definidas − Constar de una secuencia finita de operaciones − Finalizar en un tiempo finito Por ejemplo se desea calcular la superficie y el perímetro de un círculo en función de su radio. Entrada: radio del círculo Proceso: cálculo de la superficie del círculo y de la longitud de la circunferencia Salida: valor de la superficie y perímetro 4 Entrada: conjunto de información o datos que son necesarios para que el algoritmo lleve a cabo su tarea. Proceso: contiene la descripción de los pasos a seguir para resolver el problema. Programa: Es el algoritmo codificado. Salida: está constituida por los resultados que se obtienen al ejecutar el proceso en función de los datos de entrada. Nuestro procesador: la computadora • su tarea es procesar información • tratamiento de la información en forma automática • sólo puede realizar acciones elementales, tales como − operaciones básicas +, -, /, * . . . . − operaciones relacionales <, >, <=, >= . . . . − almacenar información − repetir grupos de acciones − elegir uno de entre dos o mas caminos El procesador trabaja todo en la memoria. Todo lenguaje de programación contiene CONSTRUCTORES específicos (palabras claves reservadas) las cuales permiten indicarle al procesador las operaciones básicas en dicho lenguaje. Constructores básicos: - tipos de datos - variables - operadores - operador de asignación En una primera parte del curso expondremos todos los conceptos desde un punto de vista genérico. Es decir, los conceptos expuestos son generales y no dependen de un lenguaje determinado y lo llamaremos pseudocódigo (también existe Chapin, …)-. Posteriormente haremos referencia a lenguajes de programación concretos como por ejemplo el Fortran Lenguaje de programación → sentencias → - de declaración: no implica una operación matemática o lógica - Ejecutable: implica una operación matemática o lógica - Comentario: informativa, es ignorada por el procesador 5 UNIDAD 2.- DATOS, OPERACIONES Y EXPRESIONES. Tipos de Datos. Datos simples. Tipo numérico entero y real. Tipo lógico. Tipo alfanumérico. Tipo de dato compuesto String: lectura, escritura y comparación. Constantes y variables. Expresiones. Operadores. Expresiones aritméticas. Jerarquía de los operadores. Expresiones relacionales, lógicas y alfanuméricas. Funciones internas. Tipos de Datos Los lenguajes que aplican una gestión estricta de tipos de datos se denominan lenguajes de tipos fuertes o de tipos estrictos. Por ejemplo, Pascal es un lenguaje de tipo estricto, como lo son Fortran, Java, etc. La programación en MatLab es un ejemplo de programación de tipo no estricto, es decir no hace falta una declaración explícita y estricta de los tipos de datos que se emplean. A nivel de lenguaje de máquina, un dato es un conjunto o secuencia de bits (dígitos 0 y 1). Los lenguajes de alto nivel permiten basarse en abstracciones e ignorar los detalles de la representación interna. Como se mencionó anteriormente los programas manejan datos. Es necesario, por lo tanto, disponer de un mecanismo que permita el almacenamiento y la manipulación de los datos. En un programa esto es llevado a cabo por entidades a las que denominaremos variables y constantes. Variable: es un objeto cuyo valor puede cambiar durante el desarrollo del algoritmo o ejecución del programa. Una variable es un nombre que asignamos para una/s posición/es de memoria usada/s para almacenar un valor de un cierto tipo de dato. Las variables deben declararse antes de usarse, suponiendo un lenguaje de tipo estricto. Cuando se declara una variable estamos reservando una porción de memoria principal para el almacenar los correspondientes valores correspondientes al tipo de variable. La declaración de las variables implica el darles un nombre (identificador de la variable). El valor que almacena una variable se puede modificar (el valor, el tipo NO) a lo largo del programa. Nombres válidos (convenciones): - Todo nombre válido debe comenzar con una letra (obligatorio). - El nombre debe ser representativo (muy recomendable). Constantes: similar al concepto de variable pero con la particularidad de que su valor permanece inalterable en curso de la ejecución del algoritmo. Expresiones: Las expresiones son combinaciones de constantes, variables y símbolos de operación, paréntesis y nombres de funciones. De forma similar a lo que se entiende en notación matemática tradicional. Por ejemplo: a + b * sin(t) * 10^3 Según el tipo de objetos que manipulan las expresiones se califican en : - aritméticas - lógicas - de carácter El resultado de la expresión aritmética es de tipo numérico; el resultado de la expresión relacional y de una expresión lógica es de tipo lógico; el resultado de una expresión de carácter es de tipo carácter. 6 Reglas para evaluar expresiones aritméticas Para evaluar una expresión aritmética hay reglas en cuanto a la “fuerza” de los operadores + y - tienen igual fuerza , pero menos fuerza que * y / (la conocida regla de que “ + y separan y * y / juntan”). Pero acá debemos agregar que si hay una sucesión de operadores de igual fuerza, se van evaluando las operaciones de izquierda a derecha. Las prioridades serían entonces ^ * / + Por ejemplo si tenemos 3 * xx / y + 12 * z * qual * m / 17 / ra / p / s * ty * w + 11 * a / b / c * nu estaríamos representando la siguiente expresión con notación algebraica común 3 . xx 12 . z . qual . m . ty . w 11 . a . nu ---------- + -------------------------------- + ---------------y 17 . ra . p . s b.c las funciones tienen mas fuerza que los operadores y las expresiones entre paréntesis deben ser evaluadas prioritariamente, respetando dentro de ellas las prioridades enunciadas. Expresiones lógicas El resultado de este tipo de expresiones es siempre VERDADERO o FALSO. Las expresiones lógicas se forman combinando constantes lógicas, variables lógicas y otras expresiones lógicas con operadores lógicos (NOT, AND, OR) y con operadores de relación (<, >, <=, >=, =, <>) Genérico: Expresión 1 operador Expresión 2 Operador de Relación Significado > Mayor < Menor = Igual >= Mayor o igual <= Menor o igual <> Distinto Ejemplo: A – 2 < B – 4 es falso si A=4, B=3 Para realizar la comparación de datos tipo carácter se requiere una secuencia de ordenación de los caracteres. Para ello recurrimos al código ASCII (American Standard Code for Information Interchange), donde existe un orden de todos los caracteres. Operadores lógicos Describiremos a continuación los operadores lógicos NOT, AND y OR NOT: es un operador unario, es decir que influye sobre una única expresión del tipo lógica. p F V NOT p V F 7 AND: es la conjunción o multiplicación lógica. Vincula dos expresiones lógicas. Su resultado sólo es verdadero si las dos expresiones son verdaderas. De lo contrario es falso p F F V V p AND q F F F V q F V F V OR: es la disyunción, la suma lógica. Vincula dos expresiones lógicas. Su resultado sólo es falso si las dos expresiones son falsas. De lo contrario es verdadero. p F F V V p OR q F V V V q F V F V (hemos definido el OR inclusivo, hay otro OR, el exclusivo que cuando p y q son verdaderas, su resultado es falso y se identifica EOR y que nosotros no utilizaremos en nuestro curso) Las operaciones que se requieren en los programas exigen en numerosas ocasiones, además de las operaciones aritméticas básicas, ya tratadas, un número determinado de operaciones especiales que se denominan FUNCIONES INTERNAS Función Abs(x) Arctan(x) Cos(x) Exp(x) Ln(x) Log10(x) Redondeo(x) Sin(x) Raiz(x) Trunc(x) Ejemplos: Expresión Raiz(25) Redondeo(6.6) Redondeo(3.1) Redondeo(-3.2) Trunc(5.6) Trunc(3.1) Trunc(-3.8) Abs(9) Abs(-12) Descripción Valor absoluto Arco tangente Coseno Exponencial Logar.neperiano Logar.decimal Redondeo Seno Raiz cuadrada Truncamiento Tipo de argumento Entero o real Entero o real Entero o real Entero o real Entero o real Entero o real Real Entero o real Entero o real Real Resultado Igual al arg. Real Real Real Real Real Entero Real Real Entero Resultado 5.0 (Fortran permite calcular raiz de complejos) 7 3 -3 5 3 -3 9 12 Asignación La operación de asignación es el modo de darle valores a una variable. La operación de asignación la representaremos con el símbolo ← 8 En el desarrollo de nuestros algoritmos nos encontraremos con formas del tipo variable ← expresión esto es lo que llamamos asignación y significa “asignar a la variable el resultado de la expresión” Conversión de tipo: en las asignaciones no se pueden asignar valores a una variable de un tipo diferente del suyo. Por ejemplo, se presentará un error si se trata de asignar valores de tipo carácter a una variable numérica o un valor numérico a una variable tipo carácter. (Fortran permite algunas libertades en este sentido) A una variable de tipo lógico (o booleana), por ej. res se le debe asignar un dato lógico, por lo tanto tiene sentido (suponiendo que a y b sean dos variables reales) una asignación del tipo res ← a = b ¿Cómo interpretamos ésto? a = b es la expresión, lógica en este caso. Si a es igual a b, el resultado de esta expresión será verdadero, y este valor será asignado a la variable lógica res. Como ejemplo, mas adelante, con mayor conocimiento del Fortran se puede probar el siguiente programa. program PruebaFu implicit none logical res real a , b write(*,*)"Ingrese dos reales" read(*,*) a , b res = a == b if (res) then write(*,*) "La variable res quedó con else write(*,*) "La variable res quedó con endif write(*,*)"Ingrese de nuevo dos reales" read(*,*) a , b res = a == b if (res) then write(*,*) "La variable res quedó con else write(*,*) "La variable res quedó con endif end program PruebaFu valor TRUE" valor FALSE" valor TRUE" valor FALSE" Si la primera vez que pide dos reales ingresamos para a y b los valores 3.5 y 3.5 veremos que la salida será La variable res quedó con valor TRUE Si la segunda vez que pide dos reales ingresamos para a y b los valores 2.9 y 7.1 veremos que la salida será La variable res quedó con valor FALSE 9 Entrada y Salida de información Los cálculos que realizan las computadoras requieren para ser útiles la ENTRADA de datos necesarios para la ejecución y que posteriormente definirán la SALIDA. Las operaciones de entrada de datos permiten leer determinados valores y ASIGNARLOS a determinadas VARIABLES. Este proceso se denomina genéricamente LECTURA. Análogamente, la operación de salida se denomina ESCRITURA. Como lo escribimos? leer (lista de variables de entrada) escribir (lista de salida) (en la listade salida puede haber variables, operaciones o constantes) 10 UNIDAD 3.- RESOL. DE PROBLEMAS ALGORÍTMICOS. Análisis de problemas. Diseño de algoritmos. Representación de algoritmos: Diagramas de flujo. Diagramas de Chapin. Pseudocódigo. Lenguaje natural. Estrategia de resolución: refinamientos sucesivos. Análisis de problemas Del mundo real a la solución por computadora Prob.del mundo real Modelo abstracción análisis y descomposición Módulo/s Programa diseño de algoritmos Etapas en la resolución: − Análisis del problema − Diseño de una solución − Especificación de algoritmos − Escritura del programa. − Verificación. Sintaxis y semántica Diccionario: Semántica es el estudio del significado de las unidades lingüísticas. Sintaxis es la manera de enlazarse y ordenarse las palabras en la oración. Desde el punto de vista del lenguaje de programación: Semántica representa el significado de los distintos constructores sintácticos Sintaxis conocer las reglas sintácticas del lenguaje implica conocer cómo se unen las sentencias, declaraciones y otros constructores del lenguaje − Las reglas de sintaxis de un lenguaje de programación dictan la forma de un programa. Durante la compilación de un programa, se comprueban todas las reglas de sintaxis. La semántica dicta el significado de las sentencias del programa. La semántica define qué sucederá cuando una sentencia se ejecute. − Un programa que sea sintácticamente correcto no tiene por qué serlo semánticamente. Un programa siempre hará lo que le digamos que haga y no lo que queríamos que hiciera. Errores de un programa Errores de compilación: Este tipo de errores son detectados por el compilador. Son errores de compilación los errores de sintaxis o el uso en el programa de tipos de datos incompatibles, tal como pretender almacenar un carácter a una variable de tipo numérica. Errores en tiempo de ejecución: Aunque el programa compile bien puede dar error al ejecutarse, por ejemplo por intentar dividir por cero. En este caso el programa puede que estuviese bien escrito, pero adquirir la variable que realiza el papel de divisor el valor cero, y tratar de realizar la división, es cuando se produce el error y ocasiona que el programa se pare. Los programas tienen que ser “robustos”, es decir que prevengan tantos errores de ejecución como sea posible. Errores lógicos: Se producen cuando el programa compila bien y se ejecuta bien pero el resultado es incorrecto. Esto ocurre porque el programa no está haciendo lo que debería hacer (le hemos dicho en realidad que haga otra cosa). Los errores lógicos por lo general son los más difíciles de descubrir. 11 Localización y corrección de errores (depuración) Eliminación de errores de compilación: Listado de errores. Corrección por orden (muchos errores iniciales determinan la existencia de los siguientes, es decir que al corregir uno, pueden corregirse automáticamente varios otros). Eliminación de errores de ejecución: Se debe localizar el punto del programa en el que se produce el error. Esto se hace siguiendo la traza (flujo lógico del programa) hasta que éste falla. A continuación, se debe analizar la sentencia a fin de identificar la causa del error. Una vez identificado el problema el siguiente paso es su corrección, pasando a continuación al siguiente error hasta que no haya ninguno más. Eliminación de errores de lógica: Es la tarea más difícil. Si el programa funciona pera da un resultado erróneo, lo mejor es tomar un ejemplo conocido e ir contrastando los resultados intermedios del ejemplo con los que da el programa. En este último caso hay que ir, normalmente, consultando los resultados parciales del programa en puntos concretos del mismo, estableciendo lo que se denomina “breakpoints” (punto de ruptura). Ingeniería del software Concepto de ingeniería del software. En la primera época de las computadoras el desarrollo de software tenía mucho de arte, dependiendo su calidad final de la experiencia y habilidad del programador. A mediados de los años sesenta, los sistemas software que se comenzaban a desarrollar eran sistemas donde la complejidad era ya un factor a tener en cuenta. Las técnicas artesanales no servían para tratar el desarrollo de sistemas complejos. El problema desde el punto de vista del desarrollo de software era muy importante, por lo que desató mucho interés y trabajo. La falta de control sobre el desarrollo de software se denominó crisis del software, denominación que se sigue usando hoy en día (Gibbs, 1994). La idea es aplicar al software la filosofía ingenieril que se aplica al desarrollo de cualquier producto. La ingeniería del software pretende el desarrollo de software de manera formal, cumpliendo unos estándares de calidad. El punto de vista de la ingeniería del software trasciende a la mera programación. El software se considera dentro de un ciclo de vida que va desde la concepción del producto, pasando por las etapas de análisis del problema, diseño de una solución, implementación de la misma y mantenimiento del producto, hasta su final retirada por obsolescencia. Ciclo de vida del software Un concepto fundamental en ingeniería del software es la consideración de un producto software en el contexto de un ciclo de vida. Desde un punto de vista general, todo software pasa por tres etapas fundamentales: Desarrollo, Uso y Mantenimiento. Desarrollo: Inicialmente la idea para un programa es concebida por un equipo de desarrollo de software o por un usuario con una necesidad particular. Este programa nuevo se construye en la denominada etapa de desarrollo. En esta etapa tenemos varios pasos a realizar: análisis, diseño, implementación y pruebas. Al final, generamos un programa operativo. Uso: Una vez desarrollado el programa y después de considerar que está completo, que es operativo, se pasa a los usuarios. La versión del programa que van a utilizar los usuarios se denomina “release” o versión del programa. Mantenimiento: Durante el uso, utilizamos el programa en su entorno normal de trabajo y como consecuencia de la aparición de errores, del cambio de algún elemento de entorno (software o hardware) o de la necesidad de mejorar el programa, éste se modifica. Esto es el mantenimiento. Casi siempre los usuarios descubren problemas en el programa. Además, hacen sugerencias de mejoras en el programa o de introducción de nuevas características. Estos defectos y nuevas ideas los recibe el equipo de desarrollo y el programa entra en fase de 12 mantenimiento. Estas dos últimas etapas, uso y mantenimiento, se entremezclan y, de hecho, sólo se habla de etapa de mantenimiento desde el punto de vista de la ingeniería del software. En particular la etapa de desarrollo consta, a su vez, de una serie de etapas bien definidas. Análisis: En la etapa de análisis se especifica el qué queremos. Acá se determinan los requisitos que deberá cumplir el programa de cara al usuario final. También se imponen las condiciones que debe satisfacer el programa, así como las restricciones en el tiempo de desarrollo. Diseño: En esta etapa se determina cómo se consiguen realizar los requisitos recogidos y organizados en la etapa anterior. Codificación: Una vez realizado el diseño hay que implementarlo, generando el código fuente (los programas). Una buena codificación debe constar de la correspondiente documentación. Pruebas: El objetivo de las pruebas es descubrir errores. Mantenimiento: Es la etapa que va desde la obtención de una herramienta operativa, al final de la etapa de desarrollo, hasta la retirada del programa por obsolescencia. Podemos decir que el desarrollo representa sólo el 20 % de todo el esfuerzo. Técnicas de diseño Top down: Esta técnica consiste en establecer una serie de niveles de menor complejidad que den solución al problema. Se trata de reconocer en el problema inicial una serie de subproblemas que se puedan trabajar en forma independiente, y luego tomar a cada una de estas ideas para volver a descomponerlas en acciones cada vez más primitivas. Bottom up: El diseño ascendente se refiere a identificar al problema como la suma de varios subproblemas ya resueltos. Luego tomar éstos y juntarlos para “tratar” de solucionar el problema original. Cuando en la programación se realiza un enfoque bottom-up o ascendente, es difícil (si no se tiene bastante experiencia) llegar a integrar los subsistemas al grado tal de que el desempeño global sea fluido. Tipos de representaciones − Gráficos: Es la representación gráfica de las operaciones que realiza un algoritmo (con dibujos). − No Gráficos: Representa en forma descriptiva las operaciones que debe realizar un algoritmo (texto). Los métodos usuales para representar algoritmos 1. Diagrama de flujo 2. Diagrama de Nassi Schneideerman (Organigrama de Chapin) 3. Pseudocódigo 4. Lenguaje natural 5. Fórmulas El organigrama de Chapin puede verse como un diagrama de flujo donde se omiten las flechas de unión y las acciones se escriben en rectángulos sucesivos. Los métodos de lenguaje natural y fórmulas no suelen ser fáciles de transformar en programas. No es frecuente que un algoritmo sea expresado por medio de una simple fórmula, pues no 13 tiene detalle suficiente para ser traducido a algún lenguaje de programación. Y sabemos lo ambiguo del lenguaje natural (en nuestro caso el español). Diagrama de flujo Es un diagrama que utiliza gráficos (figuras geométricas en su mayoría) y flechas para indicar la secuencia en que deben ejecutarse las acciones. Los símbolos utilizados han sido normalizados por el instituto norteamericano de normalización (ANSI). No se recomienda. Pseudocódigo Este pseudolenguaje está formado por palabras comunes (en el idioma que se elija), dichas palabras tales como VARAIBLES, REAL, ENTERO, INICIO, etc., son reservadas por el lenguaje y no podrán usarse para otra función. No hay que preocuparse de las reglas de un lenguaje específico y además es fácil de modificar si se descubren errores de lógica. Ventajas de utilizar pseudocódigo a un Diagrama de Flujo − Ocupa menos espacio en una hoja de papel. − No se necesita elementos de dibujo para realizarlo. − Permite representar en forma fácil operaciones repetitivas. En el Diagrama de flujo, por ser una técnica de representación antigua no se prevé símbolos para indicar las estructuras de control. − Es muy fácil pasar de pseudocódigo a un programa en algún lenguaje de programación. − Se puede observar claramente los niveles que tiene cada operación. − Es la representación narrativa de los pasos que debe seguir un algoritmo para dar solución a un problema determinado. − El pseudocódigo utiliza palabras comunes que indican el proceso a realizar. − Es muy simple introducir modificaciones. Por todas estas razones nosotros optaremos por el pseudocódigo para escribir los algoritmos. 14 UNIDAD 4.- ESTRUCTURA DE LOS ALGORITMOS. Estructura general de un algoritmo. Declaración de variables. Bloque de acciones. Secciones de encabezamiento, declaración y ejecución. Comentarios. Acción de asignación. Acciones de entrada y salida de información. Programación estructurada. Estructuras de control. Estructuras de selección: Estructura de selección simple. Estructura de selección múltiple. Estructuras de repetición. Anidación de estructuras. Buenos hábitos: Indentación y Comentarios. Codificación, prueba, verificación y documentación. Estructura general de un algoritmo En un algoritmo podemos distinguir tres secciones: encabezamiento, declaración y ejecución. Encabezamiento: En pseudocódigo simplemente pondremos programa NombreDelPrograma. Luego al pasar el algoritmo a un Lenguaje determinado, respetaremos las reglas del mismo. Declaración de variables: Vamos a declarar todas las variables que intervengan en nuestro algoritmo, incluso su estructura si se tratara de variables con una determinada estructura. Esto se considera de utilidad ya que nos obliga a analizar de entrada todas las variables que van a aparecer e incluso, por el hecho de declararlas, no puede haber errores o sorpresas durante la ejecución. Hay Lenguajes que no tienen esta exigencia, incluso algunos permiten que las variables puedan contener datos de diferente tipo a medida que se avanza en la ejecución del programa. Esto no se considera conveniente, por lo menos mientras el alumno esté en los comienzos de su aprendizaje. Bloque de ejecución (o de sentencias): Es la parte donde se detallan las acciones a desarrollar para llegar a la solución del problema. Comentarios: Si bien no son obligatorios, es muy útil indicar en determinados lugares del algoritmo, el objeto de las instrucciones, método utilizado, etc. Acción de asignación: Las variables tienen reservado un lugar en la memoria (enteras 2 bytes, reales 4 bytes, de carácter 1 byte, lógicas 1 byte, …), el programa irá guardando los valores que cada variable vaya tomando a medida que se va ejecutando el algoritmo. La acción de asignación la simbolizamos de la siguiente forma variable ← expresión donde variable es el nombre de una variable de cualquier tipo y expresión es una expresión (un cálculo) que tenga un resultado del mismo tipo que la variable. Funciona de la siguiente manera: Se evalúa la expresión y el resultado se guarda en el domicilio de la variable. Acciones de entrada y salida de información: Entrada: La simbolizaremos así leer ( lista de variables ) donde lista de variables es una sucesión de variables separada por comas. Funciona de la siguiente manera: Se digitan desde la unidad de entrada (supongamos el teclado) los datos que queremos asignar a las variables en el orden en que figuran en la lista, estos datos deben corresponderse en tipo con las variables. Estos datos van a ocupar los domicilios de las variables. Salida: La simbolizaremos así escribir ( lista de variables y/o expresiones ) 15 donde expresiones puede ser una operación o simplemente una constante (generalmente una constante literal o de tipo cadena) y se utiliza para acompañar la salida con textos aclaratorios. Funciona de la siguiente manera: Aparecen por la unidad de salida correspondiente (generalmente la pantalla) el resultado de las expresiones o los contenidos de las variables en el orden en que aparecen la lista. Ejemplo 01: Leer dos valores enteros, sumarlos y mostrar su resultado programa Uno variables a, b, res : enteras comienzo leer ( a , b ) res ← a + b escribir ( ´El resultado es´ , res ) fin programa Uno supongamos que el operador ingresa por teclado en la pantalla aparecerá El resultado es 17 46 <Enter> 63 Este algoritmo, pasado a Fortran sería program Uno integer a , b , res read ( * , *) a , b res = a + b write ( * , * ) “El resultado es ” , res end program Uno Programación estructurada: Cuando se comenzó con la programación, no se seguía ninguna regla, lo importante era que el programa “funcionara”. Como cada programador utilizaba sus propias técnicas, los programas eran muy difíciles de comprender y corregir por otras personas, incluso por el programador mismo, pasado un tiempo. Además fueron apareciendo diferentes campos de aplicación que hicieron necesarias nuevas técnicas para programar. Fueron surgiendo así diferentes técnicas o modelos o paradigmas de programación, como por ejemplo el estructurado, el orientado a objeto, el lógico, el funcional, . . . Nosotros, en el curso, trabajaremos dentro del paradigma estructurado, o sea, con la programación estructurada. Estructuras de control Los Lenguajes tienden a ejecutar sus sentencias secuencialmente, es decir una sentencia, luego la que le sigue, etc. Evidentemente esto no sería eficaz, ya que habría que escribir tantas sentencias como acciones deba efectuar la computadora. Existen las llamadas Estructuras de Control que permiten controlar el flujo de los cálculos o acciones, por ejemplo elegir uno de entre varios caminos, repetir varias veces un grupo de acciones, etc. Dentro de las Estructuras de control tenemos Estructuras de Selección y Estructuras de Repetición Estructuras de Selección Estructura de Selección Simple (o de tipo SI, o IF): Permiten seleccionar uno de entre dos caminos La simbolizaremos así 16 si condición entonces Bloque1 sino Bloque2 finsi donde condición es una expresión booleana, es decir algo que puede ser Verdadero (V) o Falso (F) Bloque1 y Bloque2 son cualquier conjunto de acciones dentro de las cuales puede haber cualquier estructura de las vistas o de las que veremos. Funciona de la siguiente manera: Se ingresa a la estructura (siempre desde arriba), se evalúa la condición, si la condición es Verdadera, se ejecuta el Bloque1 y se sale de la estructura, si la condición es Falsa, se ejecuta el Bloque2 y se sale de la estructura. Puede ocurrir que a veces sólo haya que realizar acciones si la condición es verdadera, en cuyo caso la estructura quedaría así si condición entonces Bloque finsi Ejemplo02: Leer los coeficientes a, b y c (a distinto de cero) de una ecuación de segundo grado y resolverla en el campo real (vamos a distinguir los tres casos: sin solución en el campo real / raíz doble / raíces distintas) (vamos a necesitar poner una estructura SI dentro de otra SI ) programa Dos variables: a, b, c, x1, x2, delta, xd : reales comienzo escribir (“Ingrese los tres coeficientes de la ecuación”) leer (a, b, c) delta ← b2 – 4 . a . c si delta < 0 entonces escribir (“No tiene solución en el campo real”) sino si delta = 0 entonces xd ← - b / ( 2 . a ) escribir (“Raíz doble”, xd) sino x1 ← ( - b – raiz(delta) / ( 2 . a ) ) x2 ← ( - b + raiz(delta) / ( 2 . a ) ) escribir (“Raíces distintas”) escribir (“x1 = ”, x1) escribir (“x2 = ”, x2) finsi finsi fin programa Dos Este algoritmo, pasado a Fortran sería program Dos implicit none esta sentencia nos obliga a declarar todas las variables real a,b,c,x1,x2,delta,xd write(*,*)"Ingrese los tres coeficientes de la ecuacion" read(*,*)a,b,c delta=b*b-4*a*c if (delta<0) then write(*,*)"No tiene solucion en el campo real" 17 else if (delta==0) then xd=-b /(2*a) write(*,*)"Raiz doble : ",xd else x1=(-b-sqrt(delta))/(2*a) x2=(-b+sqrt(delta))/(2*a) write(*,*)"Raices distintas" write(*,*)"x1 = ",x1 write(*,*)"x2 = ",x2 endif endif end program Dos Estructura de Selección Múltiple (o de tipo CASE, o SEGÚN SEA): Permite seleccionar uno de entre varios caminos Esta estructura es la que mas difiere con las implementaciones en un Lenguaje u otro. La explicaremos orientada al Fortran. La simbolizaremos así segun sea selector (s1, s2,… si,): Bloque1 (sj, sk,… sm,): Bloque2 ...... (sn, sp,… st,): BloqueQ fin segun Fortran tiene el CASE DEFAULT que es lo que se ejecutaría en caso de no aparecer el valor del selector entre los s1 , s2 , ... si , … st donde selector es una expresión de cualquier tipo simple (generalmente una variable entera) excepto real s1, s2,… st, son constantes del mismo tipo que el selector Funciona de la siguiente manera: Se ingresa a la estructura, se evalúa el selector, se busca dentro de los si un valor igual al selector y se evalúa ese Bloque, ignorándose los demás. Es usada muchas veces para armar menús de opciones. Ejemplo03: Leer dos números reales (el segundo distinto de cero) y proponer una operación según un menú de opciones programa Tres variables a , b , res : reales opcion : entera comienzo escribir (“Ingrese dos números reales”) leer ( a , b ) escribir (“Indique que operación desea hacer”) escribir (“ 1-Sumarlos”) escribir (“ 2-Restarlos”) escribir (“ 3-Multiplicarlos”) escribir (“ 4-Dividirlos”) escribir (“Ingrese su opcion”) leer ( opcion ) según sea ( opcion) ( 1 ): res ← a + b 18 ( 2 ): res ← a - b ( 3 ): res ← a . b ( 4 ): res ← a / b fin según fin programa Tres Este algoritmo, pasado a Fortran sería program Tres implicit none real a,b,res integer opcion write(*,*)"Ingrese dos numeros reales" read(*,*)a,b write(*,*)"Indique que operacion desea hacer" write(*,*)" 1-Sumarlos" write(*,*)" 2-Restarlos" write(*,*)" 3-Multiplicarlos" write(*,*)" 4-Dividirlos" write(*,*)"Ingrese su opcion:" read(*,*)opcion select case (opcion) case ( 1 ) res=a+b case ( 2 ) res=a-b case ( 3 ) res=a*b case ( 4 ) res=a/b end select write(*,*)"El resultado es : ",res end program Tres El Fortran tiene además el IF LOGICO, que tiene el peligro que puede desestructurar el algoritmo La simbolizaremos así if (condición) sentencia Se evalúa la condición, si es verdadera, se ejecuta la sentencia y se pasa a la sentencia siguiente; si es falsa, se ignora la sentencia y se pasa a la sentencia siguiente al IF lógico. Ya sabemos lo que representa condición, en cuanto a sentencia, si bien hay una amplia gama de sentencias que se pueden usar, casi siempre la usaremos con la sentencia exit cuyo efecto es salir de la estructura de repetición donde se encuentre, es decir se pasa a la sentencia inmediata siguiente al fin de la estructura de repetición. Estructuras de repetición Estructura de Repetición con Contador (o de tipo PARA o FOR) La simbolizaremos así para indi = vi , vf , es Bloque fin para 19 donde indi es llamado índice del para es una variable simple, excepto real (la mayoría de las veces una variable entera) (Fortran acepta tipo real) vi , vf , es son los parámetros del para y se los denomina valor inicial , valor final (sería mejor llamarlo valor de test) y escalón del para y son expresiones (operaciones y/o variables y/o constantes) del mismo tipo que el índice. Si es falta se interpreta que es = 1. Funciona de la siguiente manera: Se ingresa a la estructura, se recorre el Bloque por primera vez con un valor del índice indi = vi, al llegar al final de la estructura se vuelve al principio de la misma y se recorre nuevamente el Bloque con un valor de indi = vi + es, se vuelve al principio de la misma y se recorre nuevamente con un valor de indi = vi + 2 . es, y así sucesivamente hasta recorrerlo por última vez con el mayor (menor) valor de indi que no supere (supere) a vf. Observación: si al ingresar a la estructura vi > (<) vf , el Bloque no se recorre nunca. Si el valor de es fuese negativo, los valores de indi irán descendiendo y en la Observación vale lo que figura entre paréntesis. Ejemplo04: Leer 7 números enteros e informar su suma (DO con contador) programa Cuatro variables nro , tota , i : enteras comienzo tota ← 0 para i = 1 , 7 leer ( nro ) tota ← tota + nro fin para escribir ( “Los 7 números leídos suman ” , tota ) fin programa Cuatro Este algoritmo, pasado a Fortran sería program Cuatro implicit none integer nro,tota,i tota=0 do i = 1,7 read(*,*)nro tota=tota+nro end do write(*,*)"Los 7 numeros leidos suman ",tota end program Cuatro Ejemplo05: Leer los coeficientes a, b y c (a distinto de cero) de diez ecuaciones de segundo grado y resolverlas en el campo real Haremos directamente el programa Fortran program Cinco implicit none real a,b,c,x1,x2,delta,xd 20 integer cont do cont = 1 , 10 write(*,*)"Ingrese los tres coeficientes de la ecuacion" read(*,*)a,b,c delta=b*b-4*a*c if (delta<0) then write(*,*)"No tiene solucion en el campo real" else if (delta==0) then xd=-b/2/a write(*,*)"Raiz doble : ",xd else x1=(-b-sqrt(delta))/(2*a) x2=(-b+sqrt(delta))/(2*a) write(*,*)"Raices distintas" write(*,*)"x1 = ",x1 write(*,*)"x2 = ",x2 endif endif enddo end program Cinco Problema de aplicación P Se sabe que en una “viga simple”, el “momento flector” que produce una carga concentrada P aplicada a una distancia a del apoyo izquierdo vale P. a.(z–a) Mf = --------------------z a z Mf es el momento flector debajo de la propia carga y z la longitud (o luz) de la viga Confeccionar un algoritmo tal que ingresando los valores de la carga P y la luz de la viga z, calcule Mf para valores de a que varíen de 0 a z, con incrementos de a décimos de la luz. (para a = 0 y a = z, el momento es cero). Si por ejemplo a = 6m se informará Mf para a = 0,00 , 0,60 , 1,20….5,40 y 6,00 metros program momflec implicit none real a,z,p,mf integer i write(*,*)"Ingrese la luz de la viga y la carga P" read(*,*)z,p do i=1,11 a=(i-1)*z/10 mf=p*a*(z-a)/z write(*,*)” a=”,a,” Mf=”,mf enddo end program momflec 21 Problema de aplicación Una característica para calcular resistencia de vigas es el llamado “momento resistente” W. En vigas de sección rectangular W se calcula mediante la fórmula b * h2 W = ----------6 este dibujo repre- h senta la sección transversal de la viga Confeccionar un algoritmo tal que ingresando los valores de b y h iniciales, calcule W para valores de b y h que se vayan incrementando ambos de 2 cm en 2 cm hasta llegar a b + 8 cm y h + 16cm (es decir que deben informar 45 valores) b La salida debería tener la forma (suponiendo que se ingresó b = 15 y h = 24) b h ------ -----15 24 15 26 15 …… ……………… ……………… 15 40 17 24 17 26 ……………… ……………… 23 38 23 40 Fin W -----xxxx xxxx xxxx xxxx xxxx xxxx xxxx program dobleve implicit none real bcero,hcero,b,h,w integer i,j write(*,*)”Ingrese b y h iniciales de la sección transversal” read(*,*)bcero,hcero write(*,*)” b h W” write(*,*)” ----------” do i=1,5 b=bcero+(i-1)*2 do j=1,9 h=hcero+(j-1)*2 w=b*h*h/6 write(*,*)b,h,w enddo enddo write(*,*)”Fin” end program dobleve 22 Estructura de Repetición con Condición Mientras (o de tipo MIENTRAS o WHILE) La simbolizaremos así mientras condicion hacer Bloque fin mientras Funciona de la siguiente manera: Se ingresa a la estructura, si la condición es verdadera, se recorre el Bloque hasta el final, se vuelve al principio de la estructura, se evalúa nuevamente la condición, si es verdadera, se vuelve a recorrer el Bloque, y así sucesivamente, mientras la condición sea verdadera. En el momento que la condición se hace falsa, se abandona la estructura. Observación: como el control de la condición se efectúa en la cabecera del Bloque, si al tratar de ingresar a la estructura, la condición es falsa, no se ejecuta el Bloque. Es decir que podría ocurrir que el Bloque no se ejecute nunca. Ejemplo06: Leer una cierta cantidad de números enteros distintos de cero, informar cuantos números fueron leidos y su suma programa Seis variables tota , nro , conta : enteras comienzo conta ← 0 tota ← 0 leer ( nro ) mientras nro <> 0 hacer conta ← conta + 1 tota ← tota + nro leer ( nro ) fin mientras escribir ( “Entraron ”, conta, “ números que suman ”, tota) fin programa Seis Este algoritmo, pasado a Fortran sería program Seis implicit none integer tota,nro,conta conta=0 tota=0 read(*,*)nro do while (nro <> 0) conta=conta+1 tota=tota+nro read(*,*)nro enddo write(*,*)"Entraron ",conta," numeros que suman ",tota end program Seis Ejemplo07: Leer los coeficientes a, b y c (a distinto de cero) de un cierto número (no se sabe cuantas) de ecuaciones de segundo grado y resolverlas en el campo real 23 Haremos directamente el programa Fortran program Siete implicit none real a,b,c,x1,x2,delta,xd write (*,*) “Ingrese el coeficiente a (cero para finalizar)” read (*,*) a do while ( a <> 0 ) write(*,*)"Ingrese los coeficientes b y c" read(*,*)b,c delta=b*b-4*a*c if (delta<0) then write(*,*)"No tiene solucion en el campo real" else if (delta==0) then xd=-b /(2*a) write(*,*)"Raiz doble : ",xd else x1=(-b-sqrt(delta))/(2*a) x2=(-b+sqrt(delta))/(2*a) write(*,*)"Raices distintas" write(*,*)"x1 = ",x1 write(*,*)"x2 = ",x2 endif endif write (*,*) “Ingrese el coeficiente a (cero para finalizar)” read (*,*) a enddo end program Siete Problema de aplicación (el ejercicio siguiente, sólo pretende ser un ejercicio de programación, los conceptos sobre Resistencia de Materiales serán desarrollados con mayor precisión en asignaturas del Ciclo Profesional) Cuando una viga es sometida a flexión simple, en cada sección existe una relación entre el “momento flector” (Mf), el “momento resistente” (W) y la “tensión ” (σ). La misma es la siguiente (donde σ es la tensión en la fibra mas solicitada) Mf σ = ----(en la fibra mas solicitada de la sección ) W y si queremos dimensionar una viga, la presentamos así Mf Wnecesario = ------(σadm es la tensión máxima admisible para el material) σadm y elegimos la sección transversal de la pieza de modo que tenga ese “momento resistente necesario”. Lógicamente, que si por ejemplo la viga fuera un perfil de acero, como no se puede fabricar a medida, se tomará uno que tenga el W mas pequeño que supere al necesario. Si la pieza fuese de madera, tendríamos dos variables para llegar al Wnecesario, es decir, el ancho (b) y la altura (h). Tendríamos infinitas soluciones. Podemos fijar el ancho (b) y calcular la altura (h). Pero acá nos encontramos con que las piezas de madera vienen con medidas que “avanzan” de pulgada (”) en pulgada. Entonces lo que hacemos es fijar el ancho y quedarnos (avanzando de a pulgada) con la primer altura que nos de un W que supere al necesario. 24 Se desea dimensionar una viga cargada con una carga concentrada P en el centro de la misma. La viga tiene una longitud z y será construida en madera que tiene una determinada σadm. Construir un programa para dimensionar esa viga. Por cuestiones de proyecto, la viga deberá tener un ancho de cuatro pulgadas (4”) y la altura no puede superar los 40 cm (16”). El programa deberá leer P, z y σadm y deberá prever que bajo estas condiciones no haya solución. La medida mínima disponible es de 4” x 4” (10cm x 10cm) (1” = 2,5 cm). program dimensiona implicit none real p,z,mf,sadm,w,wnece,b,h write(*,*)”Ingrese carga, luz y tension admisible” read(*,*)p,z,sadm mf=p*z/4 wnece=mf/sadm b=10 h=10 notar que en este caso w=b*h*h/6 do while (w<wnece .and. h<40) el valor de salida del DO no es h=h+2.5 un valor leido, sino w=b*h*h/6 valores calculados ( w y h ) enddo if (w >= wnece) then write(*,*)”Medidas b=10cm , h=”,h,” cm” write(*,*)”W=”,w,” (W necesario=”,wnece,”)” else write(*,*)”Imposible dentro de las medidas impuestas” endif end program dimensiona El problema lo pudimos resolver simplemente b . h2 Mf mediante una fórmula, despejando h y luego Wnec= -------- = ------redondeando por exceso a un múltiplo de 2,5 6 σadm (verificar el programa de esta manera) (recordar que habíamos fijado el ancho b en 10cm) h= 6 . Mf --------------10 . σadm Estructura de Repetición con Condición Repetir (o de tipo REPETIR-HASTA QUE o REPEAT-UNTIL) La simbolizaremos así repetir Bloque hasta que condicion Funciona de la siguiente manera: Se ingresa a la estructura (siempre se ingresa), se recorre el Bloque, se llega hasta el pie de la estructura, se analiza la condición, si es falsa, se vuelve a recorrer el Bloque, hasta que la condición se haga verdadera que es el momento en que se abandona la estructura. Debemos hacer varias observaciones: - El Bloque es recorrido por lo menos una vez - Conceptualmente es opuesto al mientras ya que el mientras ejecuta el Bloque cuando la condición es verdadera y el repetir lo abandona cuando la condición es verdadera. - Muchos lenguajes no tienen la estructura repetir, pero la sustituyen con un mientras, en general invirtiendo la condición 25 - En Fortran implementaremos el repetir con un do incondicional y un if lógico con sentencia exit como última sentencia del Bloque. Ejemplo08: Leer una cierta cantidad de números enteros hasta encontrar un -3, informar luego en que posición entró el -3 programa Ocho variables nro , posi : enteras comienzo posi ← 0 repetir posi ← posi + 1 leer ( nro ) hasta que nro = -3 escribir (“El -3 entró en la posición ” , posi) fin programa Ocho Este algoritmo, pasado a Fortran sería program Ocho implicit none integer nro,posi posi=0 do posi=posi+1 read(*,*)nro if (nro==-3) exit enddo write(*,*)"El -3 entro en la posicion ",posi end program Ocho Ejemplo09: Leer los coeficientes a, b y c (a distinto de cero) de un cierto número (no se sabe cuantas, pero por lo menos una) de ecuaciones de segundo grado y resolverlas en el campo real Haremos directamente el programa Fortran program Nueve implicit none real a,b,c,x1,x2,delta,xd write (*,*) “Ingrese el coeficiente a” read (*,*) a do write(*,*)"Ingrese los coeficientes b y c" read(*,*)b,c delta=b*b-4*a*c if (delta<0) then write(*,*)"No tiene solucion en el campo real" else 26 if (delta==0) then xd=-b/2/a write(*,*)"Raiz doble : ",xd else x1=(-b-sqrt(delta))/(2*a) x2=(-b+sqrt(delta))/(2*a) write(*,*)"Raices distintas" write(*,*)"x1 = ",x1 write(*,*)"x2 = ",x2 endif endif write (*,*) “Ingrese el coeficiente a (cero para finalizar)” read (*,*) a if ( a == 0 ) exit enddo end program Nueve Validación de una entrada Tanto el mientras como el repetir pueden usarse para validar entrada de datos. Por ejemplo, supongamos que se desea ingresar un dato de tipo carácter, el cual debe ser una S o una N. Podemos resolverlo de dos maneras. Veamos la porción de algoritmo: ......... ........ escribir (“Ingrese una S o una N”) repetir leer ( letra ) escribir (“Ingrese una S o una N”) mientras letra <> ´S´ y letra <> N´ leer ( letra ) escribir (“Error, ingrese de nuevo”) hasta que letra = ´S´ o letra = ´N´ leer ( letra ) ........ fin mientras .......... Notamos que con el mientras resulta un diálogo mas amigable. Pero lo que vale la pena destacar es que debimos escribir las condiciones una opuesta a la otra. Habíamos dicho al principio del capítulo que Fortran aceptaba como parámetros del do, al tipo real y que a vf era mejor llamarlo “valor de test” en vez de “valor final”. Veamos el siguiente ejemplo: program DoConReales implicit none real i, vi, vf, esca vi=2.75 vf=3.8 esca=0.1 write(*,*)"Valores entre 2,75 y 3,75 con escalon 0,1" write(*,*)"Sin formato" do i=vi, vf, esca write(*,*)i enddo write(*,*)"Con formato" do i=vi, vf, esca write(*,"(F8.2)")i enddo end program DoConReales 27 Este programa produce la siguiente salida Valores entre 2,75 y 3,75 con escalon 0,1 Sin formato 2.750000 2.850000 2.950000 3.050000 3.150000 3.250000 3.349999 3.449999 3.549999 3.649999 3.749999 Con formato 2.75 2.85 2.95 3.05 3.15 3.25 3.35 3.45 3.55 3.65 3.75 Además estamos viendo algo que puede ocurrir a menudo. Al acumular operaciones con reales pueden aparecer “pequeños” (si las operaciones son miles, pueden no ser tan pequeños) errores que harían que un número como por ejemplo 3,35 aparezca con la forma 3.349999. Esto, en una presentación no tiene una forma agradable y puede ser mejor escribirlo con formato (en el ejemplo se usó F8.2) ya que el formato redondea la última cifra y la presentación quedó como esperábamos. Lo que debe quedar bien claro es que escribir con formato no elimina el error, simplemente deja mejor la presentación, pero el “pequeñísimo” error ya existe, en la memoria lo que está guardado es 3.349999 (al escribirlo con formato lo “redondeó”) y este error, aunque pequeño, se manifestó con apenas siete sumas. En problemas de ingeniería frecuentemente se debe trabajar con matrices de grandes dimensiones en donde la cantidad de operaciones pueden contarse de a miles y si bien el tratamiento de errores no es motivo de análisis en este curso, estos comentarios son simplemente de advertencia, Vamos a ver otra posibilidad que nos brinda el Fortran: podemos trabajar con datos complejos. Los complejos son tratados como par de reales. Por ejemplo el complejo 3.25 – 1.789 i en Fortran lo representamos como < 2.25 , - 1.789 >. Como ejemplo vamos a hacer un programa para resolver una ecuación de segundo grado en el campo complejo (si la raiz es real pondrá cero en la parte imaginaria). 28 Resolveremos la ecuación a.x2 + b.x + c = 0, dos veces, la primera vez con a = 1 b =- 5 c = 6 ( x1 = 2 y x2 = 3 ) y la segunda con a = 1 b = 1 c = 1 ( x1 = – 0,5 – 0,866 i y x2 = – 0,5 + 0,866 i ) en vez de leer a , b , c los asignamos directamente en el programa program EcuCompl implicit none real a, b, c complex x1, x2, delta a=1 b=-5 c=6 delta= b**2 - 4*a*c x1=(-b-csqrt(delta)) / x2=(-b+csqrt(delta)) / write(*,*) x1, x2 a=1 b=1 c=1 delta= b**2 - 4*a*c x1=(-b-csqrt(delta)) / x2=(-b+csqrt(delta)) / write(*,*) x1, x2 end program (2*a) (2*a) para raiz cuadrada de números complejos se usa csqrt en vez de sqrt (2*a) (2*a) la salida de este programa es < 2.000000 , 0.0000000E+00 > < -0.500000 , -0.8660254E+00 > < 3.000000 , 0.0000000E+00 > < -0.500000 , 0.8660254E+00 > 29 UNIDAD 5.- SUBALGORITMOS Programación modular. Concepto de subalgoritmo. Declaración y estructura. Subalgoritmo función. Subalgoritmo procedimiento. Cuando tenemos que diseñar un algoritmo de relativa importancia en cuanto a su complejidad, es conveniente dividirlo en partes, o módulos, o subalgoritmos. Si a su vez, a algunos de esos módulos los volvemos a dividir para reducir su complejidad, tenemos un esquema como el siguiente . . . . . . . . . . . . . . . . . . . . . . . . . . . Hemos hecho lo que llamamos refinamientos sucesivos o también análisis top down. (existe otra forma de componer algoritmos y es la bottom up pero no la vamos a utilizar, ya que es necesario tener algo mas de experiencia en algoritmia) (ya mencionado en la Unidad 3). Los subalgoritmos (o subprogramas) son los que permiten aplicar esta idea de los refinamientos sucesivos. Los subalgoritmos tienen otra importante utilidad y es que pueden desarrollarse con parámetros genéricos y luego utilizarse repetidamente con diferentes juegos de datos. Los parámetros de los subalgoritmos pueden ser de cualquier tipo. Presentaremos dos tipos de subalgoritmos: las funciones y los procedimientos. Funciones: Tienen un uso similar a las funciones internas que ya conocemos, pero acá, los cálculos los define el programador. Tienen todas las características de los programas, con la diferencia que no pueden ejecutarse sueltas, sino que se ejecutan cuando son utilizadas (o llamadas) por el programa principal u otro subprograma. Esta utilización puede ser a la derecha en una sentencia de asignación, en una condición (de un if, un while o un repeat) o incluso en una expresión en un write. Las funciones (como las funciones internas) devuelven un único valor a través de su nombre, no devuelven resultados a través de sus parámetros. Veamos un ejemplo: Ejemplo10 Leer dos juegos de 3 números enteros cada juego e informar, en ambos casos, el valor del mínimo 30 programa Diez variables d , e , f , minimo , j : enteras funcion busca (w,x,y) : entera variables w , x , y : enteras si w <= x y w <= y entonces busca ← w sino si x <= w y x <= y entonces busca ← x sino busca ← y finsi finsi fin funcion busca comienzo para j = 1 , 2 escribir (“Ingrese tres enteros”) leer ( d , e , f ) minimo ← busca ( d , e , f ) ( * ) escribir (“El mínimo es ” , minimo) fin para fin programa Diez d, e, f, minimo, j son variables globales w, x, y son parámetros pudimos haber eliminado la variable minimo , también la sentencia ( * ) y cambiar la sentencia escribir por escribir (“El mínimo es ” , busca ( d , e , f )) Este algoritmo, pasado a Fortran sería program Diez implicit none integer d,e,f,busca,minimo,j do j= 1,2 write(*,*)"Ingrese tres enteros" read(*,*)d,e,f minimo=busca(d,e,f) write(*,*)"El minimo es ",minimo enddo end program Diez integer function busca(w,x,y) integer w,x,y if (w <= x .and. w <= y) then busca=w else if (x <= w .and. x <= y) then busca=x else busca=y endif endif end function busca Los parámetros w, x, y que utilizamos en la definición de la función se llaman parámetros formales, ya que sólo sirven para definir el tipo de proceso que sufrirá cada uno. Los parámetros d, e, f que utilizamos en la llamada a la función se llaman parámetros actuales, ya que es con esos valores con los que trabajará la función. Los parámetros formales y los actuales deben coincidir en cantidad, orden y tipo. 31 Antes de terminar con el tema reiteramos un concepto importante: Las funciones no devuelven nada a través de sus parámetros, sino que devuelven un solo dato a través de su nombre. Funciones sentencia: También llamadas sentencias funciones (en inglés “statement function”) Muchas veces las funciones son muy sencillas y pueden escribirse en una sola sentencia y no ameritan la escritura de un subprograma. Fortran permite definirlas en una sola sentencia. Supongamos que en nuestro programa tenemos que usar la función g(x) = x4 + 5x3 - x2 + 9 En nuestro programa podemos tener algo así: . . . real g , x , b , z g(x) = x**4 + 5*x**3 – x**2 + 9 . . . . . . Z = 5.36 + g(b) Hay que tenerla definida antes de usarla y tenemos que definir previamente tanto el tipo de los parámetros formales como el de la función sentencia. La función puede tener mas de un parámetro . . . real juan , x integer i juan(x , i) = x**3 + (2.5+i)*i/3.2 . . . Una función sentencia puede usar otra función sentencia definida anteriormente . . . real a , h , w w(a) = a**4 / 12.5 h(a) = a**3 + a*w(6.0) . . . Lógicamente también puede usar funciones de biblioteca . . . real x , f f(x) = x – sin(x) . . . Problema de aplicación En ingeniería es común encontrarnos con que tenemos que resolver ecuaciones que no tienen una resolvente, por ejemplo f(x) = cos(x) – x = 0 no contamos con una fórmula que nos permita calcular x, tampoco suele interesar la solución exacta (nos conformamos con una solución “bastante” aproximada a la exacta). 32 En general, recurriremos a métodos que se explican en Análisis Numérico, son métodos aproximados (dicotómico, Newton Raphson, …). Haremos un método sencillo, tal vez no tan eficiente como los mencionados, pero que nos permitirá calcular la raíz también de manera aproximada, con tanta precisión como queramos. Sabemos que f(x) es continua y que f(0) = 1 > 0 y f(π/2) = - π/2 < 0. También sabemos que entre 0 y π/2 es decreciente y tiene una sola raiz. Supongamos que queremos aproximar la raiz con un error Є < І 0,00005 І (valor absoluto) Podemos arrancar desde x=0 con “pasos” de 0,0001 calculando la función y avanzando hasta que se haga negativa (o cero por si tenemos la suerte de encontrar la raiz exacta). Si adoptamos como raiz aproximada el promedio entre el último valor de x que hizo a f positiva y el primer valor de x que hizo a f negativa, estaremos asegurando la precisión pedida. program aprox implicit none real f, x, paso, anterior, siguiente, aproxi integer n f(x)=cos(x) - x paso=0.0001 anterior=0. siguiente=0.0001 n=1 do while (f(siguiente)>0. .and. n<30000) n=n+1 anterior=siguiente siguiente=siguiente+paso enddo if (f(siguiente)<=0.) then if (f(siguiente)==0.) then write(*,*)"Raiz exacta ",siguiente else aproxi=(anterior+siguiente)/2. write(*,*)"Raiz aproximada ",aproxi, " calculada en ",n, " pasos" endif else write(*,*)"No se obtuvo la raiz despues de",n,"pasos" endif end program aprox Hemos puesto un contador de pasos de manera que si después de 30.000 pruebas no se encuentra la raíz, el programa se detenga. Esto es importante, ya que si por error tomamos un valor de comienzo equivocado, el programa podría no detenerse nunca. También usamos dos valores para ir avanzando (anterior y siguiente) ya que esto es frecuente en problemas de aproximación. En este caso en particular no hubiesen hecho falta (bastaba avanzar con x) Procedimientos: Los procedimientos tienen un funcionamiento parecido a las funciones, pero no devuelven nada a través de su nombre. Pueden devolver valores (ninguno, uno o varios) a través de sus parámetros. Por lo tanto habrá parámetros de entrada, de salida, o incluso de entrada/salida al procedimiento. A los parámetros que sólo sirven para entrar datos desde el programa (sería mas correcto decir “desde el módulo invocante”) al procedimiento (o sea los de entrada) los llamaremos parámetros por valor, a los parámetros que sirven para devolver datos desde el 33 procedimiento al programa (o sea los de salida, o los de entrada/salida) los llamaremos parámetros por referencia. Veamos su forma de trabajar: Los parámetros por valor, son copiados dentro del procedimiento y el procedimiento trabaja sobre esa copia, por lo tanto las variables (los parámetros actuales) del programa no son modificados, es decir que no se devuelve nada a través de ellos. Cuando los parámetros son por referencia, no son copiados al procedimiento, sino que el procedimiento trabaja sobre el parámetro actual (es decir sobre las variables del programa) pudiendo por lo tanto salir datos del procedimiento hacia fuera. En este punto, debemos hacer una aclaración. En muchos lenguajes, como por ejemplo Pascal, el tipo de parámetro (por valor o por referencia) debe ser declarado en el propio procedimiento. Por lo tanto todas las veces que sea invocado el procedimiento, cada parámetro trabajará de la misma forma, siempre por valor o siempre por referencia. El Fortran permite no definir en el procedimiento la forma de trabajo de cada parámetro, sino que lo deja para el momento de la llamada. Es decir que dentro de un mismo programa, para un procedimiento en particular podría ocurrir que haya parámetros que en una llamada trabajen de una manera y en otra llamada de otra manera. En Fortran si en la llamada, a un parámetro lo encerramos entre paréntesis, trabajará por valor, si no, trabajará por referencia. Ejemplo11: Resolver la ecuación a.x + b = 0 ( a distinto de cero ) Vamos a resolverlo primero en pseudocódigo: programa Once variables a , b , x : reales procedimiento resuelve ( p , q , r ) variables p , q , r : reales r←-q/p fin procedimiento resuelve comienzo escribir (“Ingrese los coeficientes a y b”) read ( a , b ) resuelve ( a , b , x ) escribir (“La raíz es ” , x) fin programa Once aclaramos que este problema puede resolverse con una función no hace falta armar un procedimiento y pasado a Fortran program Once implicit none real a,b,x write(*,*)"Ingrese los coeficientes read(*,*)a,b call resuelve((a),(b),x) write(*,*)"La raiz es ",x end program Once subroutine resuelve(p,q,r) implicit none real p,q,r r = -q / p end subroutine resuelve a y b" 34 Veamos ahora como se trabaja en la memoria memoria x a programa b se copia a en p procedimiento r “apunta” a x p se copia b en q r q Ejemplo12: Programa que muestra la diferencia entre las llamadas por valor y por referencia a una subrutina program Doce implicit none integer a,b ! Este programa es para probar argumentos por valor y por referencia ! armo una subrutina con dos argumentos formales ( x e y ) a los ! cuales simplemente se les suma 5 ! en las llamadas hago las cuatro combinaciones posibles con los ! argumentos actuales con respecto a valor y referencia a=2 b=2 write(*,*)"Los dos por referencia" write(*,*)a,b call cambia(a,b) write(*,*)a,b a=2 b=2 write(*,*)"El 1ro por referencia y el 2do por valor" write(*,*)a,b call cambia(a,(b)) 35 write(*,*)a,b La pantalla debería quedar así a=2 b=2 write(*,*)"El 1ro por valor y el 2do por referencia" write(*,*)a,b call cambia((a),b) Los dos por referencia write(*,*)a,b 2 2 a=2 7 7 b=2 7 7 write(*,*)"Los dos por valor" El 1ro por referencia y el 2do por valor write(*,*)a,b 2 2 call cambia((a),(b)) 7 7 write(*,*)a,b 7 2 write(*,*)"Fin" El 1ro por valor y el 2do por referencia end program Doce 2 2 subroutine cambia(x,y) 7 7 integer x,y 2 7 x=x+5 Los dos por valor y=y+5 2 2 write(*,*)x,y 7 7 end subroutine 2 2 Fin Tanto las funciones como los procedimientos pueden tener definidas variables. Estas se llaman variables locales, y sólo “se ven” desde el subprograma; del subprograma hacia fuera, “no se ven”, es decir son desconocidas por el programa principal. Las variables definidas en el programa principal se denominan variables globales y “se ven”, y pueden ser usadas en el programa principal y en el subprograma. Si un subprograma tiene una variable local con el mismo nombre de una variable global (hecho que desaconsejamos), dentro del subprograma vale la declaración hecha en él y fuera, vale la declaración global. Cualquier función o procedimiento puede llamar a otra función y/o procedimiento, y así sucesivamente, es decir puede ocurrir una sucesión o cadena de llamadas entre funciones y procedimientos. Noción de recursividad: ¿Qué ocurre, si en vista de lo enunciado en el párrafo anterior una función se llama a si misma? Algunos lenguajes lo permiten y es lo que se conoce como recursividad. Esto es una herramienta poderosa, sobre todo cuando se trata de resolver problemas numéricos. Pero para evitar que la recursión continúe indefinidamente, dentro de la función hay que incluir una condición de terminación. Como ejemplo vamos a resolver en pseudocódigo y en Fortran una función para calcular el factorial de un número entero no-negativo. (recordar que definimos el factorial de cero igual a uno 0! = 1) En pseudocódigo, la función sería algo así: funcion factorial ( n : entero ) : entera comienzo si n = 0 entonces factorial ← 1 sino muchos lenguajes permiten declarar el tipo de sus parámetros en la propia cabecera 36 factorial ← n * factorial ( n – 1 ) finsi fin Vamos a hacer en Fortran un programa que calcule e imprima el factorial de los primeros diez números naturales. Si bien algunos lenguajes no lo exigen, en Fortran debemos aclarar al definir la función que vamos a usar recursividad. Por eso anteponemos la palabra recursive. program recu implicit none integer facto, n, resu, i do i=1,10 resu=facto(i) write(*,*)resu enddo end program recu recursive integer function facto(n) implicit none integer n if (n==0) then facto=1 else facto=n*facto(n-1) endif end function facto La salida de este programa es 1 2 6 24 120 720 5040 40320 362880 3628800 ¿Qué pasó que imprimió enteros mayores a 32767? Si bien muchos lenguajes trabajan con enteros de dos bytes (16 bits) y por lo tanto dentro del rango [-32768 , 32767], Fortran, por defecto trabaja con enteros de cuatro bytes admitiendo valores mucho mayores. No obstante, en Fortran podemos trabajar con integer(1) (ocupan 1 byte) integer(2) (ocupan 2 bytes) integer(8) (ocupan 8 bytes) (sólo disponible en algunas versiones) ¿Qué pasa si en el ejemplo anterior sustituimos por integer facto, n, resu, i integer(2) facto, n, resu, i ? 37 Obtendríamos el siguiente resultado: 1 2 6 24 120 720 5040 -25216 -30336 24320 Lo que ocurrió es que al intentar poner 40320 ( 8 ! ) en 16 bits, la capacidad “rebalsó” por tanto este valor y los dos siguientes dieron resultados inesperados. Pascal, por defecto destina dos bytes a los enteros y si corremos un programa similar en Pascal, también se producirá un rebalse similar y obtendremos exactamente los mismos valores. 38 UNIDAD 6.- ESTRUCTURAS DE DATOS. ARREGLO. Concepto de estructura de datos: datos simples y datos estructurados. Estructuras estáticas y dinámicas. Estructuras homogéneas y heterogéneas. Arreglos unidimensionales. Arreglos multidimensionales. Implantación en memoria. Declaraciones y operaciones. Arreglos como parámetros de subalgoritmos. Todos los datos que hemos visto hasta ahora son datos “sueltos”, datos que no están agrupados de ningún modo, también los llamamos escalares. Hay otros tipos de datos, que están agrupados con ciertas reglas, se llaman datos estructurados, entre los cuales tenemos arreglos, registros . . . . (array, record, . . . .), que son los que pasaremos a ver de aquí en adelante. Arreglo: Es un tipo de dato estructurado cuya principal característica es que todos los elementos que contiene deben ser del mismo tipo. Son estructuras estáticas. Tienen un tamaño fijo y son para trabajarlos en la memoria principal. Pueden tener, una, dos, tres o mas dimensiones. Los elementos del arreglo son referenciados a través de índices de manera unívoca arreglo bidimensional de quince elementos reales, organizado en cinco “filas” y tres “columnas” arreglo unidimensional de seis elementos enteros v= 61 -3 17 12 -3 0 m= v[5] -0.18 17.1 3.46 22.53 10.7 12.0 -14.2539 2.5 14.1 -3.91 2.5 6.44 -9.0 22.12 11.1 m[4,3] hemos adoptado esa forma de visualizarlos, para seguir lo que se realiza en Algebra, pero bien pudimos haberlos dibujado de otra, por ejemplo a v horizontal. Los arreglos de mas de dos dimensiones, resultan algo mas difícil de visualizar, pero es cuestión de práctica. Ejemplo13: Leer dos arreglos unidimensionales a y b de 5 elementos reales cada uno. Calcular la suma del producto de sus elementos de igual posición e informarlo. (acá deberíamos aclarar en el enunciado si se lee primero a y luego b, o si leeremos a1 y b1, luego a2 y b2, etc. Supongamos que leemos primero a y luego b) programa Trece tipo arre5 = arreglo [ 5 ] de reales variables a , b : arre5 suma : real i : entero comienzo para i de 1 a 5 leer ( a [ i ] ) si hubiésemos tenido que leer a1 y b1, luego a2 y b2, etc. tendríamos que finpara cambiar los dos “para” por uno solo para i de 1 a 5 para i de 1 a 5 leer ( b [ i ] ) leer ( a [ i ] , b [ i ] ) finpara finpara suma ← 0 para i de 1 a 5 suma ← suma + a [ i ] . b [ i ] 39 finpara escribir (“El resultado es ”, suma) fin programa Trece pasado a Fortran program Trece implicit none integer i real a,b,suma esta sentencia y la siguiente pueden sustituirse por la única sentencia dimension a(5),b(5) real a(5) , b(5) , suma write(*,*)"Ingrese el arreglo a" read(*,*)(a(i),i=1,5) esta forma de leer la llamamos lectura con formato DO write(*,*)"Ingrese el arreglo b" read(*,*)(b(i),i=1,5) suma=0 do i=1,5 suma=suma+a(i)*b(i) enddo write (*,*)”El resultado es ”,suma end program Trece En Fortran, si uno simplemente nombra el arreglo, se lee o escribe completo. Por lo tanto, las dos sentencias read, simplemente pudieron ponerse read ( * , * ) a y read ( * , * ) b Ejemplo14: Leer por filas un arreglo entero bi-dimensional de cuatro filas y tres columnas y armar un arreglo unidimensional con la suma de los elementos de cada fila y otro con la suma de los elementos de cada columna. (agregamos un ejemplo numérico para comprender el enunciado) -3 9 12 -2 12 0 9 6 0 17 -2 9 f = [ 16 27 24 ] X = c = 9 26 19 13 programa Catorce tipo matri = arreglo [ 4 , 3 ] de enteros vec1 = arreglo [ 4 ] de enteros vec2 = arreglo [ 3 ] de enteros variables x : matri c : vec1 f : vec2 i , j , sc , sf : enteras comienzo para i = 1 , 4 para j = 1 , 3 leer ( x [ i , j ] ) finpara finpara para i = 1 , 4 40 sf ← 0 para j = 1 , 3 sf ← sf + x [ i , j ] finpara c [ i ] ← sf finpara para j = 1 , 3 sc ← 0 para i = 1 , 4 sc ← sc + x [ i , j ] finpara f [ j ] ← sc finpara escribir (“La suma de las filas es”) para i = 1 , 4 escribir ( c [ i ] ) finpara escribir (“La suma de las columnas es”) para j = 1 , 3 escribir ( f [ j ] ) finpara fin programa Catorce Los arreglos son ubicados en memoria en posiciones adyacentes. Con los arreglos unidimensionales no hay problema, se ubica el primer elemento en una posición de la memoria y el resto en posiciones adyacentes consecutivas. En la ubicación en memoria de los arreglos multidimensionales, siempre se colocan sus elementos en posiciones adyacentes, pero depende del lenguaje la forma de ubicarlos. Pascal los ubica por filas y Fortran por columnas. Los arreglos pueden ser parámetros de procedimientos y funciones. Veamos un ejemplo: Ejemplo15: Leer por filas una matriz entera de 3 x 3 calcular su determinante (mediante una función) e informarlo. program Quince implicit none integer i , j , m( 3 , 3 ) , deter, sarrus do i = 1 , 3 read ( * , * ) ( m ( i , j ) , j = 1 , 3 ) enddo deter = sarrus ( m ) write ( * , * ) “Determinante vale”, deter end program Quince integer function sarrus ( x ) integer x ( 3 , 3 ) , suma , resta (suma y resta son variables locales) suma =x(1,1)*x(2,2)*x(3,3)+ x(1,2)*x(2,3)*x(3,1)+ x(1,3)*x(2,1)*x(3,2) resta=x(1,3)*x(2,2)*x(3,1)+ x(1,2)*x(2,1)*x(3,3)+ x(1,1)*x(2,3)*x(3,2) sarrus=suma-resta end function sarrus 41 Vamos a hacer otro problema, donde construiremos la función para calcular por Sarrus un determinante de 3x3 pero sus argumentos serán tres vectores y la usaremos para resolver un sistema no homogéneo de 3x3. Y al sistema en vez de verlo así lo pensaremos así a11 . x 1 + a12 . x2 + a 13 . x3 = b1 p1 . x1 + q1 . x2 + r1 . x3 = b1 a21 . x 1 + a22 . x2 + a 23 . x3 = b2 p2 . x1 + q2 . x2 + r2 . x3 = b2 a31 . x 1 + a32 . x2 + a 33 . x3 = b3 p3 . x1 + q3 . x2 + r3 . x3 = b3 Ejemplo16: Leer por filas la matriz de los coeficientes de un sistema de 3x3, a coeficientes reales. Leer luego el vector de los términos independientes. Calcular la solución e informarla (supondremos que tiene solución única) program Dieciseis implicit none integer i real p(3) , q(3) , r(3) , x1 , x2 , x3 , b(3) , sarrus2, detcoef do i=1,3 read (*,*) p(i) , q(i) , r(i) enddo read (*,*) b detcoef = sarrus2(p,q,r) x1 = sarrus2(b,q,r) / detcoef x2 = sarrus2(p,b,r) / detcoef x3 = sarrus2(p,q,b) / detcoef write (*,*) “Las raíces son ” , x1 , x2 , x3 end program Dieciseis real function sarrus2 (f,g,h) implicit none real suma , resta , f(3) , g(3) , h(3) suma = f(1) * g(2) * h(3) + f(2) * g(3) * h(1) + f(3) * g(1) * h(2) resta = f(3) * g(2) * h(1) + f(2) * g(1) * h(3) + f(1) * g(3) * h(2) sarrus2 = suma – resta end function sarrus2 Problema de aplicación Otra característica que utiliza la resistencia de materiales es el llamado “momento de inercia”, muy importante en el cálculo de vigas, columnas, etc. Si se trata de perfiles de acero, el propio fabricante provee las características geométricas de las secciones entre las cuales se encuentra el mencionado momento x de inercia. Si la pieza tiene como sección transversal una figura geométrica sencilla, el momento de inercia es fácil de calcular y suele ser un ejercicio común en Análisis Matemático para practicar integrales simples o dobles. Se sabe que para una sección rectangular, el momento de inercia con respecto al eje baricéntrico x-x viene dado por la fórmula que figura unos renglones mas arriba. x b h b . h3 Ix = ---------12 42 Construir un programa para armar una tabla con valores del momento de inercia correspondientes a anchos b que varíen desde 10cm hasta 30cm y valores de la altura h que varíen desde 10cm hasta 60cm, ambos valores con escalones de 2,5cm. Los valores del momento de inercia para un mismo b deben ir en una misma columna y los valores del momento de inercia para una misma h deben ir en una misma fila. program TablaInercia implicit none real b, h, iner(9) integer i, j h=7.5 write(*,*)" Tabla de valores del Momento de Inercia de secciones rectangulares" write(*,*)" H y B en centimetros Momentos de Inercia en centimetros a la cuarta" write(*,*)"============================================================================" write(*,*)" B" write(*,*)"============================================================================" write(*,*)" H 10.0 12.5 15.0 17.5 20.0 22.5 25.0 27.5 30.0 " write(*,*)"============================================================================" do i=1,21 h=h+2.5 b=7.5 do j=1,9 b=b+2.5 iner(j)=b*h**3/12. enddo write(*,"(F5.1,9F8.0)")h,iner enddo end program TablaInercia Hemos utilizado salida con formato para que nuestra tabla sea mas fácil de leer. El funcionamiento es muy sencillo. Para escribir un número real (o de punto Flotante) usamos el código F. Fw.d indica que el número debe ocupar w espacios (incluyendo el punto decimal, los decimales y un eventual signo “menos”), el valor d indica la cantidad de decimales que van a aparecer impresos (“redondeando” la última cifra decimal). Para los valores de h, hemos usado F5.1, ya que algunos valores tienen un decimal. Para los valores del momento de inercia hemos usado F8.0 ya que los decimales, trabajando en “centímetros a la cuarta”, en este caso, no tienen importancia desde el punto de vista de un ingeniero. Por ejemplo el número 12.374,766, con el formato F8.0 aparecerá impreso así 1 2 3 7 5 . redondeado (a cero decimales) con dos espacios en blanco a la izquierda. (se aconseja ser “generoso” con el campo d) Problema de aplicación Se tiene una viga de un puente de 15m de longitud. Sobre la P2 P1 misma puede circular una máquina vial que vamos a A representar como dos 3m cargas concentradas P1 y P2 separadas 3m Se pretende hacer circular ese “tren de cargas” x de 10cm en 10cm, desde que P1 toca el apoyo A (P2 todavía no entró) hasta que P2 toca el apoyo B (P1 ya había salido del puente). sentido de avance del “tren de cargas” B 15m 43 Para cada posición del tren de cargas nos interesa conocer el momento que genera en la viga en secciones separadas 30cm es decir que nos interesa conocer 51 valores del momento (ya que incluimos los apoyos A y B, aunque sabemos que debe ser cero) Una carga P que se encuentre a una distancia x del apoyo A, generará un momento en P una sección C que se encuentre a una A C B distancia y del mismo apoyo P.y.(15 - x) M = ---------------15 x ( si y < x ) P.x.(15 - y) M = ---------------15 ( si y > x ) y ( si y = x vale cualquiera de las dos ) Para cada una de las secciones, interesa conocer el máximo de los valores obtenidos para las diferentes posiciones del tren de carga. Si graficamos estos máximos obtenemos lo que llamamos Diagrama Envolvente de Momento Flector para un tren de dos cargas concentradas P1 y P2 separadas 3m. (si cambiamos el tren de carga, en cuanto a intensidad, cantidad, separación, etc. el diagrama envolvente cambiará). De los 51 valores del diagrama envolvente interesa también conocer el máximo de todos (máximo de todos los máximos o máximo maximorum). Trabajaremos en toneladas y metros. Nuestro programa consistirá en hacer avanzar el tren de carga de a 10cm, desde x=0,10 hasta x=17,90. Distinguiremos 3 casos: x <= 3 (sólo P1 está en el puente), 3 < x <= 15 (ambas cargas están en el puente), 15 < x <= 17,90 (sólo P2 está en el puente). Construiremos una subrutina solouna para calcular los 51 valores del momento cuando una sola carga PE está sobre la viga, a una determinada distancia equis del apoyo izquierdo. Construiremos una subrutina condos para calcular los 51 valores del momento cuando dos cargas (P1 y P2) están sobre la viga, con PUNO a una determinada distancia equis del apoyo izquierdo y PDOS tres metros atrás. Construiremos una subrutina elmayor tal que dados dos vectores de 51 elementos cada uno (v1 y v2), devuelva (en v1) un vector tal que en cada posición se encuentre el máximo de los dos valores que están en esa posición. Nuestro tren de cargas deberá avanzar desde una posición x=10cm y deberá dar 178 pasos de 10cm hasta llegar a 17,9m. program Envolvente implicit none real dista,a,b,c,p,q,r,morum,p1,p2,x,vmax(51),v(51) integer i,idelmax do i=1,51 vmax(i)=0.0 enddo write (*,*)"Ingrese el valor de P1 y P2 (en toneladas)" read (*,*)p1,p2 x=0.10 do i=1,178 if (x <= 3.0) then call solouna ((p1),(x),v) else if (x > 15.0) then 44 call solouna ((p2),(x-3.0),v) else call condos ((p1),(p2),(x),v) endif endif call elmayor (vmax,(v)) x=x+0.1 enddo write(*,*)"Valores del diagrama envolvente" write(*,*)"-------------------------------" do i=1,51 a=0.3*(i-1) p=vmax(i) write(*,*)"Abscisa:",a," Momento:",p enddo morum=vmax(1) idelmax=1 do i=2,51 if (vmax(i) > morum) then idelmax=i morum=vmax(i) endif enddo dista=0.3*(idelmax-1) write(*,"(A20,F4.2,A2,F7.3,A2)")"Maximo maximorum->M(",dista,")=",morum,"tm" end program Envolvente subroutine solouna(pe,equis,ve) implicit none real d,pe,mome,equis,ve(51) integer ka do ka=1,51 d=0.3*(ka-1) if (d < equis) then mome=pe*d*(15.0-equis)/15.0 else mome=pe*equis*(15.0-d)/15.0 endif ve(ka)=mome enddo end subroutine solouna subroutine condos(pe1,pe2,equis,ve) implicit none real d,pe1,pe2,mome,equis,ve(51) integer ka do ka=1,51 d=0.3*(ka-1) if (d < equis) then mome=pe1*d*(15.0-equis)/15.0 else mome=pe1*equis*(15.0-d)/15.0 endif ve(ka)=mome enddo equis=equis-3.0 do ka=1,51 d=0.3*(ka-1) if (d < equis) then mome=pe2*d*(15.0-equis)/15.0 else 45 mome=pe2*equis*(15.0-d)/15.0 endif ve(ka)=ve(ka)+mome enddo end subroutine condos subroutine elmayor(eldelmax,elactual) implicit none real eldelmax(51),elactual(51) integer ka do ka=1,51 if (elactual(ka) > eldelmax(ka)) then eldelmax(ka)= elactual(ka) endif enddo end subroutine elmayor Algunas operaciones con matrices xxxX ( los ejemplos siguientes sólo tienen el objeto se practicar algoritmia, los temas desarrollados serán tratados Se define la multiplicación de matrices de la siguiente manera. con verdadera rigurosidad en las Supongamos que sean asignaturas correspondientes) A = 1 0 -1 A 0 6 1 = -2 1 3 1 0 -1 B 0 6 1 -2 1 3 = 0 1 -1 2 0 3 0 1 -1 2 0 3 -1 3 1 2 5 -2 -4 3 7 -3 19 7 -1 3 1 C = A x B donde el elemento de la segunda fila, tercera columna del producto lo hemos obtenido así 0.(-1) + 6.3 + 1.1 = 19, en general podemos poner …... 3 ci,j = . . ai,k . bk,j (i=1,2,3 / j=1,2,3) k=1 Hemos ejemplificado para dos matrices cuadradas, pero también pudimos hacer la multiplicación para dos matrices no necesariamente cuadradas. El único requisito es que la cantidad de columnas de la que premultiplica sea igual a la cantidad de filas de la que postmultiplica. A m ,n x B n ,q = C m ,q deben ser iguales el producto de matrices no es conmutativo. Mas aún podría existir A x B y no existir B x A Seguiremos trabajando sólo con matrices cuadradas. 46 Definimos la transpuesta de una matriz a otra matriz en donde se han cambiado filas por columnas. (a veces la transposición suele hacerse sobre si misma). Por ejemplo A = 1 0 -1 0 6 1 -2 1 3 AT =T 1 0 -2 0 6 1 -1 1 3 Otra característica que podemos definir es la matriz adjunta de una matriz dada donde el elemento Adj(A) i, j es igual al valor del determinante que se obtiene suprimiendo la fila i y la columna j de la matriz A, con signo + o – según que i+j sea par o impar Recordar que estamos trabajando con matrices cuadradas Veamos un ejemplo A = -2 3 -1 0 2 -3 1 0 4 8 -12 -3 -7 -2 3 Adj(A) = -7 -6 -4 la hemos obtenido de la siguiente manera 2 0 3 0 3 2 - Adj(A) = -3 4 -1 4 0 1 -2 1 - -1 -3 -2 0 -3 4 0 1 -1 4 -1 -3 -2 1 -2 0 3 0 3 2 2 0 Finalmente vamos a definir la inversa de una matriz (la simbolizaremos A-1) a aquella matriz tal que pre o postmultiplicando a la matriz original nos da la matriz identidad, que es una matriz que tiene unos en la diagonal principal y ceros el resto de sus elementos. A-1 x A = A x A-1 = E donde E-1= 1 0 0 0 1 0 1 0 1 a veces a la matriz identidad también se la representa con I. Una manera de calcular la matriz inversa es a través de la adjunta, transpuesta, dividida por el determinante. A-1 = AdjT(A) / det(A) cero. por lo tanto, el determinante de la matriz debe ser distinto de 47 Vamos a hacer un programa Leer por filas una matriz real cuadrada de orden 3, calcular su inversa e informarla. Informar el valor del determinante de la matriz original. Como verificación pre y post multiplicar la matriz por su inversa e informar ambos productos (deben dar como resultado la matriz identidad). Vamos a usar la función sarrus que vimos anteriormente para calcular el determinante y las subrutinas xadju para calcular la adjunta, tra para transponerla sobre si misma y multipli para hacer los dos productos matriciales que se pidieron como verificación. program inversa implicit none integer i, j real m(3,3), invdem(3,3), determi, e(3,3), sarrus write(*,*)"Ingrese por filas la matriz a invertir" do i=1,3 read(*,*)(m(i,j),j=1,3) enddo determi=sarrus (m) write(*,*)"Su determinante vale ", determi call xadju((m),invdem) call tra(invdem) do i=1,3 do j=1,3 invdem(i,j)=invdem(i,j)/determi enddo enddo write(*,*)"Su inversa es" do i=1,3 write(*,"(3F12.3)")(invdem(i,j),j=1,3) enddo call multipli((m),(invdem),e) write(*,*)"El producto de la matriz por su inversa es" do i=1,3 write(*,"(3F12.6)")(e(i,j),j=1,3) enddo call multipli((invdem),(m),e) write(*,*)"El producto de la inversa por la matriz es" do i=1,3 write(*,"(3F12.6)")(e(i,j),j=1,3) enddo end program inversa subroutine multipli(x, y, z) real x(3,3), y(3,3), z(3,3) integer i, j, k do i=1,3 do j=1,3 z(i,j)=0. do k=1,3 z(i,j)=z(i,j)+x(i,k)*y(k,j) enddo enddo enddo end subroutine multipli 48 subroutine xadju(x,axd) real x(3,3), axd(3,3) integer p, q do p=1,3 do q=1,3 if (p==1 .and. q==1) axd(1,1)= x(2,2)*x(3,3)-x(2,3)*x(3,2) if (p==1 .and. q==2) axd(1,2)=(-1.)*(x(2,1)*x(3,3)-x(3,1)*x(2,3)) if (p==1 .and. q==3) axd(1,3)= x(2,1)*x(3,2)-x(3,1)*x(2,2) if (p==2 .and. q==1) axd(2,1)=(-1.)*(x(1,2)*x(3,3)-x(3,2)*x(1,3)) if (p==2 .and. q==2) axd(2,2)= x(1,1)*x(3,3)-x(3,1)*x(1,3) if (p==2 .and. q==3) axd(2,3)=(-1.)*(x(1,1)*x(3,2)-x(3,1)*x(1,2)) if (p==3 .and. q==1) axd(3,1)= x(1,2)*x(2,3)-x(2,2)*x(1,3) if (p==3 .and. q==2) axd(3,2)=(-1.)*(x(1,1)*x(2,3)-x(2,1)*x(1,3)) if (p==3 .and. q==3) axd(3,3)= x(1,1)*x(2,2)-x(2,1)*x(1,2) no es tan fácil el programa para calcular la matriz adjunta enddo enddo si la matriz es de orden superior a 3. end subroutine xadju subroutine tra(x) real x(3,3), aux integer p,q do p=1,3 do q=p+1 , 3 aux = x(p,q) x(p,q)=x(q,p) x(q,p)=aux enddo enddo end subroutine tra real function sarrus ( x ) real x ( 3 , 3 ) , suma , resta suma =x(1,1)*x(2,2)*x(3,3)+ x(1,2)*x(2,3)*x(3,1)+ x(1,3)*x(2,1)*x(3,2) resta=x(1,3)*x(2,2)*x(3,1)+ x(1,2)*x(2,1)*x(3,3)+ x(1,1)*x(2,3)*x(3,2) sarrus=suma-resta end function sarrus 49 UNIDAD 7.- ESTRUCTURA DE DATOS REGISTRO Y ARCHIVO. Conceptos de campo y registro. Campos clave. Declaración formal de registro. Arreglo de registros. Archivo: concepto. Organización secuencial, directa e indexada. Procesamiento de archivos secuenciales: Creación, apertura, cierre, consulta y actualización. (parte de esta Unidad ha sido extraida de apuntes de otros docentes de la Cátedra) Registro: Un registro es un tipo de dato estructurado. Está compuesto por campos que pueden ser de diferente tipo (de cualquier tipo). Es la estructura de datos que se utiliza principalmente para intercambiar datos con los archivos. Por ejemplo, supongamos que estamos tratando con datos de alumnos y que queremos manejar su legajo, apellido, nombre, notas (enteras) de cinco parciales y domicilio. Podríamos agruparlos en un tipo registro ...... tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin notas : arreglo [ 5 ] de enteros domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin fin variables alu1 , alu2 : alumno Podemos imaginar la siguiente representación: alu1 G2015 García Alberto 8 10 6 8 7 Jujuy 1678 Rosario Si en un programa hago referencia a alu1 estoy apuntando a todo el registro con alu1.notas[4] con alu1.domicilio con alu1.domicilio.ciudad Ejemplo17: Leer los datos de dos alumnos de un curso (legajo, apellido nombre, las notas de cinco parciales y domicilio) e informar promedio, legajo, apellido y domicilio del de mayor promedio. programa diecisiete tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin notas : arreglo [ 1 :: 5 ] de enteros domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin 50 fin variables x1 , x2 : alumno i : entera p1 , p2 : reales comienzo leer (x1.legajo, x1. apeynom.apellido, x1. apeynom.nombre) para i de 1 a 5 leer ( x1.notas[i] ) finpara leer (x1.domicilio.calle, x1.domicilio.numero, x1.domicilio.ciudad ) leer (x2.legajo, x2. apeynom.apellido, x2. apeynom.nombre) para i de 1 a 5 leer ( x2.notas[i] ) finpara leer (x2.domicilio.calle, x2.domicilio.numero, x2.domicilio.ciudad ) p1 ← 0 p2 ← 0 para i de 1 a 5 p1 ← p1 + x1.notas[i] p2 ← p2 + x2.notas[i] finpara p1 ← p1 / 5 p2 ← p2 / 5 si p1 > p2 entonces escribir (p1, x1.legajo, x1. apeynom.apellido, x1.domicilio.ciudad) sino escribir (p2, x2.legajo, x2. apeynom.apellido, x2.domicilio.ciudad) finsi escribir ( “ tiene mayor promedio” ) fin programa Diecisiete Se pueden armar arreglos de registros. Por ejemplo si construimos un arreglo unidimensional de registros, tendríamos una especie de “archivo”, pero con los siguientes inconvenientes: tamaño fijo, se lo trabaja en la memoria principal, desaparece al terminar el programa. Tenemos que hacer una aclaración importante: al definir arreglos dijimos que todos los elementos del arreglo tenían que ser del mismo tipo. Podría surgir la siguiente pregunta “¿Al haber dentro del registro, datos de diferente tipo, no se está cometiendo un error?”. NO, ya que desde el punto de vista del arreglo, todos sus elementos son del mismo tipo, es decir registros (por ejemplo de tipo alumno). Ejemplo18: Leer los datos (legajo, apellido, nombre, notas de cinco parciales y domicilio) de un cierto número de alumnos (no mas de 100) e informar el promedio y el apellido del de mayor promedio. Hacemos notar que para resolver este problema no hace falta guardar los datos de todos los alumnos. Podría ir tomando los datos requeridos a medida que se van leyendo los alumnos. Sólo lo hacemos para practicar con arreglos de registros. A nuestro registro le agregaremos un campo mas que llamaremos promedio al que calcularemos ni bien se leen las notas. programa Dieciocho tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin 51 notas : arreglo [ 1. . 5 ] de enteros promedio : real domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin fin variables curso : arreglo [ 1 .. 100 ] de alumno i, idelmax, nroalu : enteros p , pmax : real lega : cadena [ 5 ] comienzo nroalu ← 0 leer ( lega ) mientras lega < > “XXXXX” hacer indico fin de datos con XXXXX nroalu ← nroalu + 1 curso [ nroalu ] . legajo ← lega leer ( curso [ nroalu ] . apeynom . apellido , curso [ nroalu ] . apeynom . nombre ) p←0 para i de 1 a 5 leer (curso [ nroalu ] . notas [ i ] ) p ← p + curso [ nroalu ] . notas [ i ] finpara curso [ nroalu ] . promedio ← p / 5 leer (curso[nroalu].domicilio.calle,curso[nroalu].domicilio.numero) leer(curso[nroalu].domicilio.ciudad) leer ( lega ) fin mientras idelmax ← 1 pmax ← curso [ 1 ] . promedio para i de 2 a nroalu si curso [ i ] . promedio > pmax idelmax ← i pmax ← curso [ i ] . promedio finsi finpara escribir ( “Con ” , pmax , curso [ idelmax ] . apellido, “ tiene el mayor promedio”) fin programa Dieciocho ¿Cómo hacer para guardar, por ejemplo los datos de los alumnos del curso en memoria externa? Supongamos que la memoria externa es un disco. En un dispositivo así se podrá almacenar información (mediante la escritura en disco: de memoria a disco) que luego puede ser recuperada (mediante la lectura desde disco: de disco a memoria) para su procesamiento. Registro lógico: Cuando la información está almacenada en memoria, los datos están agrupados por una lógica impuesta en la declaración de variables del programa, y así a este registro se lo denomina registro lógico. Archivo: Un conjunto de registros con cierto aspecto en común y, organizados para algún propósito particular, se denomina archivo (file). Un archivo es una estructura diseñada para contener datos en un medio externo de almacenamiento. Los datos están organizados de tal modo que puedan ser recuperados 52 fácilmente, actualizados, borrados y almacenados nuevamente en el archivo con todos los cambios realizados. Características de los archivos ♦ Un archivo siempre está en un soporte externo de almacenamiento ( disco, cinta, etc ) ♦ Existe independencia de los datos respecto a los programas ♦ Todo programa intercambia información con un archivo mediante la estructura registro. ♦ La información guardada en un archivo es permanente. ♦ Los archivos permiten una gran capacidad de almacenamiento. Registro físico: En el disco la información está escrita “físicamente”, y la cantidad más pequeña de datos que puede transferirse en una operación de lectura / escritura entre la memoria y el dispositivo periférico se denomina registro físico. Por ejemplo un sector de un disco ( por ejemplo 512 Bytes ), una línea de impresión ( si el dispositivo es la impresora ), etc. Un registro físico puede contener uno o mas registros lógicos, de acuerdo al tamaño del registro lógico. En el ejemplo de buscar el máximo promedio (Ejemplo18) tenemos: Tamaño del registro lógico = 100 bytes legajo : 4 b. / apeynom : (20+25) b. / notas (5x2) b. / promedio : 4 b. / domicilio (15+2+20) b. Tamaño del registro físico = 512 bytes Por lo tanto en la lectura de información desde el disco a la memoria se podrán “bajar” 5 registros de un sola transferencia. Organización de los archivos: Para almacenamiento y para acceder a sus datos, se puede organizar la información dentro de los archivos de tres formas distintas, secuencial, acceso directo y secuencial indexada. El criterio de usar una u otra organización depende del uso posterior de la información. Organización secuencial: En un archivo organizado secuencialmente los registros están ordenados en el orden en el cual fueron originalmente escritos. Todos los tipos de almacenamiento externos ( discos, cinta, etc. ) permiten este tipo de organización. Algunos dispositivos, por su naturaleza física, sólo soportan archivos secuenciales, por ejemplo, acceder a la información almacenada en un registro particular de una cinta magnética, requiere el acceso a los registros desde el primero hasta el buscado en ese momento ( Es análogo a la búsqueda de un tema musical en un cassette ). Otros dispositivos estrictamente secuenciales son las impresoras. Para el manejo de este tipo de archivo existen instrucciones especiales, tales como: abrir un archivo: abrir (nombre del archivo, referencia) o abrir (nombre del archivo, variable de tipo archivo) En un programa se le da una referencia a ese archivo. Esta es una variable de tipo “archivo” (file) que se usa para cada vez que se quiera nombrar a ese archivo. Se pueden tener varios archivos abiertos al mismo tiempo en un programa. leer el registro siguiente desde el dispositivo de almacenamiento externo: leer ( registro, referencia al archivo ) o leer ( variable de tipo registro, variable de tipo archivo ) escribir o grabar un registro en dispositivo de almacenamiento externo: grabar(registro,referencia al archivo) o grabar(variable de tipo registro, variable de tipo archivo) cerrar un archivo abierto: cerrar ( referencia ) o cerrar ( variable de tipo archivo) 53 Para acceder a los datos de un archivo, primero debemos abrirlo, como si se tratara de un libro. En esta operación el procesador coloca un “puntero” que "apunta" al primer registro del archivo. Luego de leer este registro, dicho puntero se ubica en el registro siguiente. Si continuamos leyendo, llegará un momento en que hemos leído todos los registros, llegando al final del mismo. Una vez que el procesador lee la marca de fin del archivo (eof: End Of File) este no se puede seguir leyendo pues da error. Sólo se puede leer el archivo mientras no sea el final del mismo. En general en nuestros programas tendremos algo así: mientras no eof (referencia) hacer Ejemplos de algoritmos para este tipo de archivos: Altas Hacer un algoritmo que permita agregar ( ALTAS ) registros al archivo DATOS.DAT, cuyos registros tienen los campos que vimos en el Ejemplo18 (sin el campo promedio) programa Altas tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin notas : arreglo [ 1 .. 5 ] de enteros domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin fin variables a : alumno c: archivo de alumno rta , esta : char lega: string [ 5 ] comienzo abrir (DATOS.DAT , c) ! DATOS. DAT es el nombre del archivo en disco y ! c es la referencia al archivo en el programa. rta ← "S" mientras rta = "S" hacer ! Este mientras es para permitir hacer varias altas esta ← "N" escribir ( "Legajo : " ) leer ( lega ) ! ahora busca si este registro ya esta grabado leer ( a , c ) mientras no ( eof(c) ) y esta = "N" hacer si a.legajo = lega entonces escribir ( " Ya esta este Legajo " ) esta ← "S" sino leer ( a , c ) fin si fin mientras si esta = "N" entonces escribir ( "Apellido: " ) leer ( a . apeynom . apellido ) 54 escribir ( "Nombre: " ) leer ( a . apeynom . nombre ) escribir (“Ingrese las cinco notas”) para i de 1 a 5 leer ( a . notas [ i ] ) fin para escribir (“Ingrese Calle Nro. Ciudad”) leer ( a . domicilio. calle , a . domicilio . numero , a . domicilio . ciudad ) a . legajo ← lega grabar ( a, c ) fin si escribir ( " Quiere ingresar otro registro ? " ) leer ( rta ) fin mientras cerrar ( c ) fin programa Altas Bajas Hacer un algoritmo que permita eliminar ( BAJAS ) registros del archivo DATOS.DAT, cuyos registros fueron descriptos en el ejemplo anterior. Hacemos el algoritmo para una sola baja. programa Bajas tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin notas : arreglo [ 1 .. 5 ] de enteros domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin fin variables a : alumno c , b: archivo de alumno esta : char lega: string [ 5 ] comienzo abrir (DATOS.DAT , c) la idea es copiar uno a uno los registros DATOS.DAT abrir (AUX.DAT , b ) en AUX.DAT con excepción del que queremos dar está ←"N" de baja, luego eliminamos DATOS.DAT y renomescribir ( "Legajo a eliminar : " ) bramos AUX.DAT como DATOS.DAT leer ( lega ) leer ( a , c ) ahora busca si este legajo está en el archivo mientras no( EOF( c ) hacer si lega = a.legajo entonces escribir ( " Este Legajo corresponde a ", a . apeynom . apellido , “ y se borrará” esta ← "S" sino grabar ( a , b ) graba el registro en AUX fin si leer ( a , c ) siguiente registro de DATOS.DAT fin mientras 55 si esta = "N" entonces escribir (" No se encontró ese Legajo ", lega ) fin si cerrar ( c ) cerrar ( b ) borrar DATOS.DAT renombrar AUX.DAT como DATOS.DAT fin. Modificaciones Hacer un algoritmo que permita modificar ( MODIFICACIONES ) registros del archivo DATOS.DAT, cuyos registros fueron descriptos en el ejemplo anterior. Hacemos el algoritmo para una sola modificación. programa Modificar tipo alumno = registro legajo: cadena [ 5 ] apeynom : registro apellido: cadena [ 20 ] nombre: cadena [ 25 ] fin notas : arreglo [ 1 .. 5 ] de enteros domicilio : registro calle : cadena [ 15 ] numero : entero ciudad : cadena [ 20 ] fin fin variables a : alumno c, b : archivo de alumno esta : char i : entera lega: string [ 5 ] comienzo abrir (DATOS.DAT , c) abrir (AUX.DAT , b ) esta = "N" escribir ( "Legajo : " ) leer ( lega ) ahora busco si este registro ya está grabado leer ( a , c ) mientras no eof(c) y esta = "N" hacer si a . legajo = lega entonces mostrar ( a ) cambios ( a ) esta ← "S" sino grabar (a , b) fin si leer ( a , c ) fin mientras si esta = "N" entonces escribir ( " El Legajo ", lega ," no existe" ) fin si cerrar ( c ) cerrar ( b ) 56 borrar DATOS.DAT renombrar AUX.DAT como DATOS.DAT fin . procedimiento mostrar ( a: alumno ) comienzo escribir ( "Legajo : " , a . legajo) escribir ( "Apellido : ", a . apeynom . apellido) escribir ( "Nombre : ", a . apeynom . nombre) escribir ( "Calle : ", a . domicilio . calle , a . domicilio . numero) escribir ( "Localidad : " , a . domicilio . ciudad) escribir ( “Notas :”) para i de 1 a 5 escribir ( a . notas[ i ] ) finpara fin procedimiento mostrar procedimiento cambios ( a: alumno ) comienzo escribir (“Ingrese, en el mismo orden todos los datos correctos”) leer (a . legajo) leer (a . apeynom . apellido) leer (a . apeynom . nombre) leer (a . domicilio . calle , a . domicilio . numero) leer (a . domicilio . ciudad) para i de 1 a 5 leer ( a . notas[ i ] ) finpara grabar ( a, b ) fin del procedimiento cambios Organización de acceso directo: En este tipo de organización se accede al registro sin necesidad de leer los registros que le anteceden. Cuando se graban los registros que componen el archivo, en los soportes físicos (discos o CD), el procesador junto a su sistema operativo, ubica cada registro en direcciones del disco que las obtiene a través de una fórmula. Suponiendo que todos los registros tienen el mismo tamaño, y sabiendo cuanto lugar ocupa cada uno en el disco, le es simple al procesador ubicar el registro número N , sin necesidad de leer todos los registros. Organización secuencial indexada: Los registros están grabados consecutivamente, pero el acceso puede hacerse mediante un camino muy corto, casi directo. Los diccionarios con pestañas, son un buen ejemplo de este tipo de organización: si buscamos la palabra " Saber ", primero buscaremos la pestaña S , y luego buscaremos la palabra " Saber ". En esta organización los índices se encuentran en una tabla e indican la localización de un grupo de registros. Un archivo con organización secuencial indexada, o simplemente indexado consta de : - un archivo de datos: los registros con información propiamente dicha - uno o varios archivos de índices ordenados según un campo clave. Cada registro de este archivo contiene la clave del último registro de cada grupo, y la dirección relativa del primer registro del grupo. Por ejemplo en que hoja comienza la letra a y cual es la ultima palabra que empieza con dicha letra. Cuando deseamos localizar un registro por su clave, primero realiza una búsqueda en el archivo de índices, hasta encontrar la primera clave superior a la dada. Junto a esta clave, se encuentra la dirección inicial del grupo de registros que debe contener el registro a localizar. El 57 procesador se posiciona directamente en esa dirección, efectuando una búsqueda secuencial del registro por su clave. Archivos en Fortran Trabajaremos sólo con archivos de acceso secuencial. En Fortran (en otros lenguajes también) no hace falta trabajar con registros para comunicarnos con los archivos (si bien en ejemplos anteriores vimos que se puede hacer), podemos leer y o escribir las variables directamente. Cada write deja una marca como de “fin de registro”.Vamos a hacer un ejemplo muy simple. Armamos un archivo que contenga los siguientes datos: 1er. registro: 1.01 11.11 21.21 1 2do. registro: 101.01 111.11 121.21 101 3er. registro: 201.01 211.11 221.21 201 4to. registro: 301.01 311.11 321.21 301 Volvemos al 1er. registro (con REWIND) y leemos los registros y los vamos mostrando por pantalla. program ArConReg implicit none type unregi real a, c, d integer b end type unregi type (unregi) z integer i open(17, file="c:\ArConReg.dat") z.a= 1.01 z.c= 11.11 z.d= 21.21 z.b= 1 do i=1, 3 write(17,*)z z.a= z.a + 100. z.c= z.c + 100. z.d= z.d + 100. z.b= z.b + 100 enddo write(17,*)z REWIND(17) do i=1,4 read(17,*)z write(*,*)z.a, z.b, z.c, z.d enddo close(17) end program ArConReg Al abrir el archivo queda preparado para leer y/o escribir en el primer “registro”. Cada vez que se lee y/o escribe un “registro”, se avanza al “registro” siguiente (aunque no se hayan leído todas las variables del “registro”). Vamos a hecer un programa que haga exactamente lo mismo que el anterior, pero, además, que luego vuelva a leer y escribir el archivo, pero al leer el segundo “registro”, que sólo lea una variable y verificamos que aunque no lo lea completo salta al siguiente (al tercero). 58 program ArSinReg implicit none real a, c, d, w, y, z integer b, x, i open(17, file="c:\ArSinReg.dat") a= 1.01 c= 11.11 d= 21.21 b= 1 write(17,*)a, c, d, b a= a + 100. c= c + 100. d= d + 100. b= b + 100 write(17,*) a, c, d, b a= a + 100. c= c + 100. d= d + 100. b= b + 100 write(17,*) a, c, d, b a= a + 100. c= c + 100. d= d + 100. b= b + 100 write(17,*) a, c, d, b REWIND(17) do i=1,4 read(17,*)w, y, z, x esto es para hacer notar que los nombres de las variables con que write(*,*)w, y, z, x se lee el archivo no tienen por que ser iguales a los nombres de las enddo variables con que fue escrito write(*,*)"Ahora voy a leerlo de nuevo con 4 read, pero en el segundo" write(*,*)"solo voy a leer una variable, pero vemos que a pesar de no" write(*,*)"leer completo los cuatro datos grabados, lo mismo salta al" write(*,*)"""registro"" siguiente" rewind(17) read(17,*)w, y, z, x write(*,*)w, y, z, x read(17,*)w write(*,*)w read(17,*)w, y, z, x write(*,*)w, y, z, x read(17,*)w, y, z, x write(*,*)w, y, z, x close(17) end program ArSinReg Veamos un tercer programa: Un grupo de amigos decide organizar un Quini4, las apuestas consisten en elegir cuatro números distintos entre 0 y 9 (ambos incluidos). Luego se sortearán 4 números distintos entre 0 y 9 (ambos incluidos). Se considera apuesta ganadora aquella cuyos cuatro números coincidan con los cuatro sorteados (no interesa el orden). Construir un programa Fortran para: 1) Leer nombre de cada apostador (seis caracteres) y los cuatro números de su apuesta y grabarlos en el archivo c: \ apuestas.dat. No se sabe cuantos apostadores hubo. 59 2) Leer los cuatro números sorteados 3) Grabar las apuestas ganadoras en c: \ ganaron.dat. 4) Informar cuantas apuestas hubo, y si hubo o no ganadores y en caso de haberlos, cuantos fueron y sus nombres y cual fue su apuesta. program Quini4 implicit none integer i, apuesta(4), cantapu, cantgana, sorteo(4), aciertos character*6 nombre open (32,file="c:\apuestas.dat") open (10,file="c:\ganaron.dat") cantapu=0 write(*,*)"Ingrese nombre (xxxxxx termina)" read(*,*)nombre do while (nombre <> "xxxxxx") cantapu=cantapu+1 write(*,*)"y los cuatro numeros de la apuesta" read(*,*)apuesta write(32,*)nombre, apuesta write(*,*)"Ingrese nombre (xxxxxx termina)" read(*,*)nombre enddo write(*,*)"Ahora los cuatro numeros del sorteo" read(*,*)sorteo rewind(32) cantgana=0 do i=1, cantapu read(32,*)nombre, apuesta if (aciertos(sorteo, apuesta)==4) then cantgana=cantgana+1 write(10,*)nombre, apuesta endif enddo write(*,*)"Total de apuestas:", cantapu rewind(10) if (cantgana>0) then write(*,*)"hubo ", cantgana, " ganadores y son los siguientes" do i=1, cantgana read(10,*) nombre, apuesta write(*,*) nombre, apuesta enddo else write(*,*)"No hubo ganadores" endif close(10) close(32) end program Quini4 integer function aciertos(a,b) integer p, q, a(4), b(4) aciertos=0 do p=1,4 do q=1,4 if (a(p)==b(q)) then pudo ponerse un IF lógico aciertos=aciertos+1 if(a(p)==b(q)) aciertos=aciertos+1 60 endif enddo enddo end function aciertos 61 UNIDAD 8.- ORDENAMIENTO, BUSQUEDA E INTERCALACION. Ordenamiento por selección del mínimo, por inserción y por intercambio. Estimación de la cantidad de operaciones a efectuar. Idea de eficiencia de un algoritmo. Búsqueda secuencial y binaria. Intercalación. Trabajando con arreglos unidimensionales, surgen inmediatamente tres tareas típicas: ordenamiento (clasificación), búsqueda e intercalación. ordenamiento: Dado un arreglo, reordenar sus elementos de modo que queden en orden creciente (o decreciente). búsqueda: Dado un arreglo, determinar si un cierto dato se encuentra o no dentro de él. intercalación: Dados dos arreglos que contengan elementos del mismo tipo, ambos ordenados con el mismo criterio, no necesariamente ambos con la misma cantidad de elementos. Armar un tercer arreglo que contenga los elementos de ambos y que esté ordenado con el mismo criterio. Observación: Si bien el resultado sería el pedido, en intercalación no consideraremos correcto armar un arreglo con los elementos de ambos y luego ordenarlo, ya que esto implicaría un número mucho mayor de operaciones. Ordenamiento: Si bien hay capítulos de libros dedicados a este tema, sólo veremos tres métodos: por intercambio (burbuja), por selección del mínimo y por inserción. Por intercambio: Consiste en comparar el último elemento del arreglo con el penúltimo, si están en el orden buscado, se los deja así, si no, se los intercambia. Lugo se compara el penúltimo con el antepenúltimo, si están en el orden buscado, se los deja así, si no, se los intercambia. Y así sucesivamente hasta repetir lo mismo, comparando el primer elemento con el segundo. De esta manera, aseguramos que en la primer posición quedó el menor o el mas liviano, elemento que se comportó como una burbuja. Repetimos lo mismo, siempre comenzando desde el fondo del arreglo, pero esta vez llegamos hasta la segunda posición del arreglo. Estamos seguro que los dos mas pequeños quedaron en las dos primeras posiciones y en orden. Repetimos siempre lo mismo hasta la penúltima posición con lo que garantizamos que hasta la penúltima posición quedaron en orden, y por lo tanto la última también. Veamos un ejemplo numérico. Tratemos de ordenar el arreglo ( 8 2 0 5 1 8 8 8 8 8 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 0 8 8 8 8 1 1 1 1 1 1 1 0 0 0 0 2 2 2 2 1 8 8 8 2 2 2 2 5 5 1 1 1 1 1 1 2 2 2 2 8 8 4 4 1 1 5 5 5 5 4 4 4 4 4 4 4 4 8 5 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 8 1er. paso 3er. paso 2do. paso 4) 5to. paso 4to. paso 62 Ejemplo19: Leer un arreglo de 10 elementos reales, ordenarlo por el método de intercambio. Imprimirlo ordenado. program Diecinueve integer i,j real v(10), aux write(*,*)"Ingrese el arreglo" read(*,*)(v(i),i=1,10) do i = 1 ,9 do j = 9 , i , -1 if ( v(j) > v(j+1) ) then aux = v(j) podría incorporarse una verificación v(j) = v(j+1) tal que si antes de completar el DO v(j+1) = aux de mas afuera nos damos cuenta que el endif arreglo YA quedó ordenado, abandoneenddo mos el proceso. enddo write(*,*)"El mismo arreglo ordenado" write(*,*)(v(i),i=1,10) end program Diecinueve Por selección del mínimo: En un primer paso, se busca el mínimo de todos los elementos del arreglo (memorizando valor y posición) y se lo intercambia con el que está en la primera posición. Luego se vuelve a buscar el mínimo, pero buscando desde la segunda posición en adelante y se lo intercambia con el segundo. Así sucesivamente hasta llegar al penúltimo elemento. Ejemplifiquemos sobre el mismo arreglo anterior 8 0 0 0 0 0 2 2 1 1 1 1 0 8 8 2 2 2 5 5 5 5 4 4 1 1 2 8 8 5 4 4 4 4 5 8 1er. paso 2do. paso 3er. paso 4to. paso 5to. paso Ejemplo20: Leer un arreglo de 10 elementos reales, ordenarlo por el método de selección del mínimo. Imprimirlo ordenado. program Veinte implicit none integer i ,j , posmin real v(10) , mini write(*,*)"Ingrese el arreglo" read(*,*) v do i=1,9 posmin=i mini=v(i) do j=i+1,10 63 if (v(j) < mini) then posmin=j mini=v(j) endif enddo v(posmin) = v(i) v(i) = mini enddo write(*,*)"El arreglo quedo asi" write(*,*)v end program Veinte Por inserción: Este método es muy similar a la operación que se efectúa con los naipes cuando se quiere dejarlos ordenados. En medio del proceso, se elige un elemento del arreglo (un naipe), se lo reserva, y se van corriendo los anteriores una posición hasta encontrar uno que sea menor, o llegar al principio del arreglo. (el proceso empieza desde la posición 2) Supongamos que estoy en medio del proceso y deseo acomodar el quinto de ocho elementos. 2 4 11 12 7 reservo el 5o. 9 13 8 (en la variable x en el programa) 7 corro un lugar los anteriores mayores hasta donde estaba el seleccionado 1er. elem. 2 4 11 12 9 13 8 y ubico el reservado en el “hueco” que quedó 2 4 7 11 12 9 13 8 y repito el proceso hasta el final Ejemplo21: Leer un arreglo de 10 elementos reales, ordenarlo por el método de inserción. Imprimirlo ordenado. program Veintiuno implicit none integer i,j real a(10), x write(*,*)"Ingrese el arreglo" read(*,*)a do i=2,10 64 x=a(i) reservo el i-ésimo elemento j=i-1 do while (x<a(j).and.j>0) corro un lugar los anteriores a(j+1)=a(j) mayores hasta encontrar el j=j-1 primer elemento que sea endddo menor al i-ésimo reservado a(j+1)=x y ubico el elemento reservado en el “hueco” que quedó enddo write(*,*)" El arreglo quedo asi " write(*,*)a end program Veintiuno Búsqueda: Consiste en lo siguiente, dado un arreglo averiguar si dentro del arreglo está, o no un determinado elemento (que podemos llamar k). Existen muchos métodos de búsqueda, cada uno con diferentes variaciones. Veremos sólo dos, búsqueda secuencial y búsqueda binaria (o dicotómica). El uso de uno u otro método, depende de que el arreglo esté ordenado o no. La búsqueda binaria o dicotómica sirve si el arreglo está ordenado. Si no tenemos garantía de que el arreglo esté ordenado, no hay que usarla. La búsqueda secuencial sirve en ambos casos, pero en arreglos de muchos elementos si bien funciona, es ineficiente y no deberíamos usarla. Podemos hacer el siguiente cuadro. Arreglo NO ordenado Secuencial Binaria USAR NO USAR (no sirve) Arreglo ordenado NO USAR (ineficiente) USAR También en cada uno de los métodos podemos distinguir dos posibilidades. Que, en caso de estar el elemento buscado tenga la posibilidad de estar mas de una vez, y debamos decidir si nos conformamos con encontrar una ocurrencia, o queremos encontrar todas. Vale la pena hacer notar que en un arreglo ordenado, si puede haber elementos repetidos, los repetidos deben estar todos juntos. Búsqueda secuencial: Consiste en recorrer uno a uno los elementos del arreglo (desordenado) y compararlos con k hasta encontrarlo o no Para ejemplificar, supongamos que tenemos el arreglo a de 7 elementos enteros y deseamos saber si un cierto entero k está o no dentro del arreglo, y si está, en que posición está su primer ocurrencia. Supongamos a= 16 -39 0 25 5 25 17 y supongamos que k sea -4 la respuesta del algoritmo debería ser no está si suponemos que k es 25 la respuesta del algoritmo debería ser está en la posición 4 65 Si nos interesa conocer todas las ocurrencias. a= 25 -39 22 25 5 22 17 y supongamos que k sea -4 la respuesta del algoritmo debería ser no está si suponemos que k es 22 la respuesta del algoritmo debería ser está en la posición 3 está en la posición 6 Veamos los algoritmos Para el caso que interese una sola ocurrencia Ejemplo22: Leer un arreglo de 7 elementos enteros. Leer luego un entero. Informar si ese entero está o no. Si está, informar la posición de su primera ocurrencia. programa Veintidos tipo v7 = arreglo [ 7 ] de entero variables a : v7 i , k , pos : entero comienzo escribir ( ´Ingrese el arreglo´ ) para i de 1 a 7 leer ( a [ i ] ) finpara escribir ( ´Ingrese el dato a buscar´ ) leer ( k ) pos ← 0 repetir pos ← pos + 1 hasta que a [ pos ] = k ó pos = 7 si a [ pos ] = k entonces escribir ( ´está en la posición´ , pos ) sino escribir ( ´no está´ ) finsi fin programa Veintidos recorro el arreglo hasta encontrarlo o agotarlo pregunto si salí porque lo encontré o porque no lo encontré program Veintidós implicit none integer i , k , pos , a(7) write (*,*) “Ingrese el arreglo” read (*,*) a write (*,*) “Ingrese el dato a buscar” read (*,*) k pos = 0 do pos = pos + 1 if ( a(pos)==k .or. pos==7 ) exit enddo if ( a(pos)==k ) then write (*,*) “Está en la posición” , pos 66 else write (*,*) “No está” endif end program Veintidós Para el caso que interesen todas las ocurrencias Ejemplo23: Leer un arreglo de 7 elementos enteros. Leer luego un entero. Informar si ese entero está o no. Si está, informar la posición de todas sus ocurrencias. programa Veintitres tipo v7 = arreglo [ 7 ] de entero variables a : v7 i , k : entero hay : boolean comienzo escribir ( ´Ingrese el arreglo´ ) para i de 1 a 7 leer ( a [ i ] ) finpara escribir ( ´Ingrese el dato a buscar´ ) leer ( k ) pongo esa “bandera” en falso (no encontrado todavía) hay ← falso para i de 1 a 7 si a [ i ] = k entonces escribir ( ´está en la posición´ , i ) pongo la “bandera” en verdadero (hay uno por lo menos) hay ← verdadero finsi finpara si no hay entonces escribir ( ´no está´ ) finsi fin programa Veintitres program Veintitres implicit none integer i , k , a(7) logical hay write (*,*) “Ingrese el arreglo” read (*,*) a write (*,*) “Ingrese el dato a buscar” read (*,*) k hay = .false. do i=1,7 if ( a(i)==k ) then write (*,*) “Está en la posición” , i hay = .true. endif enddo if .not. hay then write (*,*) “No está” endif end program Veintitres 67 Búsqueda dicotómica: Consiste en tener dos indicadores, uno de la posición de menor orden (min) y otro de la posición de mayor orden (max). Al principio uno valdrá 1 y el otro N, suponiendo que la cantidad de elementos sea N. (N=11 en los dos ejemplos que siguen). Probamos si el elemento que está en el “medio” del arreglo es igual al buscado. Si es el buscado, lo encontramos y finaliza el algoritmo (suponemos que nos conformamos con una sola ocurrencia). Si no es el buscado, nos fijamos si es mayor o menor al buscado, y como el arreglo está ordenado, si el elemento del “medio” es menor, si el buscado está, está en la parte de “abajo”, y nos quedamos con esa parte bajando min hasta medio, pero como ya sabíamos que el del “medio” no era, lo bajamos una posición mas, es decir hacemos medio ← medio + 1 Análogo razonamiento si el elemento del “medio” es mayor. En nuestro algoritmo si max y min son de distinta paridad, su promedio dará “coma cinco” lo que está prohibido como índice, entonces tomamos la división entera, es decir hacemos medio ← ( min + max ) div 2 (donde con div simbolizamos la división entera) Vamos a ejemplificar para el caso de búsqueda exitosa (el elemento buscado se encuentra en el arreglo). Supongamos que el elemento que busco es 9 ( k = 9 ) Los elementos sombreados es porque están descartados min -22 min -16 0 medio medio max -22 -22 -16 -16 -16 0 3 9 -22 max 0 0 3 min medio 3 3 9 max 9 min/max/medio y se encuentra el valor buscado 9 17 17 17 17 25 25 25 25 40 40 40 40 63 63 63 63 82 82 82 82 94 94 94 94 Vamos a ejemplificar para el caso de búsqueda fallida (el elemento buscado NO se encuentra en el arreglo). Supongamos que el elemento que busco es 36 ( k = 36 ) min medio -22 -22 -22 -22 -22 -16 -16 -16 -16 -16 0 0 0 0 0 3 3 3 3 3 9 9 9 9 9 17 17 17 17 25 min 40 63 medio 82 max 94 max 17 25 min medio 25 40 max min/medio/max 25 max 25 40 40 min 40 63 63 63 63 82 82 82 82 94 94 94 94 68 y al quedar el máximo por encima del mínimo, se deduce que el elemento buscado no está Ejemplo24: Leer un arreglo de 11 elementos enteros, se sabe que está ordenado en forma creciente. Leer luego un entero. Informar si ese entero está o no. Si está, informar su posición. programa Veinticuatro tipo v11 = arreglo [ 11 ] de entero variables a : v11 min , k, max , medio : entero comienzo escribir ( ´Ingrese el arreglo´ ) para i de 1 a 11 leer ( a [ i ] ) finpara escribir ( ´Ingrese el dato a buscar´ ) leer ( k ) min ← 1 max ← 11 medio ← ( min + max ) div 2 mientras min <= max y k <> a [ medio ] hacer si k < a [ medio ] entonces max ←medio - 1 sino min ←medio + 1 finsi medio ← ( min + max ) div 2 finmientras si k = a [ medio ] entonces escribir ( “El valor se encontró en” , medio ) sino escribir ( “No se encontró” ) finsi fin programa Veinticuatro program Veinticuatro implicit none integer min , max , medio , k , a(11) write (*,*) “Ingrese el arreglo” read (*,*) a write (*,*) “Ingrese el dato a buscar” read (*,*) k min = 1 max = 11 medio = ( min + max ) / 2 do while ( min <= max .and. k <> a ( medio ) ) if ( k < a ( medio ) ) then max = medio – 1 else min = medio + 1 endif medio = ( min + max ) / 2 enddo 69 if ( k == a write else write endif end program ( medio ) ) then (*,*) “El valor se encontró en” , medio (*,*) “No se encontró” Veinticuatro No vamos a tratar el caso de búsqueda dicotómica de todas las ocurrencias cuando hay elementos repetidos, simplemente damos una idea. Si un arreglo ordenado tiene elementos repetidos, estarán en posiciones adyacentes. Aplicando el algoritmo de búsqueda dicotómica, que es mucho mas rápido que el secuencial, encontraremos uno, pero no sabemos si es el “primero”, el “último”, o uno cualquiera del grupo, hacemos lo siguiente: vamos “hacia arriba” hasta llegar a un elemento distinto y luego “bajamos” listando las posiciones mientras los elementos sean iguales al buscado. Intercalación: Tenemos dos arreglos, ambos del mismo tipo de elementos. Ambos ordenados con el mismo criterio (ambos en forma creciente o ambos en forma decreciente). No necesariamente ambos con la misma cantidad de elementos. Llamemos a y b a ambos arreglos. Sea N la cantidad de elementos de a, y sea M la cantidad de elementos de b. Queremos armar un nuevo arreglo c que contenga todos los elementos de a y todos los elementos de b (es decir que tenga N+M elementos) y que esté también ordenado con el mismo criterio. Veamos un ejemplo a(N=3) 9 35 47 b(M=7) 7 20 25 43 50 73 92 c ( 7 + 3 = 10 elementos) 7 9 20 25 35 43 47 50 73 92 (podría surgir la idea de armar un nuevo arreglo que contenga al principio los elementos de a y colocar a continuación los de b y luego a este nuevo arreglo ordenarlo. Pero esto es muy ineficiente comparado con el método que veremos a continuación) Vamos a llamar i al índice con que vamos a recorrer a, j al índice con que vamos a recorrer b y k al índice con que vamos a recorrer c. La idea es recorrer desde el principio los arreglos a y b, comenzamos con los índices i, j, k en 1 comparamos ai con bj y al elemento menor lo copiamos en ck, incrementamos en 1 a k y al índice del arreglo del que hayamos copiado, y así hasta agotar uno de los dos arreglos (a o b), luego ponemos los elementos que quedaban sin colocar del otro, “al final” de c. Vamos directamente al algoritmo Ejemplo25: Leer un arreglo de 10 elementos reales (se sabe que ingresa ordenado en forma creciente), leer luego un arreglo de 8 elementos reales (se sabe que ingresa ordenado en 70 forma creciente). Formar un arreglo que contenga todos los elementos del primero y todos los elementos del segundo y que también esté ordenado en forma creciente. Escribirlo programa Veinticinco variables a [ 10 ] , b [ 8 ] , c [ 18 ] de reales i , j , k , q : enteras comienzo escribir (“Ingrese el primer arreglo”) para i de 1 a 10 leer ( a [ i ] ) finpara escribir (“Ingrese el segundo arreglo”) para i de 1 a 8 leer ( b [ i ] ) finpara i←1 j←1 k←1 mientras i <= 10 y j <= 8 hacer si a [ i ] <= b [ j ] entonces c[k]←a[i] i ←i + 1 sino c[k]←b[j] j←j+1 finsi k ← k +1 finmientras si i > 10 entonces para q de j a 8 hacer c[k]← b[q] k ← k +1 finpara sino para q de i a 10 hacer c[k]← a[q] k ← k +1 finpara finsi escribir (“El resultado es”) para i de 1 a 18 hacer escribir ( c [ i ] ) finpara fin programa Veinticinco program Veinticinco implicit none real a(10) , b(8) , c(18) integer i , j , k , q write (*,*) “Ingrese el primer arreglo” read (*,*) a write (*,*) “Ingrese el segundo arreglo” read (*,*) b 71 i = 1 j = 1 k = 1 do while ( i <= 10 .and. j <= 8 ) if ( a(i) <= b(j) ) then c(k) = a(i) i = i + 1 else c(k) = b(j) j = j + 1 endif k = k + 1 enddo if ( i > 10 ) then do q = j , 8 c(k) = b(q) k = k + 1 enddo else do q = i , 10 c(k) = a(q) k = k + 1 enddo endif write (*,*) “El resultado es” write (*,*) c end program Veinticinco Eficiencia del algoritmo: ¿ Por que decimos que el algoritmo que acabamos de presentar es mas eficiente que copiar a en c y a partir de ahí copiar b en c, para luego ordenar c ? En nuestro algoritmo no tenemos ningún anidamiento de estructuras de repetición. En cambio en los métodos de ordenamiento tenemos anidadas dos estructuras de repetición. Supongamos que tenemos dos para anidados. El de mas afuera que se repita 200 veces y el interior 400 veces para i de 1 a 200 para j de 1 a 400 sentencia finpara finpara esta sentencia se ejecutará 80.000 veces ¿Qué pasa, por ejemplo con el método por intercambio? Supongamos que queremos ordenar por intercambio un arreglo de 200 elementos 72 Analicemos cuantas veces se ejecuta el IF que está dentro de esos dos DOs anidados. Usemos el ejemplo ya hecho, pero para 200 elementos program Diecinueve integer i,j real v(200), aux write(*,*)"Ingrese el arreglo" read(*,*)(v(i),i=1,200) do i = 1 , 199 do j = 199 , i , -1 if ( v(j) > v(j+1) ) then queremos ver cuantas veces se ejecuta este IF aux = v(j) v(j) = v(j+1) v(j+1) = aux endif enddo enddo write(*,*)"El mismo arreglo ordenado" write(*,*)(v(i),i=1,10) end program Diecinueve El de DO de mas afuera se ejecutará 199 veces. y el de mas adentro se ejecutará: la 1ra. vez, 1 vez la 2da. vez, 2 veces ........ ........ la última vez, 199 veces es decir que el de mas adentro (y por lo tanto el IF) se ejecutará 199 + 198 + 197 + . . . . 3 + 2 + 1 = (199 x 200) / 2 = 19.900 veces o sea que el IF se ejecutará 19.900 veces n n.(n+1) i = ---------------2 Zzzzz i=1 Otra manera, aproximada de pensarlo es decir: si el de mas adentro se ejecuta tal cual lo explicamos, podemos pensar que en promedio funciona como un DO que va de 1 a (1+199)/2=100 veces, Y como el DO de mas afuera se ejecutaba 199 veces, el IF se ejecutará 199 x 100 = 19.900 73