Texto-Técnicas de Programación

273

Click here to load reader

description

Libro para el curso de Técnicas de programación en la PUCP del profesor Miguel Guanira

Transcript of Texto-Técnicas de Programación

Page 1: Texto-Técnicas de Programación

Técnicas de Programación

Apuntes de Clases

Prof. Miguel Guanira.

Page 2: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

2

PREFACIO ............................................................................... 4 CAPÍTULO 1: Almacenamiento de datos en el computador ......................... 6

Memoria del computador ............................................................................................................................... 6 Memoria Principal ............................................................................................................................................. 6 Memoria Secundaria ....................................................................................................................................... 7 Codificación de la información para su almacenamiento ...................................................................... 7 Representación de números negativos ...................................................................................................... 9 Representación de números reales o de punto flotante .................................................................... 12 Almacenamiento invertido o “back-words” ............................................................................................. 15 Representación de caracteres ................................................................................................................... 16 Representación de cadenas de caracteres ............................................................................................ 18 Otras representaciones más complejas .................................................................................................. 19

CAPÍTULO 2: Software y lenguajes de programación .............................. 24 Conceptos generales de software ............................................................................................................ 24 Clasificación del software .......................................................................................................................... 24 Sistema Operativo ........................................................................................................................................ 24 Algoritmos ....................................................................................................................................................... 26 Lenguajes de programación ........................................................................................................................ 29

CAPÍTULO 3: Ciclo de vida de un proceso de desarrollo de software ............ 34 Pasos involucrados en la solución de problemas en computación .................................................... 34 Traductores de lenguajes de programación: ........................................................................................ 39 Compiladores e Intérpretes ...................................................................................................................... 39

CAPÍTULO 4: Estructura general de un programa ................................. 41 Definición de programa ................................................................................................................................ 41 Concepto de Identificador ......................................................................................................................... 41 Partes constitutivas de un programa: ..................................................................................................... 42 Asignación de valores y expresiones ....................................................................................................... 49 Instrucciones que permiten la salida y el ingreso de datos............................................................. 66 Ejemplos de programas secuenciales: ..................................................................................................... 72 Introducción al uso de archivos de texto ............................................................................................. 80 Ejemplos de programas que emplean archivos de textos: ................................................................ 84

CAPÍTULO 5: Programación estructurada ........................................... 89 Estructura Secuencial .................................................................................................................................. 91 Estructura Selectiva .................................................................................................................................... 91 Estructura Iterativa.................................................................................................................................... 97 Otras Estructuras ...................................................................................................................................... 105 Solución de problemas con estructuras de control .......................................................................... 109

CAPÍTULO 6: Programación modular: ............................................... 120 Implementación de la programación modular ...................................................................................... 121 Variables globales, locales y estáticas ................................................................................................. 125 Parámetros por valor y por referencia ................................................................................................ 130 Solución de problemas empleando diseño descendente y programación modular ................... 135

CAPÍTULO 7: Aplicaciones con arreglos ............................................ 149 Definición ....................................................................................................................................................... 151 Implementación de arreglos unidimensionales .................................................................................... 151 Lectura e impresión de los datos en un arreglo unidimensional .................................................... 154

Page 3: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

3

Aplicaciones que emplean arreglos de una dimensión ...................................................................... 156 Ordenación de datos ................................................................................................................................... 171 Cómo desordenar datos.............................................................................................................................. 181

CAPÍTULO 8: Arreglos de dos o más dimensiones ................................. 187 Implementación de arreglos de más de una dimensión .................................................................... 187 Aplicaciones que emplean arreglos de dos o más dimensiones ...................................................... 189

CAPÍTULO 9: Manejo de cadenas de caracteres .................................. 211 Definición ....................................................................................................................................................... 211 Declaración de una cadena de caracteres ........................................................................................... 212 Funciones y procedimientos que manejan cadenas: .......................................................................... 213 Aplicaciones que emplean cadenas de caracteres: ........................................................................... 223

CAPÍTULO 10: Registros ............................................................ 239 Definición ...................................................................................................................................................... 239 Implementación de un registro ............................................................................................................... 239 Situaciones complejas en la implementación de un registro .......................................................... 242 Aplicaciones que emplean registros ...................................................................................................... 244

CAPÍTULO 11: Archivos binarios ................................................... 255 Definición ...................................................................................................................................................... 255 Formas en las que se puede almacenar información en un archivo .............................................. 255 Diferencias funcionales entre un archivo de textos y uno binario .............................................. 256 Funciones y procedimientos elementales que manejan archivos binarios: ................................. 260 Acceso secuencial a un archivo binario ................................................................................................ 263 Acceso directo a un archivo binario ...................................................................................................... 268 Aplicaciones que emplean archivos binarios.................................... ¡Error! Marcador no definido.

Page 4: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

4

PREFACIO

Durante los años que tengo enseñando a programar a alumnos de pregrado, he notado que la mayoría de libros que se emplean para esta tarea están orientados más a la enseñanza de un lenguaje de programación. Estos volúmenes presentan tan solo reglas de sintaxis que muestran cómo escribir determinada instrucción, y con ejemplos muy sencillos muestran cómo estas instrucciones se deben colocar en un programa. Sin embargo, aprender a programar un computador es una tarea más compleja que aprender un conjunto de reglas sintácticas; crear un programa implica primero, haber entendido el problema que se nos presenta, luego encontrar la manera de solucionar el problema, que en la mayoría de casos esta solución estará enfocada al problema mismo y no a la forma cómo trabaja el computador ni al lenguaje de programación que se vaya a utilizar. Una vez que se encontró la solución se deben ordenar las ideas que implican esta solución describiéndola paso a paso. Concluida esta tarea, recién se empieza a plasmar la solución en un programa de computador.

Es por este motivo que se concibió la idea de crear un texto que se enfoque más a la enseñanza de la programación en lugar que a la de aprender un lenguaje de programación. El primer capítulo estará centrado a la forma cómo se almacenan y representan los datos en el computador, esto ayudará a tomar conciencia de las limitaciones que tiene el computador de modo que a la hora de escribir el programa se tomen en cuenta para conseguir la solución esperada. En el segundo capítulo se hará una revisión general de los conceptos relacionados con el software y los lenguajes de programación. En capítulo tres veremos en modo general qué pasos se deben seguir para solucionar un problema. A partir de allí el texto se enfocará en el planteamiento de problemas y de cómo llegar a solucionarlo, incluyendo en cada uno los conceptos de programación requeridos. Se empezará con problemas sencillos para ir aumentando paulatinamente la dificultad hasta llegar a plantear problemas que requieran estructuras de datos complejas como arreglos, registros, etc.

La meta de este texto es la de aprender a desarrollar programas, se puede afirmar que no se aprende a programar en forma teórica, la solución de un problema debe concluirse con la escritura del programa, su colocación en el computador y su ejecución para ver si la respuesta que da se ajusta a la esperada para la solución del problema. Por esto el texto estará ligado a un lenguaje de programación, al principio, como en el capítulo cuatro, se mencionarán varios lenguajes de programación, pero conforme se aumente la complejidad del los problemas se centrará en uno solo. El lenguaje de programación que se ha elegido para este fin es el Pascal y la razón para esto es que Pascal fue diseñado para aprender conceptos de programación, otros lenguajes como el C, C++, Java etc. han sido diseñados para producir programas, alguien que ya aprendió a programar encontrará un deleite en emplear estos últimos lenguajes porque incluyen elementos que facilitan la presentación final del programa o que solucionan, a modo de “cajas negras”, partes complejas de la solución; sin embargo para una persona que recién está aprendiendo a programar, estos elementos no hacen más que terminar confundiéndolos o distrayéndolos en puntos de la programación que no darán solución al problema que se plantea.

Page 5: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

5

Por el contrario el lenguaje Pascal es un lenguaje muy sencillo y fácil de aprender, además definen reglas muy estrictas que hacen que el programador inexperto se de cuenta muy rápidamente de sus errores al momento de compilar su programa, reforzando por lo tanto los conceptos aprendidos. Además el lenguaje es muy rico en instrucciones y en la posibilidad de emplear estructuras de datos muy complejas lo que favorece la elaboración de programas más complicados. Una vez que se haya aprendido los conceptos de programación y adquirido una cierta práctica con este lenguaje, la migración a otros lenguajes de programación más modernos y complejos se hará con un mínimo de esfuerzo.

Page 6: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

6

CAPÍTULO 1: Almacenamiento de datos en el computador

Una de las primeras cosas que se debe tener en cuenta para aprender a programar un computador es la forma cómo se almacena la información en la computadora. Cuando una persona ajena al mundo de la computación se pone delante de la máquina, ve un apantalla que contiene muchas imágenes, íconos, etc., y que por medio de una serie de dispositivos como el teclado, el mouse, o la misma pantalla, puede crear documentos, trazar una serie de gráficos, crear imágenes o hacer cálculos complejos. Aquellas personas, que simplemente usan el computador, no se dan cuenta lo que hay detrás de esa máquina, me refiero a la manera cómo ese texto, esa imagen o ese sonido puede estar almacenado y puede ser procesado por el computador de manera que podamos apreciarlos y sacarle el provecho que le sacamos cada día.

La programación de computadores implica la creación de aplicaciones que permita a las personas poder procesar información, esto es, crear aplicaciones para poder escribir texto, almacenarlos, recuperarlos y actualizarlos, poder trazar una gráfica, procesar imágenes, o hacer que se realicen cálculos complejos y reiterativos. Pues bien, antes de poder crear una aplicación lo primero que debemos conocer es cómo se almacena la información en el computador, también se tiene que entender que el computador es una máquina creada por el hombre y por lo tanto un elemento con muchas limitaciones, que un computador no piensa, que sólo se limita a repetir una serie de acciones y que esas acciones no son otra cosa que órdenes conglomeradas en lo que se conoce como un programa o una aplicación de computador.

En este capítulo se estudiarán en primer lugar los dispositivos que emplea el computador para almacenar la información, y en segundo lugar, donde nos explayaremos más, veremos cómo se almacena la información en la computadora, veremos cómo una máquina llena de cables y circuitos electrónicos puede guardar y procesar un número, un texto, una imagen, etc. y lo más importante, se las limitaciones que existen y cómo se hace para adaptarse a ellas.

Comencemos entonces analizando los medios que tiene le computador para almacenar información.

Memoria del computador El dispositivo que permite almacenar información, vale decir, textos, números, imágenes, sonidos, en la computadora se denomina “memoria del computador”. En un computador podemos distinguir dos tipos de memoria, la memoria principal y la secundaria.

Memoria Principal La memoria principal del computador es el lugar donde el computador colocará la información que está procesando en ese momento. Si se quiere ejecutar un programa como una hoja de cálculo, un procesador de palabras, un manejador de imágenes, etc., el computador colocará primero el programa que se va a ejecutar en la memoria principal y lo ejecutará desde allí. Una vez que el programa esté en ejecución se podrá añadir información al computador, esto es, si estamos trabajando con un procesador de palabras se agregará el texto que se desea crear, este texto también es colocado en la memoria principal del computador.

Page 7: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

7

La memoria principal tiene un problema, es “volátil”, esto quiere decir que si el computador está desconectado la información que guardaba allí se pierde y no habrá forma de recuperarla. Es por esto que el computador requiere de otro dispositivo que permita almacenar información de modo que ésta no se pierda.

Memoria Secundaria La memoria secundaria sirve para almacenar, de modo permanente, información procesada por el computador. Esto quiere decir que lo que se almacena en este dispositivo, no se pierde así el computador esté apagado, como sucede con el contenido de la memoria principal. El problema con la memoria secundaria es que la información que se guarda allí no se puede procesar, sólo se almacena; para poder procesar la información que se encuentra allí el computador tendrá que llevarla a la memoria principal.

Esto último se refiere a que, una imagen (por ejemplo un fotografía), un directorio telefónico, un programa, etc., pueden ser almacenados en la memoria secundaria, sin embargo para que una imagen pueda ser vista en el monitor del computador o impresa en papel, ésta debe ser llevada a la memoria principal del computador. Si, dado el nombre de una persona deseamos obtener su número telefónico, el directorio, parcial o totalmente, debe ser llevado a la memoria principal para realizar la búsqueda. Si deseamos escribir un documento, primero debe ejecutarse el programa Word, para esto se debe llevar el programa a la memoria principal para su ejecución. Una vez escrito el documento, si se desea preservarlo para utilizarlo en otro momento, éste debe ser almacenado en la memoria secundaria.

Existen diferentes dispositivos que cumplen la función de memoria secundaria. Estos dispositivos son por ejemplo los discos duros, los discos compactos, las memorias flash o memorias USB, etc.

Codificación de la información para su almacenamiento ¿Cómo hace el computador para almacenar “un texto”, “un número”, “una fotografía”, “un programa”, etc.?, ¿Cómo hace para ejecutar un programa? Esta tarea no es sencilla y se ha requerido una solución muy compleja e ingeniosa.

Para entender mejor su funcionamiento, imaginemos un interruptor eléctrico como se muestra en la siguiente figura:

Este dispositivo puede estar colocado en dos estados, uno en el que la compuerta está abierta, por lo tanto no pasa la corriente y por lo tanto no es registrada por un detector y otro en el que la compuerta está cerrada y por lo tanto pasa la corriente y esta es detectada. Llamemos al primer caso “estado cero” y al segundo “estado uno”.

Estado 0 (no pasa corriente), o simplemente 0

Estado 1 (pasa corriente), o simplemente 1

Page 8: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

8

Con este dispositivo se podría representar, al detectarse la corriente, de dos números, el cero (con el estado cero) y el uno (con estado uno), coincidentemente estos dos números o dígitos corresponden a los dígitos que se emplean para representar el sistema de numeración binario. A cada uno de estos estados se les denomina “bit” que deriva de la frase en inglés “binary digit”.

En los computadores actuales, no existen estos interruptores eléctricos, en lugar de eso existen dispositivos electromagnéticos que permiten representar de estos dos estados y por lo tanto los dos dígitos binarios.

Siguiendo con la analogía, con un solo interruptor sólo se puede representar el 0 y el 1, ¿Qué pasa con los demás números p. e.: 23, 1623, 45.67, -12 -89.432, 0.000034? ¿Qué pasa con los textos, las imágenes, el sonido, los videos, etc.? ¿Cómo se almacenan o representan? La respuesta a estas preguntas requiere también soluciones complejas e ingeniosas.

Si tuviéramos un dispositivo en el que se agruparan varios de estos interruptores, por ejemplo 8, se tendría un esquema como el que se muestra a continuación:

Cada interruptor podría encontrarse en un estado 0 ó 1.

En la imagen anterior, todos están en 0, por lo tanto en conjunto el dispositivo pondrían estar representando el estado 0000 0000, o simplemente el estado 0.

Si se tuviera un esquema como el que se muestra a continuación, en el que el primer interruptor estuviera cerrado (estado 1) y los demás abiertos (estado 0):

Se podría pretender representar el estado 0000 0001, o simplemente el estado 1.

La siguiente figura representa alguno de los diferentes estados que podría adaptar el dispositivo:

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Estado 0000 0010 o estado 2 Estado 0000 0011 o estado 3

0 1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Estado 0101 1011 o estado 91

Page 9: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

9

Como se puede apreciar en un dispositivo como este se pueden obtener 256 estados diferentes, por lo tanto se puede relacionar cada uno de estos estados con valores que van de 0 a 255. Dispositivos análogos a éste, conforman cada una de las celdas de memoria del computador.

En términos generales se denomina “Byte” a una unidad de información compuesta por ocho bits.

Bueno, con este dispositivos podemos almacenar números que van de 0 a 255, ¿qué se hace para almacenar números de mayor tamaño?

La respuesta es simple, agrupando más de una celda de memoria, y luego tomando la representación del conjunto, así:

Con esto, prácticamente se está solucionando el problema de los números de gran tamaño, sin embargo se debe tomar en cuenta que los sistemas definidos en el computador no dan la libertad de representar cualquier número, los sistemas definen primero bajo qué esquema se representarán sus números, empleando un byte, dos bytes, cuatro bytes, etc., una vez definido esto, el usuario sólo podrá emplear un rango de números de acuerdo a ese esquema. Así, si el sistema define una representación de 2 bytes para sus números, no se podrá manejar por ejemplo el número 70,000.

Representación de números negativos La pregunta que viene ahora es ¿qué pasa con los números negativos? Para solucionar este problema, se debe tener en cuenta, primero que el número se debe representar en un esquema de bytes, el signo “-” tiene que entrar en este esquema, y en segundo lugar, que operar dos números bajo esta representación debe ser una tarea extremadamente sencillas. Por último, si la operación da como respuesta un valor en el que se requieran más bits para representar el resultado, el valor resultante se almacenará sin el bit más significativo, éste se pierde o se acarrea según el sistema o el programa que se esté empleando.

65,536 estados diferentes

4,294’967,296 estados diferentes

Page 10: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

10

Suma de bits: 0 + 0 = 0 1 + 0 = 1 0 + 1 = 1 1 + 1 = 0 Se acarrea 1 Suma de bytes: 0000 0000 + 0000 0001 = 0000 0001 0000 0001 + 0000 0001 = 0000 0010 1111 1111 + 0000 0001 = 0000 0000 Se acarrea 1

En este sentido, la representación de números negativos debe permitir realizar operaciones tan sencillas como la que se muestra en la tabla anterior.

A continuación se muestran tres maneras de representarlos, analizando la posibilidad de hacer con ellas operaciones sencillas.

Método 1: Empleo de un bit para representar el signo

Este método consiste en emplear un bit, de los ocho que tiene un byte, para representar el signo. Esto es 0 para números positivos y 1 para negativos. Los restantes bits se empalan para representar el número.

Lo primero que se debe apreciar es que en un byte ya no se podrán representar números que vayan de 0 a 255, sino que ahora sólo se podrá representar valores de 0 a 127, esto debido a que se cuenta con tan solo 7 bits, el octavo se emplea para el signo.

Bajo este esquema, el número 27 se representaría como 0001 1011 y -27 se representa como 1001 1011. El esquema de representación es sencillo, sin embargo al querer hacer operaciones con estos números se presentan estas situaciones:

27 + 9 ---- 36

0001 1011 + 0000 1001 ----------- 0010 0100

0010 0100 es la representación de 36, la operación es correcta.

-27 + -9 ---- -36

1001 1011 + 1000 1001 ---------- 0010 0100 El acarreo final se pierde.

0010 0100 representa 36 y no -36, la operación es incorrecta.

27 + -9 ---- 18

0001 1011 + 1000 1001 ---------- 1010 0100 1010 0100 representa -36 y no 18.

Como se puede apreciar, no es tan sencillo sumar dos números en esta representación, se tendría que agregar condicionales para que la operación sea hecha en forma correcta, y esto no se puede aceptar.

Método 2: Complemento

Este método consiste en representar un número negativo como el complemento del número positivo, esto es 27 se representa como 0001 1011 y -27 como 1110 0100, observe que en el primer caso el bit más significativo es 0 y en el segundo caso es 1, aquí también se podrán representar números que van sólo de -127 a 127.

Veamos cómo se comporta esta representación cuando se desea hacer una suma:

Page 11: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

11

27 + 9 ---- 36

0001 1011 + 0000 1001 ---------- 0010 0100

0010 0100 es la representación de 36, la operación es correcta.

-27 + 9 ---- -18

1110 0100 + 0000 1001 ---------- 1110 1101

1110 1101 es el complemento de 0001 0010 y esto representa 18, por lo tanto la operación es correcta.

27 + -9 ---- 18

0001 1011 + 1111 0110 ---------- 0001 0001

0001 0001 representa 17 y no 18. La operación es incorrecta

-27 + -9 ---- -36

1110 0100 + 1111 0110 ---------- 1101 1010

1101 1010 es el complemento de 0010 0101 y esto representa 37 y no 36 por lo tanto la respuesta no es correcta.

Nos encontramos en un caso similar al anterior, la representación de los números es sencilla pero las operaciones con ellos no.

Método 3: Complemento a 2

Este método consiste en representar un número negativo como el complemento del número positivo pero a esta representación se le suma 1, esto es 27 se representa como 0001 1011 y -27 como 1110 0101 (a diferencia de la representación anterior que daba 1110 0100), observe que, de la misma forma, en el primer caso el bit más significativo es 0 y en el segundo caso es 1 y por lo tanto se podrán representar números que van de -128 a 127.

Veamos cómo se comporta esta representación cuando se desea hacer una suma:

27 + 9 ---- 36

0001 1011 + 0000 1001 ---------- 0010 0100

0010 0100 es la representación de 36, la operación es correcta.

-27 + 9 ---- -18

1110 0101 + 0000 1001 ---------- 1110 1110

1110 1110 es el complemento a 2 de 0001 0010 y esto representa 18, por lo tanto la operación es correcta.

27 + -9 ---- 18

0001 1011 + 1111 0111 ---------- 0001 0010

0001 0010 representa 18, la operación es correcta.

-27 + -9 ---- -36

1110 0101 + 1111 0111 ---------- 1101 1100

1101 1100 es el complemento a 2 de 0010 0100 y esto representa 36 por lo tanto la respuesta es correcta.

Esta es una representación sencilla en la que se pueden hacer operaciones sencillas sin que, como en los otros casos, tengan que introducir condicionales.

La representación de números enteros en complemento a 2 es un estándar por lo que todos los lenguajes de programación lo emplean.

Page 12: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

12

Representación de números reales o de punto flotante Una característica de los número reales es que si tomamos dos números cualesquiera que pueden estar muy juntos, se puede probar que existen infinitos números entre esos dos números. Así por ejemplo, si tomamos el 2 y el 3, podemos encontrar el 2.1, 2.2, 2.3,… 2.9, pero también el 2.01, 2.02,… 2.09, 2.11, 2.12,… y así sucesivamente.

Esta propiedad hace que para representar números reales se haya tenido que definir una forma mucho más compleja que para los números enteros. Más aun, los diferentes autores no han podido ponerse de acuerdo sobre el formato final que se les da a los números reales de modo que los diferentes lenguajes de programación emplean distintos formatos para el manejo de sus números reales, por ejemplo el Pascal emplea 6 bytes para representar un número real, mientras que C lo hace con 4, por esta razón la información que sale de un lenguajes no puede ser empleada con facilidad por otro lenguaje. Esto no se da con los enteros ya que allí si se emplea un estándar.

De lo que sí se puede hablar de común que existen entre los formatos de números reales es en el principio que se emplea para representarlos en memoria. El método utilizado se base en la “notación científica normalizada”, esta notación se basa en que un número se multiplique o divida varias veces por la base en la que esté representado hasta que todos los dígitos menos uno aparezcan a la derecha del punto decimal, de modo que la parte entera del número sea mayor que cero.

Por ejemplo:

El número 157.381 (10) puede ser representado como 1.57381x102 El número 0.00007281 (10) puede ser representado como 7.281x10-5 El número -7800000000.0 (10) como -7.8x109 El número - 0.00000000000456 (10) como -4.56x10-15 El número 10101.1101 (2) como 1.01011101x24 El número 0.00F3A (16) como F.3Ax16-3 etc.

En términos generales se puede afirmar que un número cualquiera R expresado en una base b, se puede representar como:

R = m x be Donde “m” es un número cuya parte entera es de un solo dígito mayor que cero, “b” es la base del sistema y “e” es un número entero que denota cuántas veces se desplazó el punto decimal, a la derecha (+) o izquierda (-), para obtener el valor de “m” a partir de “R”. En otras palabras el número está compuesto por una “mantisa multiplicada por una “base” elevada a una potencia entera denominada “exponente”.

La ventaja de este método es que se pueden representar números muy grandes y también extremada mente pequeños como 1.3456x1023 ó 8.09234x10-56.

Veamos ahora cómo esta notación científica puede ser empleada por los computadores para representar los números de punto flotante. Primero se decide cuantos bytes se van a utilizar en esta representación, por ejemplo Pascal emplea 6 bytes para sus datos de tipo real, por otro lado el lenguaje C emplea 4 bytes para representar el tipo float.

Page 13: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

13

En el caso del lenguaje C, la representación de 4 bytes (32 bits), se distribuirían de la siguiente manera: un bit para el signo del número, 8 bits para representar el exponente, el resto (23) queda para la mantisa.

En esta representación no se emplea el complemento a dos para la mantisa por lo que el bit de signo indicará positivo con un cero (0) y negativo con un uno (1). Por otro lado debido a que el sistema binario (en base 2) sólo se emplea los dígitos 0 y 1, la parte entera de la mantisa siempre será 1, este dígito no se lleva a la representación en el computador como se verá más adelante.

Para manejar el exponente, tampoco se emplea la notación en complemento a dos debido a que complicaría las operaciones con los números de ponto flotante. Por esto debido, a que el exponente estaría en el rango de -126 á 127, éste se desplaza en 127, con lo que se obtienen valores entre 1 y 254. El exponente cero (0) se emplea para denotar el número 0 mientras que el valor 255 para denotar el valor infinito (∞).

Ejemplos:

1) El número 734.8267 de representaría de la siguiente manera:

Bit de signo: 0 (número positivo)

Mantisa: 734.8267 (10) = 1011011110.110100111010001010011100011… (2)

Mantisa normalizada: 1.011011110110100111010001001110001111001… x 29

Exponente: 9, normalizado = 136(10) (9+127) 10001000(2)

Por lo tanto el número quedará representado como:

Signo Exponente Mantisa 0 10001000 01101111 01101001 1101001

Separado en bytes tendíamos:

01000100 00110111 10110100 11101001 o, en hexadecimal:

44 37 B4 E9

2) El número -0.000008267 de representaría de la siguiente manera:

Bit de signo: 1 (número negativo) Mantisa: 0.000008267 (10) =

0.00000000000000001000101010110010011111110100000011101 Mantisa normalizada: 1.000101010110010011111110100000011101…x 2-17

Exponente: -17, normalizado = 110(10) (-17+127) 01101110 (2)

Por lo tanto el número quedará representado como:

Signo Exponente Mantisa 1 01101110 00010101 01100100 1111111

Separado en bytes tendíamos:

10110111 00001010 10110010 01111111

o, en hexadecimal:

B7 0A B2 7F

Page 14: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

14

En el caso del lenguaje Pascal, los números de punto flotante se representan en 6 bytes (48 bits), los cuales se distribuyen de la siguiente manera: primero va el exponente de 8 bits para, le sigue un bit para el signo del número y el resto (39 bits) para la mantisa.

Nótese que la primera diferencia con respecto al C, sin contar el número de bits empleados, es la posición del signo, en C va primero mientras que en Pascal va después del exponente.

En esta representación tampoco se emplea el complemento a dos para la mantisa por lo que el bit de signo indicará positivo con un cero (0) y negativo con un uno (1).

Para la mantisa se emplea otro estándar, en éste, la notación se basa en que un número se multiplique o divida varias veces por la base en la que esté representado hasta que todos los dígitos aparezcan a la derecha del punto decimal, esto es, el número 157.381(10) se representa como 0.157381x103 a diferencia del otro método en el que se obtiene 1.57381x102. La mantisa se toma a partir del punto decimal. Finalmente, en la representación del número se toma a partir del segundo dígito, esto porque el primer dígito siempre será 1.

Para manejar el exponente, a éste se le suma 128, en lugar de 127 como se hace en C.

Ejemplos:

1) El número 734.8267 de representaría de la siguiente manera:

Mantisa: 734.8267 (10) = 1011011110.110100111010001010011100011110… (2)

Mantisa normalizada: 0.1011011110110100111010001010011100011110… x 210

Exponente: 10, normalizado = 138(10) (10+128) 10001010(2)

Bit de signo: 0 (número positivo)

Por lo tanto el número quedará representado como:

Exponente Signo Mantisa 10001010 0 01101111 01101001 11010001 01001110 0011110

Separado en bytes tendíamos:

10001010 00110111 10110100 11101000 10100111 00011110

o, en hexadecimal:

8A 37 B4 E8 A7 1E

2) El número -0.000008267 de representaría de la siguiente manera:

Mantisa: 0.000008267 (10) =

0.00000000000000001000101010110010011111101010000001111000

Mantisa normalizada: 0.1000101010110010011111101010000001111000…x 2-16

Exponente: -16, normalizado = 112(10) (-16+120) 01110000 (2)

Bit de signo: 1 (número negativo)

Por lo tanto el número quedará representado como:

Exponente Signo Mantisa 01110000 1 00010101 01100100 11111101 01000000 1111000

Page 15: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

15

Separado en bytes tendíamos:

01110000 10001010 10110010 01111110 10100000 01111000

o, en hexadecimal:

70 8A B2 7E A0 78

Almacenamiento invertido o “back-words” Como hemos podido apreciar en detalle hasta este momento, como se representan los valores numéricos en el computador, hemos apreciado que se requiere muchas veces más de un byte para poder representarlos. Sin embargo nos falta un concepto que es muy importante cuando se estudia la representación interna de datos, este concepto se denomina “almacenamiento invertido de palabras o back-words.

Cuando nosotros visualizamos un número éste lo leemos de izquierda a derecha, desde la cifra más significativa, esto es por ejemplo el número novecientos cuarenta y siete (947) la cifra de mayor valor (la más significativa), el 9, se encuentra a la izquierda del número, mientras que la de menos valor, el 7, se encuentra a la derecha.

Esto no tiene nada de particular puesto que así estamos acostumbrados a representarlos, sin embargo, podemos darnos cuenta que ese no es el orden en que operaríamos dos números, las operaciones se hacen de derecha a izquierda, empezando de la cifra menos significativa, esto es

5638 + 294 2

Para en computador, leer un número en un sentido y luego operarlo en el otro resulta una tarea muy complicada, por esta razón en el computador los números se almacenan empezando de la parte menos significativa hasta la más significativa. Esto quiere decir que, por ejemplo, si el computador trabajara en base 10 el número novecientos cuarenta y siete se almacenaría como 749 (y se leería siete cuarenta novecientos) a esta forma de almacenamiento o manejo de los números se denomina back-words. De ese modo se operaría el número en el mismo sentido al que se lee. Por ejemplo:

El número entero 8049653 representado en binario empleando 4 bytes como 00000000 01111010 11010011 11110101 (00 7A D3 F5(16)) se almacena en la memoria del computador como 11110101 11010011 01111010 00000000 (F5 D3 7A 00(16)), fíjese que la representación inversa es a nivel de bytes y no de bits.

En el caso de los números reales, en los ejemplos se tenía:

Para el número 734.8267 se obtenía para el lenguaje C, 44 37 B4 E9, en memoria se almacenaría como E9 B4 37 44. El mismo número en Pascal se obtenía 8A 37 B4 E8 A7 1E su representación en memoria será 8A 1E A7 E8 B4 37, obsérvese que en el caso del lenguaje C todo el número se escribe al revés, sin embargo en el Pascal, sólo se invierte la mantisa del número.

De igual manera, el número -0.000008267 que en C se representaría como B7 0A B2 7F en memoria se almacenaría como 7F B2 0A B7, y en Pascal su representación sería 70 8A B2 7E A0 78 y en memoria se almacena 70 78 A0 7E B2 8A.

Page 16: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

16

Representación de caracteres El computador sólo puede representar códigos binarios, lo hemos visto con los números. El caso de los caracteres no es una excepción, para almacenar en la memoria del computador un caracter, como la letras A, el símbolo #, etc., se requiere codificar el caracter en un formato binario.

A lo largo del tiempo se han venido utilizando diferentes sistemas de codificación, se empezó con el sistema BCDIC o Binary Coded Decimal Interchange Code (código binario de intercambio decimal), en él se agruparon los 64 caracteres más comunes y a cada uno se le relacionó con un número entero de 0 a 63, el valor binario del número corresponde al código del caracter. Como se puede deducir este sistema se representaba en 6 bits.

Por los años setenta se termina de implementar otro sistema de codificación denominado ASCII o American Standard Code for Information Interchange este sistema empezó empleando 7 bits para representar 128 caracteres que incluían las letras mayúsculas y minúsculas del alfabeto inglés así como caracteres de puntuación y de control. Sin embargo, con el paso del tiempo se amplió el código ASCII a 8 bits para poder incluir caracteres internacionales como la Ñ, á, ü, æ, ç etc. El código ASCII extendido, o por costumbre simplemente ASCII, se popularizó muchísimo y hasta hoy se viene empleando en forma masiva en todo el mundo, sin embargo se debe tener en cuenta que si bien los 128 primeros caracteres son siempre los mismos, el segundo grupo puede variar de acuerdo a la localidad donde se emplee. El juego de caracteres ASCII más común, se muestra en la tabla que a continuación se presenta.

Nótese que cada caracter viene acompañado de un código numérico (dado como un número decimal y como un número hexadecimal). Es este código y no el caracter es el que se almacena en la memoria del computador como cualquier valor entero, así por ejemplo un la letra A se almacena en la memoria como el número 65 codificado en binario.

Usted se preguntará entonces cómo se hace para diferenciar un caracter de un número entero almacenado en la memoria, la respuesta en es que no hay forma, serán las funciones de los lenguajes de programación las que harán la diferencia. Por ejemplo en el lenguaje C, a una variable entera asignada con el valor de 65 puede mostrarse en la pantalla como el número 65, como el número 41h o como el caracter A y esto sólo depende de la manera cómo se llame a la función de salida. Esto es:

x=65; printf(“%d”, x); muestra el número decimal 65 printf(“%x”, x); muestra el número hexadecimal 41 printf(“%c”, x); muestra el caracter A

Observe también que en la tabla los caracteres vienen agrupados, esto es las letras mayúsculas vienen juntas desde la posición 65 a la 90, igual pasa con las letras minúsculas y los dígitos. Note también que entre el caracter “A” y el caracter “a” existen 32 posiciones. Esto se ha hecho para podes manipular los caracteres en forma sencilla, si se tiene almacenado el código de la letra H (72) fácilmente se puede obtener el caracter I, para esto sólo hay que sumarle uno al código de la letra H, por otro lado si le sumamos 32 al código de la letra H obtenemos el caracter h.

Page 17: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

17

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

C Ó D I G O D E C

C Ó D I G O H E X

C A R A C T E R

0 00 32 20 64 40 @ 96 60 ` 128 80 Ç 160 A0 á 192 C0 224 E0 α 1 01 33 21 ! 65 41 A 97 61 a 129 81 ü 161 A1 í 193 C1 225 E1 β 2 02 34 22 " 66 42 B 98 62 b 130 82 é 162 A2 ó 194 C2 226 E2 Γ 3 03 ♥ 35 23 # 67 43 C 99 63 c 131 83 â 163 A3 ú 195 C3 227 E3 Π 4 04 ♦ 36 24 $ 68 44 D 100 64 d 132 84 ä 164 A4 ñ 196 C4 228 E4 Σ 5 05 ♣ 37 25 % 69 45 E 101 65 e 133 85 à 165 A5 Ñ 197 C5 229 E5 σ 6 06 ♠ 38 26 & 70 46 F 102 66 f 134 86 å 166 A6 ª 198 C6 230 E6 μ 7 07 • 39 27 ' 71 47 G 103 67 g 135 87 ç 167 A7 º 199 C7 231 E7 τ 8 08 40 28 ( 72 48 H 104 68 h 136 88 ê 168 A8 ¿ 200 C8 232 E8 Φ 9 09 41 29 ) 73 49 I 105 69 i 137 89 ë 169 A9 ­ 201 C9 233 E9 Θ 10 0A 42 2A * 74 4A J 106 6A j 138 8A è 170 AA ¬ 202 CA 234 EA Ω 11 0B 43 2B + 75 4B K 107 6B k 139 8B ï 171 AB ½ 203 CB 235 EB Δ 12 0C 44 2C 76 4C L 108 6C l 140 8C î 172 AC ¼ 204 CC 236 EC ∞ 13 0D 45 2D - 77 4D M 109 6D m 141 8D ì 173 AD ¡ 205 CD 237 ED Φ 14 0E 46 2E . 78 4E N 110 6E n 142 8E Ä 174 AE « 206 CE 238 EE Є 15 0F 47 2F / 79 4F O 111 6F o 143 8F Å 175 AF » 207 CF 239 EF Ω 16 10 48 30 0 80 50 P 112 70 p 144 90 É 176 B0 208 D0 240 F0 Ξ 17 11 49 31 1 81 51 Q 113 71 q 145 91 æ 177 B1 209 D1 241 F1 ± 18 12 50 32 2 82 52 R 114 72 r 146 92 Æ 178 B2 210 D2 242 F2 ≥ 19 13 ‼ 51 33 3 83 53 S 115 73 s 147 93 ô 179 B3 211 D3 243 F3 ≤ 20 14 ¶ 52 34 4 84 54 T 116 74 t 148 94 ö 180 B4 212 D4 244 F4 ∫ 21 15 § 53 35 5 85 55 U 117 75 u 149 95 ò 181 B5 213 D5 245 F5 ∫ 22 16 54 36 6 86 56 V 118 76 v 150 96 û 182 B6 214 D6 246 F6 ÷ 23 17 55 37 7 87 57 W 119 77 w 151 97 ù 183 B7 215 D7 247 F7 ≈ 24 18 ↑ 56 38 8 88 58 X 120 78 x 152 98 ÿ 184 B8 216 D8 248 F8 ° 25 19 ↓ 57 39 9 89 59 Y 121 79 y 153 99 Ö 185 B9 217 D9 249 F9 26 1A → 58 3A : 90 5A Z 122 7A z 154 9A Ü 186 BA 218 DA 250 FA · 27 1B ← 59 3B ; 91 5B [ 123 7B 155 9B ¢ 187 BB 219 DB 251 FB √ 28 1C ∟ 60 3C < 92 5C \ 124 7C | 156 9C £ 188 BC 220 DC 252 FC ⁿ 29 1D ↔ 61 3D = 93 5D ] 125 7D 157 9D ¥ 189 BD 221 DD 253 FD ² 30 1E 62 3E > 94 5E ^ 126 7E ~ 158 9E ₧ 190 BE 222 DE 254 FE 31 1F 63 3F ? 95 5F _ 127 7F 159 9F ƒ 191 BF 223 DF 255 FF

TABLA DE CARACTERES ASCII

Page 18: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

18

Como se dijo anteriormente, el código ASCII tiene un problema, este es que los caracteres de la segunda mitad de la tabla no son siempre los mismos lo que hace muy difícil su transporte a otras localidades. Por los años 90 se propuso un nuevo sistema de codificación denominado Unicode en los que cada caracter es codificado en dos bytes (16 bits). Este sistema permite la representación de de 65535 caracteres con lo que con esto queda prácticamente cubierto todos los símbolos del planeta. Unicode se ha convertido en un sistema de codificación cuyo objetivo es proporcionar el medio por el cual un texto en cualquier forma e idioma pueda ser codificado para el uso informático.

Unicode cubre la mayor parte de las escrituras usadas actualmente, incluyendo: Árabe, Armenio, Bengalí, Braille, Sílabas aborígenes canadienses, Cheroqui, Copto, Cirílico, Devanāgarī, Esperanto, Ge'ez, Georgiano, Griego, Guyaratí, Gurmukhi, Hangul (Coreano), Han (Kanji, Hanja y Hanzi), Japonés (Kanji, Hiragana y Katakana), Hebreo, Jémer (Camboyano), Kannada (Canarés), Lao, Latino, Malayalam, Mongol, Burmese, Oriya, Syriac, Tailandés (Thai), Tamil, Tibetano, Yi, Zhuyin (Bopomofo).

Unicode ha ido añadiendo escrituras y cubrirá aún más, incluyendo escrituras históricas menos utilizadas, incluso aquellas extinguidas para propósitos académicos: Cuneiforme, Griego antiguo, Linear B, Fenicio, Rúnico, Sumerio, Ugarítico.

Los jeroglíficos egipcios han sido propuestos para la versión de UNICODE 4.11

Representación de cadenas de caracteres La representación de cadenas de caracteres no está estandarizada, esto quiere decir que los diferentes lenguajes de programación no manejan las cadenas de caracteres de la misma manera, a pesar que el manejo de cadenas se basa en la aglomeración de caracteres, los cuales como hemos visto si están estandarizados.

En Pascal por ejemplo las cadenas de caracteres se basan en un tipo de datos denominado string. Este tipo de datos define las cadenas como un arreglo de 256 caracteres, cuyos índices van de 0 a 255. A partir de la posición 1 se colocan los caracteres que se quiere almacenar en la cadena, en la posición 0 se almacena un caracter cuya representación numérica corresponde a la cantidad de caracteres que se almacenó en la cadena. Por ejemplo:

Si se quiere almacenar la siguiente cadena de caracteres:

‘Rodolfo Alexander López Rojas’

Esto se haría de la siguiente manera: ↔ R o d o l f o A l e x a n d e r L ó p e z R o j a s … 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3

Nótese que en la posición 0 del arreglo se coloca el caracter ‘↔’ que corresponde al código 29 en la tabla ASCII.

Cualquier operación que se realice sobre la cadena afectará en forma automática la posición cero del arreglo de modo que en cada momento se tenga la cantidad de caracteres válidos almacenados.

1 Tomado de : http://es.wikipedia.org/wiki/Unicode

Page 19: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

19

Es bueno notar que debido a que la longitud de la cadena se denota por un solo caracter es imposible que esta representación pueda almacenar más de 255 caracteres.

Otros lenguajes de programación manejan sus cadenas de caracteres de manera muy diferente. Por ejemplo el lenguaje C, la representación de cadenas se hace por medio de arreglos, pero también por medio de direcciones de memoria (punteros) a espacios de memoria gestados dinámicamente (en tiempo de ejecución de un programa). Aquí los caracteres son colocados a la manera de un arreglo pero no se coloca la longitud en el inicio, más bien se coloca un caracter especial de terminación al final de los caracteres. En este caso se coloca el caracter cuyo código ASCII es cero. Todo trabajo efectuado sobre la cadena se empieza desde el inicio de la misma, hasta encontrar el caracter de terminación. Por ejemplo la cadena:

‘Rodolfo Alexander López Rojas’

Se almacenaría de la siguiente manera: R o d o l f o A l e x a n d e r L ó p e z R o j a s ‘\0’ … 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 ‘\0’ representa al caracter cuyo código ASCII es cero

Esto permite que las cadenas no tengan límite de tamaño, lo que puede ser una gran ventaja en algunos casos.

Otras representaciones más complejas Veamos ahora cómo se representan elementos más complejos en el computador, por ejemplo una imagen.

Una figura, una fotografía, un dibujo, etc., para que pueda almacenarse en el computador tendrán que de alguna manera llevarse a una representación basada en ceros y unos.

Si cogemos una fotografía y la observamos a través de un lente de aumento podemos observar que ésta no es impresa de manera continua como cuando se coge un pincel y se hace un trazo con él. Lo que observamos es que la fotografía está compuesta por una serie de puntos muy pequeños colocados lo suficientemente juntos como para que, vista la imagen a una distancia prudencial, parezca que esté formada por trazos continuos.

Si observamos un monitor de computador o televisión podemos observar el mismo esquema. Las imágenes se descomponen en pequeños elementos a los que se le asignan un color determinado, el conjunto visto desde una distancia prudencial forma una imagen.

Precisamente del inglés “picture element” deriva el nombre píxel, conocido como el elemento más pequeño en que se descompone una imagen.

La pregunta ahora es saber cómo representamos un píxel en términos de ceros y unos. Empecemos imaginándonos una figura en blanco y negro, para representar cada píxel sólo necesitaremos dos valores, uno para representar el blanco y otro para el negro, podemos asignar al blanco el valor de cero y uno al negro. De esta forma sólo necesitamos un bit para representar un píxel y un byte puede almacenar la representación de 8 píxeles. A una imagen que tuviera por ejemplo 4 colores, por ejemplo azul, verde, rojo y amarillo, podríamos asignarle cero al azul, uno al verde, dos al rojo y tres al amarillo. Aquí podemos observar que requerimos dos cifras binarias para representar cada color

Page 20: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

20

(azul = 00, verde = 01, rojo = 10, amarillo = 11), por lo tanto dos bits por cada píxel. Si quisiéramos almacenar una imagen que tenga millones de colores quizá requiramos cuatro o más “bytes” para representar cada píxel.

En el ejemplo de la página siguiente vemos dos figuras y a su lado la misma imagen pero descompuesta en los valores que tendría cada píxel.

En el primer caso la figura es en blanco y negro, en la otra empleamos hasta 8 colores diferentes. Mientras que en la primera se requiere sólo un digito binario para representar cada píxel en la segunda se requieren tres dígitos binarios por cada píxel.

Ya tenemos definido la manera que se codifica cada píxel, sin embargo aun no podemos decir que el problema está solucionado, no es suficiente con esta información. Un programa que pueda manipular imágenes debe ser capaz de procesar diferentes tipos de imágenes, de lo contrario se tendría que tener un programa para leer imágenes en blanco y negro, otro para imágenes con cuatro colores, otro para imágenes de 8 colores y así sucesivamente. Por otro lado se debería tener un programa por cada tamaño de imagen que se quisiera procesar (combinado con la cantidad de colores). Se podría pensar en miles de programas. Esto es ilógico, un solo programa debe ser capaz de procesar cualquier imagen.

Es por esto que el formato que se emplee para almacenar una imagen en el computador de una imagen debe incluir de alguna manera la cantidad de colores o el número de bits que se requieren para representar un píxel y el número de píxeles que tiene cada línea y el número de líneas que tiene la imagen. En este sentido se podría almacenar en el primer byte la cantidad de bits que se requieren para representar cada píxel, en los dos bytes siguientes se puede almacenar un número entero sin signo con la cantidad de píxeles por fila que tiene la imagen y en los dos bytes siguiente, con el mismo formato, la cantidad de filas de la imagen. Con esa información un programa podría reconstruir la imagen.

Page 21: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

21

Valor de cada píxel: 1 píxel = 1 bit 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 1,0,1,0,0,0,0,1,1,1,0,0,0,0,1,0,0,1 1,0,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,1 1,0,0,0,1,0,1,1,1,1,1,0,1,0,0,0,0,1 1,0,0,0,0,1,0,1,1,1,0,1,0,0,0,0,0,1 1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1 1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1 1,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1 1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1 1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1 1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,1 1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,1 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

Valor decimal de cada píxel. Valor de cada píxel: 1 píxel = 3 bits 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001 1,3,2,3,3,3,3,4,4,4,3,3,3,3,2,3,3,1 001,011,010,011,011,011,011,100,100,100,011,011,011,011,010,011,011,001 1,3,3,2,3,3,4,4,4,4,4,3,3,2,3,3,3,1 001,011,011,010,011,011,100,100,100,100,100,011,011,010,011,011,011,001 1,3,3,3,2,3,4,4,4,4,4,3,2,3,3,3,3,1 001,011,011,011,010,011,100,100,100,100,100,011,010,011,011,011,011,001 1,3,3,3,3,2,3,4,4,4,3,2,3,3,3,3,3,1 001,011,011,011,011,010,011,100,100,100,011,010,011,011,011,011,011,001 1,3,3,3,3,3,2,3,4,3,2,3,3,3,3,3,3,1 001,011,011,011,011,011,010,011,100,011,010,011,011,011,011,011,011,001 1,3,3,3,3,3,3,2,2,2,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,010,010,010,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,010,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,010,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,010,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,6,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,110,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,6,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,110,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,3,6,3,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,011,110,011,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,6,3,6,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,110,011,110,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,6,3,6,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,110,011,110,011,011,011,011,011,011,011,001 1,3,3,3,3,3,3,6,3,6,3,3,3,3,3,3,3,1 001,011,011,011,011,011,011,110,011,110,011,011,011,011,011,011,011,001 1,3,3,3,3,3,6,3,3,3,6,3,3,3,3,3,3,1 001,011,011,011,011,011,110,011,011,011,110,011,011,011,011,011,011,001 1,3,3,3,3,3,6,3,3,3,6,3,3,3,3,3,3,1 001,011,011,011,011,011,110,011,011,011,110,011,011,011,011,011,011,001 1,3,3,3,3,3,6,3,3,3,6,3,3,3,3,3,3,1 001,011,011,011,011,011,110,011,011,011,110,011,011,011,011,011,011,001 1,3,3,3,7,7,7,3,3,3,7,7,7,3,3,3,3,1 001,011,011,011,111,111,111,011,011,011,111,111,111,011,011,011,011,001 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001,001

Page 22: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

22

A continuación se muestra cómo quedaría la representación de cada figura.

Figura 1: Byte 1 Bytes 2 y 3 Bytes 4 y 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10

0000 0001 0000 0000 0001 0010 0000 0000 0001 0101 1111 1111 1111 1111 1110 1000 0111 0000 1001 1001 1 bit x píxel 18 píxeles x fila 21 filas Valor de cada pixel

Byte 11 Byte 12 Byte 13 Byte 14 Byte 15 Byte 16 Byte 17 Byte 18 Byte 19 Byte 20 0011 1110 0100 0110 0010 1111 1010 0001 1000 0101 1101 0000 0110 0000 1010 1000 0001 1000 0001 1100

Byte 21 Byte 22 Byte 23 Byte 24 Byte 25 Byte 26 Byte 27 Byte 28 Byte 29 Byte 30

0000 0110 0000 0010 0000 0001 1000 0000 1000 0000 0110 0000 0010 0000 0001 1000 0000 1000 0000 0110

Byte 31 Byte 32 Byte 33 Byte 34 Byte 35 Byte 36 Byte 37 Byte 38 Byte 39 Byte 40 0000 0010 0000 0001 1000 0000 1000 0000 0110 0000 0101 0000 0001 1000 0001 0100 0000 0110 0000 0101

Byte 41 Byte 42 Byte 43 Byte 44 Byte 45 Byte 46 Byte 47 Byte 48 Byte 49 Byte 50 0000 0001 1000 0010 0010 0000 0110 0000 1000 1000 0001 1000 0010 0010 0000 0110 0011 1000 1110 0001

Byte 51 Byte 52 Byte 53 1111 1111 1111 1111 1100 0000

Figura 2 Byte 1 Bytes 2 y 3 Bytes 4 y 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10

0000 0011 0000 0000 0001 0010 0000 0000 0001 0101 0010 0100 1001 0010 0100 1001 0010 0100 1001 0010 3 bits x píxel 18 píxeles x fila 21 filas Valor de cada pixel

Byte 11 Byte 12 Byte 13 Byte 14 Byte 15 Byte 16 Byte 17 Byte 18 Byte 19 Byte 20 0100 1001 0010 0100 1011 0100 1101 1011 0111 0010 0100 0110 1101 1011 0100 1101 1001 0010 1101 1010

Byte 21 Byte 22 Byte 23 Byte 24 Byte 25 Byte 26 Byte 27 Byte 28 Byte 29 Byte 30 0110 1110 0100 1001 0010 0011 0110 1001 1011 0110 0100 1011 0110 1101 0011 1001 0010 0100 1000 1101

Byte 31 Byte 32 Byte 33 Byte 34 Byte 35 Byte 36 Byte 37 Byte 38 Byte 39 Byte 40 0011 0110 1101 1001 0010 1101 1011 0110 1001 1100 1001 0001 1010 0110 1101 1011 0110 0100 1011 0110

Byte 41 Byte 42 Byte 43 Byte 44 Byte 45 Byte 46 Byte 47 Byte 48 Byte 49 Byte 50 1101 1011 0100 1110 0011 0100 1101 1011 0110 1101 1001 0010 1101 1011 0110 1101 1010 0100 1001 1011

Byte51 Byte 52 Byte 53 Byte 54 Byte 55 Byte 56 Byte 57 Byte 58 Byte 59 Byte 60 0110 1101 1011 0110 0100 1011 0110 1101 1011 0110 1101 0011 0110 1101 1011 0110 1101 1001 0010 1101

Byte 61 Byte 62 Byte 63 Byte 64 Byte 65 Byte 66 Byte 67 Byte 68 Byte 69 Byte 70 1011 0110 1101 1011 0100 1101 1011 0110 1101 1011 0110 0100 1011 0110 1101 1011 0110 1101 0011 0110

Byte 71 Byte 72 Byte 73 Byte 74 Byte 75 Byte 76 Byte 77 Byte 78 Byte 79 Byte 80 1101 1011 0110 1101 1001 0010 1101 1011 0110 1101 1011 1100 1101 1011 0110 1101 1011 0110 0100 101

Byte 81 Byte 82 Byte 83 Byte 84 Byte 85 Byte 86 Byte 87 Byte 88 Byte 89 Byte 90 1011 0110 1101 1011 0111 1001 1011 0110 1101 1011 0110 1100 1001 0110 1101 1011 0110 1101 1110 0110

Byte 91 Byte 92 Byte 93 Byte 94 Byte 95 Byte 96 Byte 97 Byte968 Byte 99 Byte 90 1101 1011 0110 1101 1011 0010 0101 1011 0110 1101 1011 1100 1111 0011 0110 1101 1011 0110 1100 1001

Byte 101 Byte 102 Byte 103 Byte 104 Byte 105 Byte 106 Byte 107 Byte 108 Byte 109 Byte 110 0110 1101 1011 0110 1111 0011 1100 1101 1011 0110 1101 1011 0010 0101 1011 0110 1101 1011 1100 1111

Byte 111 Byte 112 Byte 113 Byte 114 Byte 115 Byte 116 Byte 117 Byte 118 Byte 119 Byte 120 0011 0110 1101 1011 0110 1100 1001 0110 1101 1011 0111 1001 1011 0111 1001 1011 0110 1101 1011 0010

Byte 121 Byte 122 Byte 123 Byte 124 Byte 125 Byte 126 Byte 127 Byte 128 Byte 129 Byte 130 0101 1011 0110 1101 1110 0110 1101 1110 0110 1101 1011 0110 1100 1001 0110 1101 1011 0111 1001 1011

Byte 131 Byte 132 Byte 133 Byte 134 Byte 135 Byte 136 Byte 137 Byte 138 Byte 139 Byte 140 0111 1001 1011 0110 1101 1011 0010 0101 1011 0111 1111 1111 0110 1101 1111 1111 1101 1011 0110 1100

Byte 141 Byte 142 Byte 143 Byte 144 Byte 145 Byte 146 Byte 147 1001 0010 0100 1001 0010 0100 1001 0010 0100 1001 0010 0100 1001 0010

Page 23: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

23

Observe que la segunda imagen sólo tiene 8 colores, y es una figura pequeña; un imagen que ocupe toda la pantalla del computador en una resolución baja se maneja con 800x600 píxeles empleando millones de colores, imagínese el tamaño de esos archivos si se almacenara en un formato como el que describimos anteriormente.

El formato empleado es muy simple pero no es óptimo, en el mercado existen muchos formatos para almacenar imágenes, algunos se basan en algoritmos que permiten compactar las imágenes de modo que ocupen menos espacio en memoria. Entre los formatos conocidos podemos mencionar BMP, JPEG, GIF, PDF, PCX, TIFF etc.

Por último, imagínese usted cómo se puede almacenar en un computador un video, o sonidos, pues al igual que en los otros casos analizados, se tiene que dividir cada medio en elementos simples y estos llevarlos a representaciones binarias, no es una tarea fácil, pero si factible.

Page 24: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

24

CAPÍTULO 2: Software y lenguajes de programación

Conceptos generales de software Un computador está compuesto por dos elementos principales: el “Hardware” y el “Software”. Mientras el hardware está referido a todos los componentes físicos del computador, es decir todo lo tangible que se encuentre en él, como el monitor, teclado, disco duro, etc., el software es el conjunto de programas que permiten realizar alguna tarea en el computador de modo que se pueda controlar éste con un fin específico. Al software también se le conoce como el componente lógico o intangible del computador.

Se debe entender por “programa” al conjunto de órdenes que se proporcionan al computador y que éste luego ejecutará al pie de la letra, obteniendo un resultado esperado.

Clasificación del software El software se puede clasificar en tres grandes grupos: el “Software de sistema”, el “Software de aplicación” y el “Software de programación”.

El software de sistema lo conforman programas que permiten controlar los diferentes dispositivos del computador como por ejemplo la impresora, el monitor, el disco duro, etc. Entre estos se pueden distinguir los sistemas operativos como el Windows, Linux, etc. también dentro de esta categoría de software está el BIOS (Basic Input/Output System).

El software de aplicaciones permite solucionar problemas en general que no necesariamente tienen que ver con el computador. Un ejemplo de este tipo de software lo constituyen los procesadores de textos, los programas de diseño CAD (Computer Aided Design), los procesadores de imágenes, las hojas de cálculo, etc.

El software de programación se refiere a los entornos de desarrollo IDE (Integrated Development Environment). Este tipo de software está conformado por editores de textos, compiladores y depuradores que permiten generar software de las tres categorías antes descritas. Un entorno de desarrollo en C permitió la creación de sistemas operativos como el Unix.

Sistema Operativo Un sistema operativo es un software que permita administrar los recursos del computador. Recursos como por ejemplo el procesador, los medios de almacenamiento (memoria RAM, disco duro, discos compactos y flexibles, etc.), los dispositivos de entrada y salida (teclado, monitor, impresora, etc.) los dispositivos de comunicación.

Una tarea adicional de los sistemas operativos es la de proporcionar una interfaz entre el usuario y el computador de modo que se pueda establecer una comunicación entre el hombre y la máquina. Esta interfaz ha venido evolucionando a través del tiempo desde lo que se conoció como una interfaz de “línea de comandos” en donde el usuario del computador debía escribir un texto con la orden dada, hasta una interfaz gráfica basada en cajas de diálogo, ventanas, etc.

Page 25: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

25

La figura que se muestra a continuación muestra la forma como se obtiene la lista de archivo almacenados en el disco duro en una carpeta determinada.

Línea de comandos:

1) Aparece una línea de texto al final de la cual se escribe la orden:

2) Luego de escribir la orden se presiona la tecla “Enter” ( )

3) El resultado aparece en modo texto:

Page 26: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

26

Interfaz Grafica

1) las órdenes se dan a través del “Mouse”

Presionando el botón derecho

Presionando el botón izquierdo

2) Eligiendo las opciones a través de cajas de diálogo

Algoritmos La palabra algoritmo proviene del nombre del matemático persa llamado Mohamed Al-Khowarizmi (léase “al-jouresmi”). Este personaje vivió en el siglo IX D.C. y alcanzó una gran reputación por sus enunciados en los que daba las reglas “paso a paso” para poder sumar, restar, dividir y multiplicar números decimales. La traducción al latín del apellido derivó en “algorismi” y posteriormente “algoritmo”. En este sentido podemos definir un algoritmo como “la descripción paso a paso de un método para resolver un problema”.

Page 27: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

27

En realidad, siempre trabajamos con algoritmos, piense en un problema cualquiera, “se nos pinchó un neumático y tenemos que reemplazarlo”, “necesitamos prepara un plato de comida”, “tenemos que calcular las raíces de una ecuación de segundo grado”, si usted es capaz de describir paso a paso cómo solucionar el problema entonces habrá creado un algoritmo.

En las ciencias de la computación la definición de algoritmos es algo muy común e importante en la elaboración de programas, sin un algoritmo, no se puede escribir un programa. Por otro lado cuando hablamos de un algoritmo en programación estamos pensando en algo un poco más riguroso que un simple listado de tareas que hay que realizar, esto debido a que un algoritmo será ejecutado por un computador a través de un programa y a un computador no se le pueden dar órdenes como una persona se las da a otra.

Todo algoritmo debe cumplir ciertas reglas que garanticen una correcta solución a un problema, es por esto que los algoritmos deben tener las siguientes características:

Un algoritmo debe ser PRECISO, debe indicar claramente el orden de realización de cada paso.

Un algoritmo no debe mostrar AMBIGÜEDADES.

Un algoritmo debe ser DEFINIDO, es decir, si se sigue el algoritmo dos veces, se debe obtener el mismo resultado.

Un algoritmo debe ser FINITO. Si se sigue el algoritmo, se debe terminar en algún momento.

Como se indicó anteriormente, un algoritmo debe reflejar la solución de un problema, y esta debe ser la mejor que podamos hallar. En este sentido debe entenderse que un algoritmo no es un programa de computador, es un paso previo para llegar a ese fin, por lo tanto un algoritmo no debe escribirse empleando terminologías propias de un lenguaje de programación, ya que los lenguajes de programación, como veremos más adelante, traen muchas limitantes. Escribir un algoritmo basándose en un lenguaje de programación haría que la solución se dé en función de las limitantes del lenguaje y no en la mejor solución. Una vez que se establezca el algoritmo de solución a un problema, se debe buscar un lenguaje de programación que pueda adaptarse a él, y no lo contrario.

Por esta razón existen diferentes formas en las que podemos expresar un algoritmo, de modo que cualquier programador pueda entenderlo y pueda expresarlo en el lenguaje de programación que desee o que sea más adecuado. Entre las diferentes formas que podemos expresar un algoritmo, podemos mencionar las más comunes:

Lenguaje Natural.- Se refiere al mismo lenguaje empleado por las personas para comunicarse. Si bien es cierto que utilizar este lenguaje para describir un algoritmo es muy práctico, se debe tener mucho cuidado al hacerlo ya que se pueden introducir ambigüedades que harían deficiente el algoritmo. Frases como “Te vi con el telescopio” pueden parecer, en un principio, correctas pero si se pone a pensar, ¿quién tiene el telescopio? verá que no es posible asegurar quién lo tiene.

Page 28: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

28

Expresar un algoritmo empleando un lenguaje natural podría ser más o menos como sigue:

Para calcular el área de un triángulo, debemos: - Determinar cuál es valor de la base y altura del triángulo que queremos calcular. - Comprobamos que estos valores son positivos, de ser así calculamos el área empleando la

siguiente fórmula 2

alturabaseárea

×= , de comprobarse que uno de los dos no es positivo no se

deberá calcular el área.

Pseudocódigo.-

Es un intermedio entre el lenguaje natural y un lenguaje de programación. Se emplean una serie de palabras claves como “leer”, “imprimir”, “si… entonces… de lo contrario…”, “inicio”, “fin”, etc., esto con la finalidad de eliminar las ambigüedades que puede presentar el lenguaje natural y de no extender innecesariamente el algoritmo, pero sin llegar a la rigidez de un lenguaje de programación. Un ejemplo de pseudocódigo se presenta a continuación:

Inicio “Cálculo del área de un triángulo” Lee base y altura Si base y altura son positivas, entonces

Calcular área del triangulo = (base x altura) ÷ 2 Imprimir el área del triángulo

De lo contrario Imprimir “Error, los datos deben ser positivos”

Fin

Diagramas de flujo.-

Permite mostrar un algoritmo en forma gráfica. Para realizar esto, se cuenta con: una serie de símbolos o figuras que reemplazan a las palabras claves del pseudocódigo, flechas que indican la dirección del flujo del algoritmo y conectores.

Algunos de estos símbolos son:

Indica: leer,

Indica: imprimir,

Indica: proceso o cálculo,

Indica: si… entonces… de lo contrario…,

Indica: inicio o fin, etc.

Entonces, el algoritmo que calcula el área de un triángulo se puede presentar mediante un diagrama de flujo de la siguiente manera:

Page 29: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

29

Lenguajes de programación Un lenguaje de programación es un conjunto de vocablos o símbolos que permiten expresar un algoritmo en el computador.

Como hemos podido apreciar en el capítulo anterior el computador es una máquina que por medio de elementos electromagnéticos almacena y procesa información. En sus inicios, la manera de expresar un algoritmo en el computador era la de proporcionarle códigos binarios directamente, el lector se podrá imaginar lo complicado que sería esta tarea, en otras palabras el lenguaje que se empleaba era el de la propia máquina.

La necesidad de hacer esta tarea más sencilla hizo que a alguien se le ocurriera la idea de escribir un programa que pudiera traducir, al lenguaje que entiende la máquina, otros códigos que se acerquen más al lenguaje empleado por las personas. El conjunto de códigos empleados y las reglas en que estos códigos se relacionan dan lugar a los lenguajes de programación.

Clasificación de los lenguajes de programación

En la actualidad existen, y han existido, innumerables lenguajes de programación, por eso, para poder entenderlos mejor debemos agruparlos en función a características comunes que puedan tener entre ellos. Existen varias maneras de clasificarlos, a continuación veremos algunas de ellas.

En función de cuán cercano esté el lenguaje a la forma cómo el computador procesa la información o cuán alejado esté de ella y cercano esté al lenguaje humano, los lenguajes de programación se clasifican en:

Lenguajes máquina: es el lenguaje empleado por el computador, el lenguaje está basado en códigos binarios. Si es cierto que esté lenguaje no se emplea en la actualidad para

Cálculo del área de un triángulo

Base, altura

Base y altura son

ERROR: los datos deben ser positivos

área

Fin

si no

Page 30: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

30

desarrollar programas, cualquier lenguaje de programación que empleemos deberá ser traducido al lenguaje máquina para poder ser ejecutado por el computador. Como veremos más adelante estos traductores reciben los nombres de compiladores o intérpretes.

Lenguajes ensambladores: (Assembler): es un lenguaje que trata de reemplazar los códigos binarios por una palabra mnemotécnica que sea más fácil de recordar. Una instrucción en lenguaje máquina podría ser como se muestra a continuación:

0100 0110 0011 1010 0001 0100

Está instrucción podría significar “mover el valor de 20 a la posición de memoria 3Ah".

En lenguaje ensamblador la misma instrucción sería escrita de la siguiente manera:

mov 3Ah, 20

Como se puede apreciar el código binario 0100 0110 ha sido reemplazado por la palabra “mov”, el código 0011 1010 por el valor hexadecimal 3A (la h le indica al compilador que es un valor en base 16) y finalmente el código 0010 0100 por el número 20.

Indudablemente, si se quiere recordar cómo se debe hacer para colocar un valor en una posición de memoria, el emplear un lenguaje ensamblador es más fácil que emplear el lenguaje máquina. Si es cierto que el esfuerzo al crear el lenguaje ensamblador trajo muchos beneficios para programar computadores, esto no fue suficiente debido a que se sigue escribiendo programas en función de la forma que el computador trabaja, cuando se trata de programas grandes, recordar que la base del triángulo que necesitamos para calcular el área de éste se encuentra en la posición de memoria 3Ah y que la altura está en la posición F4h, y que luego de calcular el área, ésta debe ser almacenad en la posición 9Bh para que la podamos utilizar más tarde, puede darnos más de un dolor de cabeza.

Lenguajes de alto nivel: estos lenguajes tienen como característica principal que los códigos que emplea se acercan más al lenguaje que emplean los seres humanos para comunicarse. Para poder realizar esta tarea, los traductores realizan operaciones mucho más complejas que en los lenguajes ensambladores en donde sólo se limitan a reemplazar el código por un valor binario. En los compiladores de lenguajes de alto nivel un código puede ser remplazado por muchas instrucciones del lenguaje máquina.

Una de las novedades que trajo estos lenguajes fue el empleo de “identificadores”, la posibilidad de dar un nombre a las posiciones de memoria de modo que las podamos identificar por medio de ellos. Así por ejemplo si queremos almacenar el valor de la base de un triángulo para luego poder calcular su área, podemos emplear los identificadores “base”, “baseTri”, “baseDelTriangulo” o el que mejor nos parezca. La orden para asignar un valor al identificador podría ser como: “baseTri = 20”.

Como se puede imaginar, la aparición de los lenguajes de alto nivel hizo que la programación de computadoras fuera más accesible, los programas empezaron a ser más ambiciosos y más extensos lo que obligó a mejorarlos cada vez más hasta lo que son en la actualidad.

Page 31: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

31

Otra manera de clasificar los lenguajes de programación es por la metodología que se emplea para diseñar y construir un programa (paradigma de programación). En este sentido podemos clasificar los lenguajes de la siguiente manera:

Lenguajes imperativos o procedurales: este tipo de lenguajes de programación están muy influenciados por la forma en que el computador trabaja, los programas que en ellos se pueden implementar se basan en la idea de instrucciones que se tienen que llevar a cabo una tras otra como una receta. Estos lenguajes se basan en la definición de variables para almacenar valores, de instrucciones que permiten asignar valores a las variables y del uso de instrucciones iterativas. Ejemplos de leguajes imperativos tenemos al Assembler, Fortran, Cobol, Pascal, C, Modula2, etc.

Lenguajes lógicos o declarativos: Su diseño está muy poco influido por la forma particular que trabaja el computador. En estos programas se emplean elementos basados en la lógica de predicados. Entre los lenguajes de programación más conocidos esta el Prolog.

La programación lógica trata con relaciones entre objetos. Las relaciones se especifican con hechos y con reglas. Un hecho es una frase como “Pedro es padre de Juan”, “Pedro es padre de Carlos”, “Alfredo es padre de Pedro” (en Prolog, estos hechos se escribirían como: padre(Pedro, Juan), padre(Pedro, Carlos), padre(Alfredo, Pedro).

Las reglas se emplean para expresar frases como “Una persona X es abuelo de otra Y si X es padre de una persona Z y Z es padre de Y” (en Prolog se escribiría como: abuelo(X, Y) :- padre(X, Z), padre(Z, Y)) o “Dos personas son hermanos si son diferentes personas y ambos tienen el mismo padre” (en Prolog, hermanos(X, Y):- X<>Y, padre(Z, X), padre(Z, Y)).

La ejecución de programas lógicos consiste en la demostración de hechos sobre las relaciones por medio de preguntas, por ejemplo “¿Es Alfredo padre de Carlos?”, “¿Es Pedro padre de Carlos?”, “¿De quién es abuelo Alfredo?” (en Prolog, ?- padre(Alfredo, Carlos) cuya respuesta es falso, ?- padre(Pedro, Carlos) que se obtiene como resultado verdadero, ?- abuelo(Alfredo, X) cuya respuesta es Juan, Carlos.)

Lenguajes funcionales: estos lenguajes están diseñados bajo el concepto matemático de “función”. Una función es un elemento que recibe una serie de datos y devuelve un resultado, la esencia de la programación funcional es combinar funciones para producir otras funciones más potentes. Un ejemplo de este tipo de lenguajes es el Lisp. Una función en Lisp está compuesta por una serie de datos encerrados entre paréntesis, el primero de los cuales corresponde al nombre de la función, por ejemplo “(> 3 4)” es la función mayor que evalúa el valor 3 y 4, la función devuelve verdadero o falso (en este caso falso) otro ejemplo sería (if A B C) es la función “if”, devuelve B si A es verdadero o C en caso contrario. Estas dos funciones se pueden juntar para formar otra más compleja, por ejemplo (defun mayor (n, m) (if (> n m) n m) ) ), define una nueva función denominada mayor que devuelve el valor mayor entre dos números.

Lenguajes orientados a objetos: antes que apareciera este estilo de programación los lenguajes tenían la particularidad de separar, en un programa, los datos de las funcionalidades, por un lado se colocaban las definiciones de datos y por otra la funciones y procedimientos que permiten modificar el estado o el valor de los datos. Por

Page 32: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

32

ejemplo si se quería escribir un programa que almacene un texto o cadena de caracteres y luego se desea borrar parte de él, se tenía que definir una variable del tipo de datos correspondiente y luego implementar una función o procedimiento que permita hacer la tarea o emplear una ya implementada en el entorno de desarrollo, para poder realizar esta acción se tiene que pasar como parámetro la variable que contiene al texto y otra información que se requiera. En lenguaje Pascal las instrucciones para realizar esta tarea serían como sigue:

var texto: string; begin texto:=’Hola amigos’; delete(texto,2, 6); borrar de la variable texto 6 caracteres a partir

de la posición 2

… el texto resultante queda en ‘Hogos’ Este estilo de programación se empezó a cuestionar mucho a fines de los años 80, la críticas se basaban en que, en la naturaleza, los objetos no están separados, las partes de las funciones que realizan. Imagine un automóvil, no se puede pensar en un auto viendo por un lado un conjunto suelto de ruedan, piezas de motor asientos, etc. y por otro lado pensar en él como una máquina que nos puede desplazar, que rinde un determinado número de kilómetros por galón de gasolina, etc., cuando se piensa en un automóvil se ve como una unidad los elementos que lo conforman y las funciones que puede realizar.

En este sentido la programación orientada a objetos parte de la premisa que los datos y sus funcionalidades deben estar juntos en un elemento que se le denomina “objeto”. Un programa orientado a objetos puede presentar más o menos como a continuación se muestra:

string texto(‘Hola amigos’); Se define la variable texto como una cadena de caracteres, asignándole el texto correspondiente

texto.delete(2, 6); borrar de la variable texto 6 caracteres a partir de la posición 2

Además de estas características, los lenguajes orientados a objetos introducen conceptos como “herencia” y “polimorfismo” que permiten aumentar la versatilidad de los lenguajes de programación.

Finalmente existe otra forma de clasificar los lenguajes de programación es agrupándolos por generaciones. Esta clasificación se realiza de la siguiente manera:

La primera generación: la constituyen los lenguajes que aparecieron en los inicios del computador, estamos hablando del lenguaje máquina y del lenguaje ensamblador

La segunda generación: se forma con la aparición de los primeros lenguajes de alto nivel a fines de los años 50. Estos lenguajes fueron el Fortran (que deriva del inglés; “Formula Translator” o traductor de fórmulas) y el Cobol (“Common Business Oriented Language”), el primero fue creado con fines científicos, era capaz de calcular muy rápidamente fórmulas complejas para la época, el segundo se hizo con fines más comerciales, se trataba de un lenguaje de programación que haciendo operaciones muy sencillas permitía manejar grandes cantidades de datos.

Page 33: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

33

La tercera generación: a mediados de los años 60 se presenta una crisis en el software producto de la necesidad de crear programas más ambiciosos, en esa época la programación era muy desordenada, era muy difícil dar manteniendo a los programas por lo enredado de los programas. Es entonces que aparece la programación estructurada, concepto que se estudiará en los capítulos siguientes y que dio una orientación distinta a la forma de programas. Es por esta época que aparecen lenguajes de programación como el Algol68, el Pascal, el Modula, el C, etc. Estos lenguajes son en principio imperativos pero aplicaban los lineamientos de la programación estructurada.

Lenguajes de cuarta generación: los lenguajes de esta generación se orientaron a la gestión y al tratamiento de grandes bases de datos. Son lenguajes denominados “de consulta”, esto quiere decir que con instrucciones sencillas se puede clasificar, buscar información y dar formato a la información contenida en archivos. entre estos lenguajes encontramos al Mantis, Ideal, QBE, SQL,etc.

Lenguajes de quinta generación: esta generación de lenguajes se relaciona al campo de la inteligencia artificial, los sistemas basados en el conocimiento, sistemas expertos, etc. los lenguajes que a parecen en esta generación son Lisp y el Prolog.

Page 34: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

34

CAPÍTULO 3: Ciclo de vida de un proceso de desarrollo de software

Pasos involucrados en la solución de problemas en computación “PIENSE PRIMERO, CODIFIQUE DESPUÉS”

Henry Ledgard, en su libro Programming Proverbs (Hayden Book Co. Inc., 1975), enunció un interesante corolario a la Ley de Murphy al que denominó la Ley de Murphy de la programación. Enunciada en términos sencillos, la ley establece: “Entre más pronto comience a codificar un programa, mayor será el tiempo que le llevará”. Aunque la veracidad de esta máxima no ha sido to-davía probada formalmente, la experiencia personal ha más que demostrado su validez. El tratar de escribir, aun el más simple de los programas, sin un esquema de solución bien definido es como construir una casa con un martillo clavos y madera, pero sin un plano. El caos se presentará casi de inmediato. Piense primero, piense después, piense un poco más y sólo entonces comience a escribir su programa. Tomado de: Introducción a la programación y a la solución de problemas con Pascal. De G.M. Scneider, S.W. Weingart, D. M. Perlman

Crear un programa en el computador no es una tarea sencilla, sin embargo si pretendemos solucionar un problema en computación sin mantener un orden y sin respetar ciertas recomendaciones, esta tarea se torna aun más difícil, y nos llevará sólo a frustraciones y fracasos.

A continuación se presentan una serie de pasos que pueden ayudar en esta tarea.

1° Definición del problema

Esta etapa implica entender el problema que se nos presenta. Se debe tener muy claro cuál es el resultado que se espera y con qué herramientas o datos contamos. Esto parece muy trivial pero entienda que no se puede solucionar un problema si no se sabe o entiende exactamente lo que se nos pide.

Entienda que es muy diferente que se nos pida “buscar el teléfono de una persona en una guía telefónica impresa” que “buscar a quién pertenece un número telefónico en una guía telefónica impresa” y aun más diferente “buscar a quién pertenece un número telefónico en una guía telefónica digital”. El entender uno por otro, nos llevaría a una solución incorrecta o a la realización de un proceso altamente ineficiente.

2° Análisis del problema

En esta etapa se hace un bosquejo de la solución del problema, aquí debe quedar claro que por simple que sea el problema, éste puede descomponerse en partes. La labor que debemos hacer, por lo tanto en esta etapa es la de identificar en forma general y no en forma específica, cuáles son las tareas que se deben realizar para solucionar un problema.

Si nos plantearan el siguiente problema: “Emitir un reporte en el que aparezcan los alumnos de un curso de acuerdo con el promedio obtenido, de mayor a menos nota”, quizá la primera sensación que tengamos al leer el problema y saber que lo tenemos que resolverlo se parezca mucho a la figura siguiente:

Page 35: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

35

Al parecer el problema es cómo esta roca, tan complejo o grande que quizá no lo podamos solucionar o detener, y nos pasará por encima aplastándonos.

Sin embargo si luego de un análisis comprendemos que el problema es un conjunto de tareas, la situación puede cambiar, esto debido a que cada tarea puede ser considerada como un problema independiente y por lo tanto estamos ahora ya no ante un problema muy grande , sino ante varios problemas más sencillos.

Si la misma técnica se aplicase a cada tarea se podría descomponer el problema en partes mucho más pequeñas o simples que finalmente nos permita resolver el problema inicial.

3° Diseño de las estructuras de datos y del algoritmo

Problema

1 2

3

4

5

Tareas: 1- Obtener los datos 2- Separar a los alumnos que llevan el curso 3- Obtener los promedios de cada alumno 4- Ordenar a los alumnos por promedio 5- Imprimir los datos ordenados

1

2 3

4 5

Page 36: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

36

En esta etapa se definen los tipos de datos que se emplearán para solucionar las tareas del problema, así como también se debe describir paso a paso la solución de cada una de las tareas.

La mala elección del tipo de datos que emplearemos puede llevarnos a una solución ineficiente de la tarea que queremos solucionar. Tomemos el siguiente ejemplo: “Se tienen que asignar a los diferentes cursos las aulas en las que se dictarán las clases, para lo cual se cuenta con una listas de cursos con su respectivo número de alumnos matriculados, por otro lado se cuenta con las características del edifico de aulas, esto es la cantidad de pisos del edificio, la cantidad de aulas en cada piso y la capacidad de cada aula”, la siguiente figura ilustra el problema:

Veamos a continuación cómo una mala elección en las estructuras de datos nos lleva a un mal algoritmo de solución:

La lista de curso se puede almacenar en dos arreglos unidimensionales, uno que contiene el curso y otro la cantidad de alumnos matriculados. Con esta estructura de datos podríamos ordenar los cursos por la cantidad de alumnos, empezando por el mayor, de esa forma damos prioridad al curso que tenga más alumnos.

El cuanto a los datos del edificio, la forma del mismo nos pude llevar a decidir emplear un arreglo de dos dimensiones, en donde las filas del mismo representan los pisos del edificio y las columnas el número del aula, de modo que por ejemplo la celda que se encuentra en la fila 2, columna 3 representa el aula 203; las celdas contienen la capacidad del aula. Luego como en un arreglo de dos dimensiones, la cantidad de columnas debe ser igual en todas las filas, pero el edifico no necesariamente tiene la misma cantidad de aulas por piso, se debe definir un criterio para que cuando se analicen las aulas no se tome un aula inexistente. Esto se puede hacer de varias formas, se podría colocar un cero en las celdas correspondientes a las aulas inexistentes, también se podría definir un arreglo auxiliar unidimensional en el que en el primer elemento se coloque la cantidad de aulas del piso 1, en la segunda la cantidad de aulas del piso 2 y así sucesivamente.

Bajo esas condiciones, la labor de asignar un aula a un curso se centra en tomar el primer curso y empezar a buscar el en arreglo bidimensional un aula que pueda contener el número de alumnos matriculados, así se toma el elemento de la fila 1, columna 1 y se compara con el número de alumnos matriculados, si no lo puede contener se analiza el electo de la fila 1 columna 2, si no se le puede asignar se

Lista de cursos

INF144-H431 58 INF144-H341 45 MAT149-H401 79 MAT149_H402 123 MAT149-H103 89 …

Edificio

Piso 1

Piso 2

Piso 3

Piso …

Aula 101 Cap.:40

Aula 102 Cap.:50

Aula … Cap.:…

Page 37: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

37

sigue con la celda siguiente, si encontramos un aula con capacidad 0 ó si el número de columna es mayor que el valor de la primera celda del arreglo auxiliar, seguimos buscando en la fila 2, y así sucesivamente hasta encontrar un aula que soporte al curso o hasta que se terminen las aulas. Si se da lo segundo se tendrá que enviar un mensaje indicando que al curso no se le puede asignar un aula. Si se encuentra un aula para el curso el mensaje debe indicar el aula asignada, además se debe realizar una acción que impida que el aula se vuelva a asignar a otro curso. Esto se puede hacer colocando un valor de control en la celda del aula, no debe ser cero porque ese valor indica que ya no ya más aulas en la fila, quizá un valor negativo.

Esta solución puede parecer lógica y acertada, sin embargo póngase a pensar que si se tiene un curso cuyo número de alumnos sobrepasa la máxima capacidad de las aulas que se tienen disponibles en ese momento, no se podrá enviar el mensaje correspondiente hasta no haber recorrido todo el arreglo bidimensional. Por otro lado cuando se busque un aula se debe hacer consultando celda por celda, inclusive se debe consultar en aquellas aulas ya asignadas, porque no hay forma de saber si ya se asigno o no sin consultarla.

Esta forma de resolver el problema es muy ineficiente porque la estructura de datos elegida nos está llevando a lo que se denomina una “búsqueda secuencial”. Esta solución es equivalente a que si queremos buscar al Sr. Juan Pérez en la guía telefónica empecemos a leer uno por uno los nombres que se encuentran en la guía comenzando de la primera página.

Cambiemos la estructura de datos empleada, en este caso no nos guiaremos de la forma presentada en la figura. Se mantendrán los arreglos unidimensionales que contiene a los cursos, pero en cuanto a las aulas, esta vez se elegirán dos arreglos unidimensionales; en un arreglo se colocarán los nombres de las aulas, por ejemplo en la celda 1 se colocará “101”, en la celda 2 se colocará “102”, y así sucesivamente. En el segunda arreglo la capacidad del aula, por ejemplo en la celda 1 se colocará 40, en la celda 2 se colocará 50, etc., teniendo en cuenta que el aula 101 tiene una capacidad de 40 alumnos, el aula 102 una capacidad de 50, etc.

Bajo estas condiciones, para solucionar el problema, se ordenan los arreglos de cursos de mayor a menor por cantidad de alumnos matriculados, luego se ordenan los arreglos de aulas de mayor a menor capacidad. Luego se toma el primer curso y la primera aula, si a capacidad del aula es menor que la cantidad de alumnos del curso se envía un mensaje indicando que al curso no se le puede asignar un aula y no se analiza el resto de aulas, esto porque a estar ordenado el primer elemento del arreglo de aulas contiene el aula de mayor capacidad, si la capacidad del aula puede contener al curso se envía el mensaje correspondiente. Luego se toma el siguiente curso y se hace la misma operación.

Como puede apreciar ambos arreglos se recorrerán una sola vez.

Este es un ejemplo muy claro que muestra lo importante de las estructuras de datos en la solución de problemas.

4° Transformación del algoritmo en un programa (Codificación)

Page 38: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

38

Esta etapa debe empezarse definiendo el lenguaje de programación que emplearemos para codificar el programa. No se puede pensar que todos los problemas se deben o pueden resolver empleando el mismo lenguaje de programación ya que existen innumerables lenguajes, unos más eficientes en la solución de cierto tipo de problemas que otros. El no seguir esta regla implicará que la solución que demos estará influenciada por las limitaciones del lenguaje en lugar de la eficiencia en la solución, podemos llegar a situaciones como las descritas en el problema de las aulas.

Esta etapa, para realizarse correctamente, implica el conocimiento y el dominio de diversos lenguajes de programación.

Una vez determinado el lenguaje de programación la tarea de codificación se torna en una tarea muy sencilla de realizar, tenga en cuenta que se domina el lenguaje elegido y por otra parte la solución del problema ya está hecha en las etapas previas.

5° Depuración, prueba y validación

Una vez escrito el programa, éste debe ser traducido al lenguaje máquina empleando un compilador, el compilador verificará que el código escrito en el paso anterior está correcto, si se detecta algún error se debe corregir, el compilador no traducirá el programa si existe algún error, a este tipo de error se denomina “Error de sintaxis”.

Una vez que el programa esté traducido, se debe probar, esta tarea implica la ejecución del programa, el suministro de datos al programa y la verificación que los resultados sean los esperados. Si resultará que el programa no da resultados correctos, en este caso se denomina “Error lógico”, se debe verificar el código, entiéndase que la orden “Suma = A – B” puede ser sintácticamente correcta pero lógicamente incorrecta ya que queremos sumar A y B, y no restarlos.

Si luego de revisarlo los resultados incorrectos persisten, se debe revisar el algoritmo empleado.

La validación del programa también puede implicar medir su eficiencia, muchas veces no es suficiente que un programa nos de los resultados correctos, sino que debe hacerlo en un tiempo razonable. Si tenemos un programa al que le proporcionamos el nombre de una persona y éste nos da su número telefónico, estará bien, pero si la respuesta nos la da luego de 10 minutos, mejor lo buscamos nosotros mismos en la guía telefónica en vez de emplear el programa. El tiempo de respuesta de un programa es un factor que se debe tomar en cuenta en la mayoría de soluciones planteadas.

6° Documentación

Una vez concluido, el programa debe ser entregado al usuario que lo va a manejar. Esta persona debe ser entrenada para que pueda utilizar el programa sin problemas, para esto se debe preparar un documento denominado “Manual de usuario” en el que se explique cómo poner en marcha el programa así como la manera de acceder a todas las opciones que permite el programa.

Page 39: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

39

Por otro lado se debe pensar que en algún momento se puede desear modificar o actualizar el programa, por lo que se va a requerir las especificaciones que se emplearon para resolver el problema. Es por esta razón que se debe elaborar otro documento denominado “Manual técnico” donde queden escritos estos detalles.

Traductores de lenguajes de programación:

Compiladores e Intérpretes Para escribir un programa empleando un lenguaje de programación se requiere en primer lugar un procesador de palabras, un programa que nos permita escribir un texto y almacenarlo en el computador. Sin embargo, este texto no puede ser ejecutado en el computador debido a que para él sólo son códigos (letras y símbolos) almacenados. Para que este texto pueda ser ejecutado por el computador, las órdenes escritas en este texto deben ser traducidas al lenguaje que entiende el computador, el “lenguaje máquina”.

Para poder traducir este texto se requiere de de otro programa que pueda realizar esta tarea, estos traductores reciben los nombres de “compiladores” o “intérpretes”. Cada lenguaje de programación que empleemos debe tener su propio traductor.

En el mercado existen muchos productos que reúnen en un mismo entorno procesadores de palabras, traductores y otros programas que permiten depurar los programas, esto se hace con la finalidad de facilitar la labor del programador. Estos productos reciben la denominación de IDE (Integrated Development Environment) o “Entorno de Desarrollo integrado”. Un ejemplo de estos productos son el Borland™ Pascal, el Visual Studio™, etc.

Compiladores

Un compilador, como ya hemos indicado es un programa cuya tarea es la de traducir un texto escrito según las reglas de un lenguaje de programación al lenguaje máquina, para esto toma como dato el archivo con el texto, denominado “Programa fuente” y crea otro archivo con los códigos en lenguaje máquina denominado “programa objeto”. El compilador concluye su tarea cuando todo el texto haya sido traducido, esto quiere decir que si en el proceso de traducción encontrara un error de sintaxis el compilador se detiene, envía un mensaje indicando el error y no crea el programa objeto.

Intérpretes

Un intérprete también es un traductor, sin embargo la forma en que realiza su tarea es diferente a la del compilador. El intérprete no crea un archivo como el programa objeto, lo que hace es tomar del archivo fuente una a una las órdenes las traduce e inmediatamente las ejecuta. Esto permite apreciar la ejecución de un programa aun si tuviera errores de sintaxis, mientras el intérprete no llegue a la orden con el error, el programa continúa su ejecución. Esto puede ser una ventaja, sin embargo se requerirá del intérprete cada vez que se desee ejecutar el programa; un programa que ha sido compilado no requiera del compilador para ser ejecutado ya que el programa objeto se

Page 40: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

40

encuentra escrito en lenguaje máquina y por lo tanto el computador lo entiende completamente.

Fases de la compilación

Para conseguir un programa que se ejecute directamente por el computador se requiere, como hemos dicho, un compilador. Se pueden definir dos etapas en el proceso de compilación, la compilación propiamente dicha y el enlace (link).

La fase de compilación propiamente dicha es la etapa de traducción, el texto del programa fuente se traduce al lenguaje máquina, es aquí donde se detectan los errores de sintaxis. Al finalizar esta etapa el programa no puede ejecutarse aun, esto se debe a que, entre otras cosas, las órdenes muchas veces emplean elementos que no pueden ser ejecutadas directamente, por ejemplo si se pretende calcular el seno de un valor x y asignarlo a una variable A, la orden dice:

A = sin(x);

La traducción de esta línea lleva a indicar que se debe calcular el seno de x, sin embargo no indica cómo se debe calcular el seno, y se debe entender que la computadora no tiene porqué saberlo. Las instrucciones para calcular el seno se le deben proporcionar al computador, sin embargo esas órdenes no se escriben el programa fuente, imagínese lo complicado y laborioso que sería si tuviéramos que hacerlo.

Es en la fase del enlace donde se incorporarán las órdenes necesarias para completar el programa objeto. Resulta que los entornos de desarrollo incluyen una lista de archivos donde se encuentran precisamente las órdenes para poder calcular por ejemplo el seno de un ángulo. Estos archivos por lo general ya están en lenguaje máquina por lo que no requieren una traducción, simplemente que se los incorpore al programa objeto.

Esta etapa de la compilación permite también que podamos incorporar al programa otros archivos que hayan sido creados por el mismo programador o por otros programadores. Resulta que, como vimos en la etapa de análisis, un problema debe dividirse en partes más sencilla, cada parte puede ser analizada por diferentes personas que forman parte de un equipo, cada persona puede elaborar la solución y el código de cada parte, luego aplicar la fase de compilación propiamente dicha a cada uno de sus módulos y cuando estos estén traducidos enlazarlos en un único programa fuente. Esto implica que un problema puede ser atacado en paralelo por diferentes personas obteniéndose una respuesta más rápida a la solución del problema, incluso como lo que se enlaza son códigos en lenguaje máquina, cada módulo pudo haber sido elaborado en diferentes lenguajes de programación, el que más se adapte a la solución planteada en el respectivo módulo.

Page 41: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

41

CAPÍTULO 4: Estructura general de un programa

Definición de programa Como se dijo en capítulos anteriores, un lenguaje de programación es un conjunto de vocablos o símbolos que permiten expresar un algoritmo en el computador.

Para poder empezar a desarrollar programas se debe conocer al detalle el o los lenguajes de programación que emplearemos para este fin. Cuanto más conozcamos de un lenguaje podremos conseguir mejores y más eficientes programas.

A continuación estudiaremos las partes más elementales de un lenguaje de programación

Concepto de Identificador Como se mencionó en capítulos anteriores, la memoria del computador está compuesta por una serie de celdas que sirven para almacenar información, cada una de estas celdas se identifica por un número al que se denomina “dirección de memoria”. Si cada vez que en un programa tuviéramos que guardar un dato, que luego vamos a utilizar para realizar alguna operación en el programa, o quisiéramos ejecutar una porción de código ubicado en alguna parte de la memoria, tuviéramos que hacerlo escribiendo la dirección de memoria; elaborar un programa sería algo muy complicado. Afortunadamente los lenguajes de programación de alto nivel proporcionan una herramienta que permite que esta tarea sea muy simple, esta herramienta se denomina Identificador.

Un identificador es un nombre que está relacionado con una dirección de memoria y sirve para denotar ciertos elementos de un programa. Estos elementos son: las variables, las constantes, las funciones y los procedimientos, elementos que se definirán más adelante.

Los identificadores son definidos por el programador, esto quiere decir que si éste requiere de una variable o una función, pues sólo tiene que pensar en un nombre para identificarla y declararla en el programa. Luego, si quiere hacer uso de esa variable o función, sólo tiene que hacerlo a través del nombre que le dio.

Un identificador no puede ser definido de cualquier manera, los lenguajes de programación tiene una serie de reglas de formación de identificadores. A continuación se describen algunas de ellas.

En el leguaje Pascal, los identificadores sólo pueden estar formados por las letras del alfabeto inglés (a, b, c,… x, y, z ó A, B, C,… X, Y, Z), los dígitos decimales (0, 1, 2,… 7, 8, 9) y el signo de subrayar, para formar un identificador sólo tenemos que definir una palabra combinando estos símbolos, sin embargo un identificador no puede empezar con un dígito. Las palabras que empleemos para definir un identificador no puede coincidir con una palabra reservada (ver tabla 4.1 – Palabras reservadas del Pascal). El compilador de Pascal no hace distinción entre mayúsculas y minúsculas, esto quiere decir que para él da lo mismo definir el identificador suma, que Suma o que SuMa, o SUMa, o SUMA, etc.

De acuerdo a esto, será considerada como correcta, para la formación de identificadores, la lista siguiente:

Page 42: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

42

suma Capacidad apellidoPat INTERES tasa_de_interes Factor398 _AX _678 _456_Base _myor_

La siguiente lista, rompe las reglas de formación, por lo que no son aceptadas por el compilador:

Año (la ñ no pertenece al alfabeto inglés) Interés (la é no pertenece al alfabeto inglés) δx (δ no es un símbolo aceptado) gastos+intereses (+ no es un símbolo aceptado) Apellido Paterno (no se aceptan identificadores con espacios) 456_Base (no se puede empezar con un dígito) REPEAT (repeat es una palabra reservada)

and asm array begin case const constructor destructor div do downto else end exports file for function goto if implementation in inherited inline interface label library mod nil not object of or packed procedure program record repeat set shl shr string then to type unit until uses var while with xor

Tabla 4.1 – Palabras reservadas del Pascal

En el lenguaje C y C++, las reglas son similares, con la única diferencia que aquí si se distinguen entre mayúsculas y minúsculas, esto quiere decir que para el compilador suma y SUMA serán dos identificadores diferentes y estarán relacionados a distintos elementos.

Partes constitutivas de un programa: 1° Encabezado: Es una parte opcional en todo programa, la finalidad del encabezado es

la de poder indicar, de una manera sencilla: el propósito del programa, el autor del programa, la fecha en que se creó, la fecha que se hizo la última modificación, etc., de modo que una persona que lea el programa tenga esa información en las primeras líneas del programa y no tenga que buscarla a través del texto del programa o por otros medios.

Por lo general es muy similar la forma que se coloca un encabezado en un programa, por ejemplo en el lenguaje C o C++, el encabezado se coloca a través de textos que son ignorados por el compilador, denominados comentarios. Un comentario en lenguaje C y C++ se coloca luego de los símbolos /* y se da por concluido con los símbolos */. Por ejemplo:

Page 43: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

43

/* Programa que permite calcular las raíces de una ecuación de segundo grado. Autor: Ana Cecilia Roncal Neyra Fecha de creación: 01/01/2003 Última actualización: 10/03/2006 */

En el lenguaje C++ también se pueden emplear lo que se denomina comentarios en línea, los cuales se delimitan por los símbolos // y el cambio de línea, de la siguiente manera:

// Programa que permite calcular las raíces de una ecuación de segundo grado. // Autor: Ana Cecilia Roncal Neyra // Fecha de creación: 01/01/2003 // Última actualización: 10/03/2006

En el lenguaje Pascal se combina una instrucción y comentarios, la instrucción empieza con la palabra reservada program seguida de una palabra que debe ceñirse a las reglas de formación de un identificador. Los comentarios se encierran ente los símbolos y . Por ejemplo:

program permiteCalcularLasRaicesDeUnaEcuacionDeSegundoGrado; Autor: Ana Cecilia Roncal Neyra Fecha de creación: 01/01/2003 Última actualización: 10/03/2006

2° Inclusión de bibliotecas de funciones y subprogramas: Los compiladores de los diferentes lenguajes de programación implementan de manera nativa, dentro de los elementos que conforman el lenguaje, una serie de palabras reservadas, funciones y subprogramas de uso común que pueden ser utilizados en cualquier parte del programa. Sin embargo, para mayor comodidad de los usuarios, con la finalidad que cada vez que escriban un programa no se tengan que empezarlo de cero, los diferentes compiladores definen mecanismos que permiten incorporar a los programas que estamos escribiendo, bibliotecas de de funciones y subprogramas, es decir archivos que contengan el código de funciones y subprogramas, de manera que podamos reutilizar el código.

El lenguaje C y C++ definen en su núcleo una cantidad muy reducida de palabras reservadas y todos los demás elementos se incorporan a través de bibliotecas de funciones, en algunos casos estas bibliotecas son proporcionadas por el entorno de desarrollo. Por ejemplo, si se desea realizar una operación de ingreso de datos, se debe incorporar la biblioteca de entrada y salida. El proceso de incorporación de una biblioteca en C ó C++ se realiza en dos pasos, el primero es incorporar la declaración de la funciones en el texto del programa que estamos escribiendo, esto se hace mediante una cláusula denominada include y el segundo paso es incorporar el código al programa en el proceso de compilación, este paso depende del entorno de desarrollo que estemos utilizando. A continuación se presenta un ejemplo en C:

Page 44: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

44

#include "winbgim.h" int main( )

initwindow(400,300); moveto(0,0); lineto(50,50); closegraph(); return 0;

Luego el código de la biblioteca se incorpora al programa ejecutable con operaciones desde la línea de comandos del sistema operativo, como la que se muestra a continuación:

bcc32 -edibujo.exe dibujo.cpp winbgim.lib

En el lenguaje Pascal, la inclusión de bibliotecas de funciones se hace a través de lo que denominan Unidades (UNIT). Una unidad es un archivo compuesto por una serie de funciones y procedimientos, que ya está compilado y que se incorpora al programa que estamos elaborando mediante la cláusula USES. Al igual que el lenguaje C y C++, los entornos de desarrollo creados para Pascal traen un conjunto de bibliotecas de funciones que pueden ser empleadas en el momento que se requieran, y también los usuarios pueden crear sus propias bibliotecas, sin embargo la elaboración de una unidad en Pascal es un poco más laborioso que cuando se quiere hacer lo mismo en lenguaje C y C++, ya que en Pascal se requiere seguir una serie de reglas de sintaxis definidas por el lenguaje y que son diferentes a la de un simple programa; en el lenguaje C y C++ es más simple porque no se requieren reglas especiales. A continuación se presenta un ejemplo:

program ejemploDeUnidades; uses CRT; begin

clrscr; writeln(‘Se borró la pantalla’);

end.

3° Definición de constantes: Una constante es un elemento de un programa definido por un identificador. En la dirección de memoria relacionada al identificador se ha colocado un valor cualquiera, este valor no podrá ser cambiado a lo largo del programa, de allí su nombre.

En Pascal las constantes se definen en una zona, esto quiere decir que todas las constantes definidas en un programa van agrupadas por lo general al inicio del programa, la forma como se realiza esto se muestra a continuación:

Aquí se incluyen las declaraciones de las funciones que se encuentran en la biblioteca de funciones para graficar denominada winbgim

Estos son elementos propios del núcleo de C

Esta son funciones implementadas en la biblioteca para graficar, denominada winbgim. Con estas funciones se crea y muestra una ventana gráfica de 400x300 píxeles, se mueve el cursor de dibujo a la posición 0,0, se dibuja una línea hasta la posición 50,50 y finalmente se cierra la ventana gráfica. No hay que escribir el código para realizar estas tareas, sólo invocar a las funciones.

Estos son elementos propios del núcleo de C

Orden para compilar

Nombre del programa ejecutable que se creará

Biblioteca de funciones para graficar

Archivo con el texto del programa anterior

Aquí se incluyen las funciones y procedimientos que se encuentran en la biblioteca de manejo de pantalla denominada CRT.tpu

Estos son elementos propios del núcleo de Pascal

Este es un procedimiento de la biblioteca de manejo de pantalla.

Page 45: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

45

program ejemploDeDeclatracionDeConstantes; const IMPUEST0 = 0.12; FACTOR = 3; MAXIMO = 200;

En el lenguaje C y C++, las constantes se individualmente, esto quiere decir que para cada una se debe la cláusula correspondiente:

En C: #define IMPUESTO 0.12 #define FACTOR 9 #define MAXIMIO 200 En C++2: const float IMPUESTO = 9.12; const float FACTOR = 9; const int MAXIMO = 200; Como regla convencional, para identificar fácilmente una constante de cualquier otro elemento del programa, el identificador empleado para definir una constante debe hacerse enteramente en mayúsculas.

4° Definición de tipos de datos: En el capítulo 1, se pudo apreciar cómo se representan los datos en la memoria del computador. Cuando se habla de tipos de datos en programación, se está refiriendo a cuáles son las formas de representación de datos que maneja un lenguaje de programación y cómo se clasifican éstos.

En el lenguaje Pascal, los datos se clasifican en dos tipos, los tipos escalares y los tipos estructurados. Cuando hablamos de tipos escalares, nos estamos refiriendo a datos individuales, esto quiere decir que si definimos un elemento de tipo escalar, estaremos haciendo referencia a un solo valor. Por otro lado un tipo estructurado es todo lo contrario, cuando definimos un elemento estructurado estamos hablando de un conjunto de valores. Los tipos escalares se clasifican en tipos estándar y en tipos definidos por el usuario, mientras que los datos estructurados se clasifican en cadenas de caracteres, arreglos, conjuntos, registros, archivos y punteros3.

Los tipos estándar se refieren a datos cuyos valores están enmarcados en rangos definidos por el propio lenguaje, mientras que en los definidos por el usuario es el programador el que define su rango de valores. Los tipos estándar de clasifican en ordinales y no ordinales, mientras que los definidos por el usuario se clasifican en subintervalos y enumerados4

Los tipos ordinales se refieren a valores que se encuentra en un rango de datos para los que se cumple la siguiente regla: “Todo valor del conjunto, excepto el primero y el último, poseen un único valor que los precede y un único valor que los sucede”. Dentro de esta definición podemos encontrar a los valores enteros, un valor como 253 tiene un único predecesor, el 252, y un único sucesor, el 254. Los valores de tipo caracter también son ordinales, si el caracter ‘Q’ pertenece al código ASCII, tiene un único predecesor, el caracter ‘P’ y un único sucesor, el caracter ‘R’.

2 int y float son tipos de datos. Este concepto se analizará más adelante. 3 Los tipos estructurados se estudiarán detalladamente en los capítulos siguientes. 4 Los tipos definidos por el usuario serán analizados más adelante.

Todas se definen con una sola cláusula: const

Page 46: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

46

También podemos encontrar los tipos lógicos, al valor falso (false) lo sucede el valor verdadero (true), y a éste lo precede el valor falso.

Los tipos no ordinales son aquellos que no se puede establece su predecesor ni su sucesor, este es el caso de los valores reales o de punto flotante. Al valor real como por ejemplo 32.67 no se le puede definir un sucesor, ya que si decimos que lo sucede el 32.68 estaríamos en un error ya que también podría ser 32.657 ó 32.671 ó 32.6701 ó 32.6700001, etc.

La tabla 4.2 muestra un esquema de los tipos de datos definidos en Pascal.

El lenguaje Pascal define una variedad de tipos enteros y reales, los cuales se diferencian por la cantidad de bytes que empelan para su representación, lo que está directamente ligado, como ya hemos dicho, con el rango de valores que pueden manejar. Las tablas 4.3 y 4.4 nos muestran estos tipos de datos. Los valores de tipo caracter y lógico, se definen con los tipos Char y Boolean respectivamente, ambos se representan en un byte. Las cadenas de caracteres se definen mediante el tipo String y se almacenan en 256 bytes.

En el lenguaje C y C++ se definen tipos de datos similares.

Tipos de Datosen Pascal

Tipos estructuradosTipos escalares

Tipos definidos porEl usuario

Tipos estándar

Punteros

Archivos

Registros

Cadenas decaracteres

Arreglos

Objetos

Sub-intervalos

EnumeradosTipos noordinales

Tiposordinales

RealesEnteros

Caracteres

Lógicos

Tabla 4.2 Tipos de datos de Pascal

Tipo de dato Espacio de almacenamiento Rango de valores Byte 1 byte 0 .. 255 – valores sin signo

ShortInt 1 byte -127 .. 28 – valores con signo Word 2 bytes 0 .. 6,5535 – valores sin signo

Integer 4 bytes5 -2,147’484,648 .. 2,147’484,647 Valores con signo

LongInt 4 bytes -2,147’484,648 .. 2,147’484,647 Valores con signo

Tabla 4.3 – Tipos de datos enteros en Pascal

5 Dependiendo del entorno de desarrollo que se emplee, los enteros de tipo Integer pueden definirse con 2 o 4 bytes.

Page 47: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

47

Tipo de dato Espacio de almacenamiento Rango de valores Dígitos significativos Single 4 bytes ± 1.5x10-45 .. 3.4x1038 7 a 8 Real 6 bytes ± 2.9x10-39 .. 1.7x1038 11 a 12

Double 8 bytes ± 5.0x10-324 .. 1.7x10308

15 a 16

Extended 10 bytes ± 1.9x10-4932 .. 1.1x104932

19 a 20

Comp6 8 bytes -263 + 1 .. 263 - 1 19 a 20 Tabla 4.4 – Tipos de datos reales o de punto flotante en Pascal

Volviendo a la definición de tipos de datos, los lenguajes de programación permiten crear nuevos tipos de datos, a partir de los datos definidos en forma nativa por el lenguaje, este es el caso de de los datos estructurados. Una vez creados los nuevos tipos de datos, se pueden definir elementos de esos nuevos tipos.

En el lenguaje Pascal esta operación se realiza de una manera muy simple, el nombre que se emplee para el nuevo tipo de dato debe seguir las mismas reglas de formación que los identificadores y la sintaxis empleada se muestra continuación:

type TipoDeDatoEntero = Integer;

En este caso se define un nuevo tipo de dato llamado TipoDeDatoEntero, con las mismas características que los tipos de datos Integer. Más adelante se verán formas más complejas de definición de tipos de datos, cuando se vean los tipos de datos estructurados.

Finalmente, como en el caso de las constantes, para poder diferenciar un tipo de dato de otro elemento, convencionalmente la palabra que define el nuevo tipo de dato debe empezar con una letra mayúscula y si ésta está formada por varias palabras, cada palabra debe empezar con una letra mayúscula como se indica en el ejemplo.

5° Definición de subprogramas: En los capítulos anteriores se vio la conveniencia de dividir un problema en partes más pequeñas parar facilitar la solución de un problema. Los subprogramas son porciones de código que nos permiten dividir un programa en varias partes. Los subprogramas, de acuerdo a cómo realizan la tarea que se les encomienda, pueden dividirse en dos tipos, las funciones y los procedimientos.

Las funciones están enfocadas al cálculo o a la obtención de un valor determinado, por ejemplo si necesitamos obtener el área de un triángulo, lo más conveniente sería definir una función a la que le entreguemos la base y altura del triangulo y el código de la función calculará y entregará el área; si queremos determinar el mejor alumno de un curso, se puede elaborar una función que reciba la tabla con los nombres y notas del curso, la función buscará en la tabla el alumno que tiene la mayor nota y nos entregará su nombre.

Los procedimientos están orientados a realizar procesos. Por ejemplo es útil crear un procedimiento si se desea obtener un listado en el que aparezcan los alumnos de un curso que salieron desaprobados, o si por ejemplo se desea actualizar la información contenida en un archivo.

6 Los tipos de datos Comp manejan únicamente valores enteros de 8 bytes

Page 48: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

48

Normalmente los subprogramas se desarrollan luego de tener una idea clara de la solución del problema, este momento por lo general se da después de haber planteado el programa principal, es por eso que en esta zona del programa sólo se colocan las definiciones de los subprogramas, dejando la implementación de los mismos para después. En lenguajes de programación como C y C++ esto se da así, sin embargo en Pascal uno se ve obligado a colocar la implementación en esta zona, lo que no quita que se pueda implementar después de haber implementado el programa principal.

6° Definición de variables: Una variable es, al igual que las constantes, un elemento de un programa definido por un identificador. Sin embargo a diferencia de estas últimas, las variables pueden recibir valores en cualquier parte del programa, pudiéndose reemplazar o modificar su valor anteriormente asignado.

Una variable está relacionada directamente con un tipo de dato del lenguaje. En el lenguaje Pascal la definición de variables se realiza de acuerdo a la siguiente regla sintáctica:

Como regla convencional, también para diferenciarlas, los identificadores que se empleen como variables deben empezar con una letra minúscula.

A continuación se muestran ejemplos de definición de variables en Pascal.

program ejemploDeDeclatracionDeVariables; var base, altura, areaDelTriangulo : Real; opcionInicial : Char; finDelCiclo, hayError, seTrerminaronLosDatos : Boolean; contador, linea : Integer

7° Programa principal: Es la porción de código que se ejecuta primero en un programa. Aquí se realizan las tareas más generales del programa, haciendo llamados a los subprogramas que realizarán la tarea de solucionar el problema.

En C y C++ el programa principal se implementa mediante una función denominada “main”, en Pascal esto se indica mediante un bloque de instrucciones que empieza con la palabra reservada begin y termina con la palabra reservada end seguida de un punto.

A continuación se muestra un programa implementado en C y en Pascal, en donde se aprecian las partes de un programa, como se podrá apreciar no necesariamente se deben colocar todas ellas.

En C: /* Programa que calcula e imprime el volumen d eun cilindro.*/ #include <stdio.h> // biblioteca que permita la entrada y salida de datos #define PI 3.141592 float volumen(float, float); // definición de una función

:

,

; var Identificador Tipo de dato

Page 49: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

49

void main (void) // función principal float radioDeLaBase, altura, vol; // declaración de variables

scanf(“%f %f”, &radioDeLaBase, &altura); // lectura de datos vol = volumen(radioDeLaBase, altura); printf(“Volumen= %f\n”, vol); //impresión de resultados

float volumen(float radioDeLaBase, float altura) // implementacion de la función

return PI * radioDeLaBase * radioDeLaBase * altura;

En Pascal: program calculoDelVolumenDeUnCilindro; const PI = 3.141592; function volumen(radioDeLaBase, altura:Real): Real; begin

volumen := PI * radioDeLaBase * radioDeLaBase * altura; end; var radioDeLaBase, altura, vol: Real; begin

readln(radioDeLaBase, altura); vol := volumen(radioDeLaBase, altura); writeln(‘Volumen = ‘, vol:8:2);

end.

Asignación de valores y expresiones Una operación de asignación es una operación por medio de la cual se coloca información en una variable. En Pascal7 este proceso se realiza empleando la siguiente regla sintáctica:

Podemos definir una expresión como un conjunto de valores constantes, constantes, variables y funciones, unidos por algún operador, que se agrupan con la finalidad de obtener un resultado. El resultado que se obtenga al evaluar la expresión debe ser del mismo tipo de dato que la variable, si el resultado de la expresión da por ejemplo un valor real, éste no se le podrá asignar a una variable de tipo entero sin que se produzca, dependiendo del lenguaje de programación que se emplee, un error o que se asigne un valor incorrecto a la variable.

Un valor constante es la representación de información que no está contenida en una variable o constante y que se requiere en un instante dado. Esta representación depende del tipo de dato que queramos manejar. A continuación mostramos una tabla en la que se aprecian las diferentes maneras de representar valores constantes:

7 A partir de este momento los ejemplos estarán referidos al lenguaje Pascal únicamente.

:= ; variable expresión

Page 50: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

50

Enteros: Valores decimales: 37 5237 8 12345 Valores hexadecimales: $AB3 $F0B $C5A

Reales: Valores decimales: 3.1416 3427889.523 Notación científica: 3.8451e+14 8.345e-25 3.5E26

Caracteres: Con el caracter ASCII: ‘A’ ‘*’ ‘&’ Con el código ASCII #65 #74 #26 #10

Cadenas de caracteres: ‘Juan López’ ‘Hola amigos como están’ Lógicos: true false

Una operación de asignación por lo tanto se puede dar en los siguientes términos:

program asignacionDeValoresConstantes; var i1, i2: Integer;

r1, r2: Real; c1, c2: Char b1, b2: Boolean; cad: String;

begin i1 := 37; i2 := $F0B; r1 := 3.1416; r2 := 8.345e-25; c1 := ‘A’; c2 := #10; b1 := true; b2 := false; cad := ‘Juan López’;

end. Las constantes fueron definidas en la sección anterior, sin embargo aquí queremos resaltar la importancia del uso de constantes en una expresión, en lugar de emplear valores constantes. Si en un programa quisiéramos calcular el impuesto que tenemos que pagar por una venta realizada, podemos realizarlo de dos maneras diferentes:

1° impuesto := 0.18 * ventasRealizadas; ó 2° const IGV = 0.18;

begin … impuesto := IGV * ventasRealizadas;

La primera forma es más sencilla que la segunda y por eso muchas veces nos veremos tentados a emplearla. Sin embargo si es cierto que a la hora de programar es más fácil hacerlo de la primera forma, cuando tengamos que depurar o mantener el programa la primera forma nos dará muchos dolores de cabeza y nos hará perder mucho tiempo.

Imagínese que se optó por la primera forma y que la instrucción de es parte de un programa de contabilidad de una empresa. La cantidad de líneas que puede tener el programa pueden llegar a miles; imagínese que luego de haber escrito el programa y saber que se ejecuta correctamente, se determina que el impuesto ya no es 18% sino 17%. En ese momento se tendría que buscar la ubicación dónde se colocó la instrucción, si el cambio se hizo meses depuse de haber entregar el programa, esta tarea será muy engorrosa y nos tomará mucho tiempo. Si por otro lado el cambio se debe realizar en un tiempo muy corto y si el valor constante (0.18) aparece en otras partes del programa, la

Page 51: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

51

premura nos podría hacer equivocarnos y hacer que cambiemos un valor que no era el que debíamos cambiar; esto podría ocasionar pérdidas muy grandes en la empresa, incluso problemas legales.

Si por el contrario se optó por la segunda forma, el programador sólo tiene que explorar las primeras líneas del programa buscar la constante IGV cambiar su valor, volver a compilar el programa y tenerlo listo en unos pocos minutos, con la seguridad que no hayamos cometido errores. Es por esta razón que se recomienda el uso de constantes en un programa en lugar de los valores constantes. Los valores constantes deben emplearse sólo en caso de constantes universales (p. e.: π = 3.14.1592, g = 9.78, e = 2.7182, etc.) o valores que estemos seguros no cambiarán en el tiempo.

Las variables, que fueron definidas en la sección anterior, son elementos que deben ser tratados también con mucho cuidado en un programa. Como hemos visto, las variables son elementos definidos mediante un identificador, esto significa que tenemos mucha libertad para elegir el nombre que le daremos a una variable y esto es muy importante en un programa.

Cuando uno escribe un programa, debe pensar que el programa tendrá que ser depurado cuando éste se termine, y cada cierto tiempo se le deberá dar mantenimiento, por eso es muy importante que el programa sea muy fácil de entender, de lo contrario se perderá mucho tiempo en estas tareas. Lo primero que debemos tomar en cuenta es que las variables deben ser muy fácilmente identificadas de los otros elementos del un programa, es por eso que hemos dicho que las variables deben empezar con una letra minúscula, las constantes deben estar enteramente escritas en letras mayúsculas y los tipos de datos deben empezar con una letra mayúscula. La segunda regla, que debe ser la más importante es que el nombre que le demos a las variables, deben estar directamente relacionados con su contenido, el siguiente ejemplo refleja esta regla.

La siguiente es una operación de asignación en la que se entrega a una variable el resultado de una expresión:

a := ((b + c + d + e - f) * g / 3 + h * i + j * k)/(g + i + k);

La operación es, sintácticamente correcta, esto quiere decir que cuando compilemos el programa no nos va a dar errores. Por otro lado si hemos tenido el cuidado de asignar valores correctos a cada una de las variables, vamos a tener un resultado correcto y esperado. Sin embargo, alguien puede decir, sin dudar, qué es lo que se quieres calcular en esta instrucción. Usted se puede imaginar que después de un año de haber escrito esta instrucción en un programa, el creador del programa se podrá acordar qué es los que representa la variable f. Las respuestas a estas preguntas son indudablemente no.

Veamos ahora la misma instrucción escrita de otra manera:

promCurso := ((notaPr1 + notaPr2 + notaPr3 + notaPr4 - notaMinimaPr) * pesoPr / 3

+ notaEx1 * pesoEx1 + notaEx2 * pesoEx2)/( pesoPr + pespEx1 + pesoEx2);

¿Qué queremos calcular? Pues es obvio que el promedio de un curso.

¿Qué significa notaMinimaPr? Pues en muy probable que la nota mínima de prácticas.

Page 52: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

52

Es cierto que la segunda expresión es más larga y nos va a tomar más tiempo en escribirla que la primera, pero el tiempo que dediquemos a depurar y mantener el programa no tendrá punto de comparación.

Los operadores8 son elementos de enlace en una expresión y nos sirven para realizar operaciones, a continuación se presentan los distintos operadores que se encuentran en el Pascal.

1° Operadores aritméticos: trabajan con valores numéricos, la tabla 4.5 muestra estos operadores:

Operador Operación Tipos de datos permitidos Tipo de dato del resultado + Suma

enteros o reales entero + entero9 » entero

- Resta entero + real real + entero » real

* Multiplicación real + real / División real enteros o reales Real

div División entera sólo enteros Entero mod Módulo o resto sólo enteros Entero

Tabla 4.5 - Operadores aritméticos

El código mostrado a continuación nos muestra ejemplo del uso de estos operadores: program usoDeOperadoresAritmeticos; var a, b, c: Integer;

r, s, t: Real; begin

a := 7; b := 25; r := 3.56; s := 36.21; c := a + b; la variable c recibe el valor de 32 t := a * r; la variable t recibe el valor de 24.92 c := s - b; error: el resultado es 11.21, no puede asignarse a la variable c t := s / r; la variable t recibe el valor de 10.171348315 c := a / b; error el resultado es 0.28, no puede asignarse a la variable c c := b div a; la variable c recibe el valor de 3 c := b mod a; la variable c recibe el valor de 4

end.

2° Operadores de relación: trabajan con valores escalares. Como su nombre lo indica relacionan dos valores, el resultado es un valor lógico (true o false), la tabla 4.6 muestra estos operadores:

Operador Operación = igualdad <> desigualdad < menor que <= menor o igual > mayor que >= mayor o igual

Tabla 4.6 - Operadores de relación

8 Con la finalidad de estar en condiciones de elaborar expresiones rápidamente, se optó por tratar este tema antes que el de funciones. 9 El signo + puede reemplazarse por – ó *

Page 53: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

53

A continuación mostramos ejemplos del uso de estos operadores:

program usoDeOperadoresDeRelacion; var a, b: Integer; r, s: Real;

m, n: Char; cad1, cad2: String; l1, l2, l3: Boolean;

begin a := 7; b := 25; r := 3.56; s := 36.21; l1 := a = b; la variable l1 recibe el valor de false l2 : = r < s; la variable l2 recibe el valor de true l3 := r = 3.56; esta operación no debe realizarse en un programa, esto debido

a que por la forma como se codifican los valores reales podría asignar a l3 el valor de false

m := ‘A’; n := ‘F’; l1 := m < n; se comparan los códigos ASCII de ambas variables, se le

asigna a la variable l1 en valor de true m := ‘a’; n := ‘F’; l1 := m < n; el código ASCII del caracter ‘a’ es mayor que el de ‘F’, se le

asigna a la variable l1 en valor de false cad1 := ‘maria’; cad2 := ‘mario’; l1 := cad1 < cad2; se comparan los códigos ASCII del primer caracter de cada

cadena, si son iguales se comparan los dos segundos hasta encontrar un par diferente, la respuesta se da según la comparación de estos últimos caracteres. Se le asigna a la variable l1 en valor de true

l1 := true; l2 := false; l3 := l1 <> l2; true se almacena como 1 y false como 0, se le asigna a la

variable l3 en valor de true end.

3° Operadores de lógicos: sirven para enlazar expresiones lógicas, el resultado es un valor lógico (true o false), la tabla 4.7 muestra estos operadores:

Operador Operación not negación and conjunción or disyunción inclusiva xor disyunción exclusiva

Tabla 4.7 - Operadores lógicos

Estos operadores se comportan según la siguiente tabla de verdad:

expL1 expL2 not expL1 expL1 and expL2 expL1 or expL2 expL1 xor expL2 false false true false false false false true true false true true true false false false true true true true false true true false

Tabla 4.8 – Tabla de verdad de los operadores lógicos

A continuación se presentan algunos ejemplos:

Page 54: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

54

program usoDeOperadoresLogicos; var a, b: Integer; r, s: Real;

l1, l2, l3: Boolean; begin

a := 7; b := 25; r := 3.56; s := 36.21; l1 := a = b; la variable l1 recibe el valor de false l2 : = (r < s) and (a >= 0); la variable l2 recibe el valor de true l3 := not l1 xor (s > 10.5); la variable l3 recibe el valor de false

end.

4° Operador de caracteres: trabajan sobre cadenas de caracteres, la tabla 4.9 muestra este operador:

Operador Operación + concatenación

Tabla 4.9 - Operadores de caracteres Este operador junta dos cadenas de caracteres para forma una sola.

program usoDeOperadoresDeCaracteres; var cad1, cad2, cad3: String; begin

cad1 := ‘Juan’; cad2 := ‘López’; cad3 := cad1 + cad2; la variable cad3 recibe el valor de ‘JuanLópez’ cad3 := cad1 + ‘ ‘ + cad2; la variable cad3 recibe el valor de ‘Juan López’

end.

5° Operadores de Bits: Sirven para manipular datos enteros desde su representación interna, esto es, las operaciones se dan a nivel de los bits de los datos.

La confusión que se puede dar con estos operadores es que coinciden con las mismas palabras con las que se definen los operadores lógicos, sin embargo habrá que entender que los operadores de bits trabajan con variables enteras a diferencia de los otros que trabajan sobre expresiones lógicas.

La tabla 4.10 muestra estos operadores:

Operador Operación not negación a nivel de bits and conjunción a nivel de bits or disyunción inclusiva a nivel de bits xor disyunción exclusiva a nivel de bits shl desplaza un numero n de bits a la izquierda shr desplaza un numero n de bits a la derecha

Tabla 4.10 - Operadores de Bits

A continuación se presentan algunos ejemplos:

program operadoresDeBits_1; var a, b: Byte; // Enteros de 1 byte sin signo

c, d: ShortInt; // Enteros de 1 byte con signo begin

a := 89; Se le asigna a la variable 'a' el valor de 0101 1001

Page 55: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

55

b := not a; Se niega cada uno de los bits de 'a', entonces a la variable 'b' se le asigna el valor de 1010 0110 que representa el valor 166

c := 89; se le asigna a la variable 'c' el valor de 0101 1001 d := not c; Se niega cada uno de los bits de 'c', entonces a la

variable 'd' se le asigna el valor de 10100110 que representa el valor -90

end. program operadoresDeBits_2; var a, b, c: Byte; // Enteros de 1 byte sin signo begin

a := 89; Se le asigna a la variable 'a' el valor de 0101 1001 b := 113; Se le asigna a la variable 'b' el valor de 0111 0001 c := a and b; Se opera cada par de bits de las variables tomando

los ceros como valor falso y unos como valor verdadero y se aplica a cada par la operación ‘and’ lógica. Por lo tanto la variable 'c' recibe el valor de 0101 0001 que representa el valor de 81

c := a or b; Al igual que en el caso anterior se opera cada par de bits, y se aplica a cada par la operación ‘or’ lógica. Por lo tanto la variable 'c' recibe el valor de 0111 1001 que representa el valor de 121

c := a xor b; Al igual que en el caso anterior se opera cada par de bits,

y se aplica a cada par la operación ‘xor’ lógica. Por lo tanto la variable 'c' recibe el valor de 0010 1000 que representa el valor de 40

end. program operadoresDeBits_3; var a, b: Byte; // Enteros de 1 byte sin signo

m, n: ShortInt; // Enteros de 1 byte con signo x, y: Integer; // Enteros de 4 bytes con signo

begin a := 113; Se le asigna a la variable 'a' el valor de 0111 0001 b := a shl 3; Se desplaza 3 bits del numero hacia la izquierda, como la

variable sólo puede contener 8 bits, los tres bits más significativos se perderán y los tres bits menos significativos se rellenarán con ceros. Por lo tanto el valor que recibe la variable 'b' será 1000 1000, que representa el valor 136

m := 113; Se le asigna a la variable 'a' el valor de 0111 0001

Page 56: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

56

n := m shl 3; La operación es igual a la anterior, la diferencia está en la representación de ese conjunto de bits, esto es, el valor que recibe la variable 'n' será 1000 1000 (igual al anterior), pero en este caso representa el valor -120

x := 113; Se le asigna a la variable 'a' el valor de 0000 0000 0000 0000 0000 0000 0111 0001 y := x shl 3; La operación es similar a la anterior, la diferencia está en que

al tener más espacio, los bits más significativo no se pierde, por lo tanto el valor que recibe la variable 'y' será

0000 0000 0000 0000 0000 0011 1000 1000. En este caso representa el valor 904

a := 113; Se le asigna a la variable 'a' el valor de 0111 0001 b := a shr 3; Se desplaza 3 bits del numero hacia la derecha, los tres bits

menos significativos se perderán y los tres bits más significativos se rellenarán con ceros. Por lo tanto el valor que recibe la variable 'b' será 0000 1110, que representa el valor 14. Para los otros tipos de datos el resultado será el mismo.

end.

6° Operador de Pertenencia: Sirven para poder determinar si un valor pertenece o no a un conjunto dado. En Pascal, un conjunto se representa por un grupo de valores ordinales del mismo tipo encerrado entre corchetes y separados por comas. Por ejemplo, [3, 5, 7, 9] es un conjunto de enteros, [a’, ‘e’, ‘i’, ‘o’, ‘u’] es un conjunto de caracteres, [‘A’..’Z’] conjunto formado por la letras mayúsculas10, letras. La tabla 4.11 muestra este operador:

Operador Operación in pertenece a

Tabla 4.11 - Operador de conjunto

El valor devuelto por este operador será de tipo Boolean, esto es true o false.

A continuación se presentan algunos ejemplos:

program opradoresDePermanencia; var a , b: Integer;

m: Char; r1, r2, r3, r4: Boolean;

begin a := 25; b := 33; m := 'h';

10 La expresión ‘A’..’Z’ es un sub rango de datos que contiene todos los caracteres en la taba ASCII que se encuentren entre la ‘A’ y la ‘Z’. Si vemos la tabla ASCII que aparece en el primer capítulo, encontraremos que estos caracteres coinciden con las letras mayúsculas del alfabeto inglés.

Page 57: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

57

r1 := a in [10, 20, 30, 40, 50]; / Falso, 25 no pertenece al conjunto r2 := a in [15..45]; // Verdadero, 25 pertenece al conjunto de valores que se

encuentra entre 15 y 45; r3 := not(b in [10, 20, 30, 40, 50]); // Verdadero: 33 no pertenece al

conjunto. Observe la sintaxis, estará mal si se hace b not in [10, 20, 30, 40, 50].

r4 := m in ['A'..'Z']; // Falso, 'h' no es una letra mayúscula. end.

Reglas de prioridad de operadores: cuando en una expresión se encuentren varios operadores, la forma cómo se evalúen los elementos de la misma se rigen mediante una serie de reglas. El no seguir estas reglas cuando se construye una expresión nos llevará a resultados incorrectos. Estas reglas son las siguientes:

1ra prioridad los paréntesis ( ): si en una expresión, se encuentra una porción de ésta encerrada entre paréntesis, entonces la porción encerrada se evaluará antes que cualquier otra parte de la expresión.

Por ejemplo en la instrucción de asignación: a := 4.5 * (2 + 1.65); primero se evalúa la operación 2 + 1.65 y el resultado se multiplica a 4.5, por lo tanto se le asigna a la variable a el valor de 16.425.

2da prioridad la operación unaria - : cuando el signo menos (-) se emplea para cambiar de signo a un operador, esta operación tiene prioridad sobre los otros operadores.

Por ejemplo en la instrucción a := b * -c; primero se cambia el signo a la variable c y luego se multiplica a la variable b.

3ra prioridad la multiplicación, división, shl y shr

4ta prioridad la suma y resta: esto quiere decir que por ejemplo en a := b + c * d; primero se evalúa c * d y luego este resultado se suma a la variable b.

5ta prioridad los operadores lógicos

6ma prioridad los operadores de relación: esto hace que una expresión lógica deba ser escrita como sigue: bool1 := (a > b) and (c <>0); si se quitarán los paréntesis habría un error porque por la prioridad se intentaría evaluar primero b and c.11

Como regla adicional se establece que en una expresión, si se encontraran dos operadores con la misma prioridad, se evaluará primero el que se encuentre más a la izquierda. Esto último es muy importante porque si quisiéramos evaluar la fórmula

cbax×

= , y escribimos la instrucción de asignación x := a/b*c; estaremos cometiendo

un error debido a que por la regla anterior primero se evalúa a/b y el resultado se

11 En el lenguaje C y C++ la prioridad la tienen los operadores relacionales por encima de los operadores lógicos, por lo que no se requiere agregar los paréntesis.

Page 58: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

58

multiplica por c, con lo que estaríamos evaluando la fórmula b

cax ×= , diferente a la

original. La instrucción escrita correctamente debería ser x:=a/(b*c); ó x:=a/b/c;

Las funciones son parte importante en una expresión, estos elementos nos permiten simplificar el contenido de una expresión, esto debido a que los cálculos que están inmersos en una función están escritos en otra parte; en la expresión sólo se coloca el nombre de la función seguida de los parámetros que requiere.

Las funciones en un lenguaje de programación se incorporan a través de bibliotecas o también pueden ser parte del núcleo del mismo.

Para evitar tener que perder el tiempo escribiendo código innecesario, el programador debe conocer muy bien las funciones que existen en el lenguaje de programación, esto le ayudará a enfocar sus esfuerzos en las partes más importantes del programa que está elaborando.

A continuación se estudiará a fondo las funciones que vienen incorporadas en el núcleo de Pascal, denominadas funciones estándar, y que son similares a la mayoría de lenguajes de programación, de modo que el lector pueda darse una idea del potencial de las mismas.

Funciones aritméticas:

Función Argumento Tipo de resultado Resultado abs(n) entero o real igual al del argumento Valor absoluto: | n | sqr(n) entero o real igual al del argumento Eleva al cuadrado el argumento: n2 sqrt(n) entero o real siempre real Obtiene .la raíz cuadrada de n

Ejemplos:

program usoDeFuncionesAritmeticas; var a, b, c: Integer; r, s, t: Real; begin

a := 7; b := -25; r := -3.56; s := 36.21; c := abs(b); la variable c recibe el valor de 25 t : = abs(r); la variable t recibe el valor de 3.65 c := sqr(a); la variable c recibe el valor de 49 t := sqr(s); la variable t recibe el valor de 1311.1641 t := sqrt(a); la variable t recibe el valor de 2.6457513 t := sqrt(s); la variable t recibe el valor de 6.0174746 c := sqrt(a); error: el resultado es un número real, no se puede asignar a c

end.

Funciones trigonométricas:

Función Argumento Tipo de resultado Resultados pi - siempre real El valor de π: 3.141592 sin(n) entero o real en radianes siempre real El seno del ángulo n cos(n) entero o real en radianes siempre real El coseno del ángulo n arcTan(n) entero o real en radianes siempre real El arco cuya tangente es n.

Ejemplos:

Page 59: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

59

program usoDeFuncionesTrigonometricas; var a, b : Real; begin

a := 2.54; la variable a representa 2.45 radianes b := sin(a); la variable b recibe el valor de 0.565956 b := cos(a); la variable b recibe el valor de -0.824435 a := 0.3456; b := arctan(a); a variable b recibe el valor de 0.332750 radianes a := 28; la variable a representa 28 grados sexagesimales b := sin(a * pi / 180); la variable b recibe el valor de 0.4694720 b := cos(a * pi / 180); la variable b recibe el valor de 0.882948 a := 0.3456; b := arctan(a) * 180 / pi; la variable b recibe el valor de 19.065150°

end.

Ejercicio:

Si β es igual al arco cuyo seno es de 0.7542, ¿cuánto vale β?

Solución: Dado que el Pascal no posee la función arco seno, debemos buscar la forma de hallar el valor de β por otros medios. Por las reglas de la trigonometría se sabe que el seno de β es 0.7542, por lo tanto en un triángulo rectángulo se tendrá el siguiente esquema:

El cateto que falta se puede evaluar como:

Con lo que se tienen:

Por lo tanto podemos decir que β es el arco tangente de 0.7542 ÷

La instrucción que permite calcular β en Pascal será:

b := arcTan(0.7542/sqrt(1-sqr(0.7542)))*180/pi; la variable b recibe el valor de 48.955517° grados sexagesimales

Observar que se emplea b y no β en la instrucción.

0.7542 1

β

0.7542 1

β

12 - 0.75422

12 - 0.75422

12 - 0.75422

Page 60: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

60

Funciones exponenciales:

Función Argumento Tipo de resultado Resultado exp(n) Entero o real siempre real Calcula el valor de en. ln(n) Entero o real siempre real Calcula el logaritmo natural de n.

Ejemplos:

program usoDeFuncionesExponenciales; var a, b : Real; begin

a := 2.54; b := exp(a); la variable b recibe el valor de 12.679671 b := ln(a); la variable b recibe el valor de 0.932164 b := exp(1); la variable b recibe el valor de 2.718282 valor de e

end.

Ejercicio:

Calcular el valor de 12.452.5 y el de log10(2.54)

Solución: El lenguaje Pascal no tiene funciones ni operadores que permitan calcular el valor de un número elevado a una potencia dada (xy) o el logaritmo decimal, como lo tiene el lenguaje C (pow(x, y) y log10(x) de la biblioteca math.h) o el lenguaje Basic (el operador ^ para calcular xy), por eso debemos solucionarlo aplicando las propiedades de los logaritmos, entonces:

Dado que queremos calcular: Z = XY

Al aplicar el logaritmo natural a ambos factores se tiene: ln(Z) = ln(XY)

Aplicando las propiedades de los logaritmos se tiene: ln(Z) = Y·ln(X)

Al aplicar una operación inversa ambos factores se tiene: eln(Z) = eY·ln(X)

Se cancelan las operaciones en el primer factor y se tiene: Z = eY·ln(X)

Por otro lado queremos calcular: Y = log10(X)

Se sabe que ( ) ( )( )npp

K

Kn log

loglog = , por lo tanto Y = ln(X) ÷ ln (10)

Las instrucciones que permiten calcular estas operaciones en Pascal serán:

program usoDeFuncionesExponenciales; var a, b : Real; begin

a := exp(2.5 * ln(12.45)); la variable a recibe el valor de 546.919463 b := ln(2.54) / ln(10); la variable b recibe el valor de 0.404834

end.

Page 61: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

61

Funciones de cambio de tipo:

Función Argumento Tipo de resultado Resultado

trunc(n) entero o real entero Devuelve la parte entera de n. int(n) entero o real real Devuelve la parte entera de n pero en formato real. round(n) entero o real entero Redondea el valor de n al 0.5. frac(n) entero o real real Devuelve la parte fraccionaria de n.

Ejemplos:

program usoDeFuncionesDeCambioDeTipo; var r1, r2 : Real; i1, i2 : Integer; begin

r1 := 23.756; i1 := trunc(r1); la variable i1 recibe el valor de 23 i2 := int(r1); ERROR: el resultado es un valor real r2 := int(r1); la variable r2 recibe el valor de 23.0 i1 := round(r1) la variable i1 recibe el valor de 24 r2 := frac(r1) la variable r2 recibe el valor de 0.756

end.

Ejercicio:

Se desea redondear el valor de 23.736 al segundo decimal. program redondeoDecimal; var r1, r2: Real;

i1: Integer; begin

r1 := 23.736; r2 := r1 * 100; la variable r2 recibe el valor de 2373.6 i1 := round(r2); la variable i1 recibe el valor de 2374 r1 := i1 / 100; la variable r1 recibe el valor de 23.73

end.

Funciones ordinales:

Función Argumento Tipo de resultado Resultado ord(n) valor ordinal entero La posición de n en la tabla a la que pertenece chr(n) entero, entre

0 y 255 caracter Devuelve el caracter ASCII correspondiente al valor de n.

pred(n) valor ordinal igual al del argumento Da el valor que precede a n en la tabla a la que pertenece succ(n) valor ordinal igual al del argumento Da el valor que sucede a n en la tabla a la que pertenece

Ejemplos: program usoDeFuncionesOdinales; var i1, i2, i3 : Integer;

c1, c2, c3 : Char; b1, b2, b3 : Boolean;

Page 62: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

62

begin c1 := ‘A’; i1 := ord(c1); la variable i1 recibe el valor de 65 b1 := true; i1 := ord(b1); la variable i1 recibe el valor de 1 b2 := false; i1 := ord(b2); la variable i1 recibe el valor de 0 i1 := 75 c1 := chr(i1); la variable c recibe el valor de ‘K’ i1 := 56;

i2 := pred(i1); la variable i2 recibe el valor de 55 i3 := succ(i1); la variable i3 recibe el valor de 57

c1 := ‘F’; c2 := pred(c1); la variable c2 recibe el valor de ‘E’ c3 := succ(c1); la variable c3 recibe el valor de ‘G’

b1 := true; b2 := pred(b1); la variable b2 recibe el valor de false b3 := succ(b2); la variable b3 recibe el valor de true

end.

Ejercicio:

Se desea asignar a una variable el mayor valor entre otras dos, por ejemplo si la variable a tiene el valor de 3 y la variable b tiene 5, la variable c debe recibir el valor de 5. La operación debe funcionar correctamente sin saber cuáles serán los valores que se asignarán a las variables a y b.

Solución: La expresión que diseñemos debe asignar el mayor valor entre dos variables. Como no sabemos de antemano el valor de las variables, la expresión debe de alguna manera anular el menor valor y hacer prevalecer el mayor.

La solución tiene que ir por el siguiente camino: c := a * exp1 + b * exp2;

Donde debemos encontrar una expresión exp1 que anule el valor de la variable a si este es menor que el de la variable b y lo mantenga en caso contrario. Por otro lado la expresión exp2 debe hacer algo similar, esto es anular el valor de la variable b si es menor que el de la variable a o mantenerlo en caso contrario.

La manera de anular un valor se puede hacer multiplicando el valor por cero (0) y de mantenerlo multiplicándolo por uno (1). Por lo tanto las expresiones exp1y exp2 deben dar como resultado 0 ó 1. La forma más sencilla de obtener 0 ó 1 en una expresión es aplicando la función ord a una expresión lógica (ord(false) da como resultado 0 y ord(true) da como resultado 1).

Por lo tanto la solución para el ejercicio será:

c := a * ord(a>b) + b * ord(b>a);

Observe que si a es mayor que b, la expresión a>b da verdadero y la expresión b>a da falso, por lo tanto se anula el valor de la variable b, y se anulará el valor de la variable a si el valor de b es mayor que el de a.

Page 63: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

63

Ejercicio:

Dada una variable de tipo char que contiene un caracter cualquiera, convertir ese caracter en una letra minúscula si el caracter es una letra mayúscula, pero mantener su valor inalterado si es otro caracter.

Solución: En este caso hay que observar la ubicación de las letras mayúsculas y minúsculas en la tabla ASCII, ahí se puede observar que las letras mayúsculas se encuentran agrupadas consecutivamente desde la ‘A’ hasta la ‘Z’ en una zona de la tabla (como veremos más adelante, no será necesario conocer la posición exacta de los caracteres en la tabla, ni su código ASCII), y que las letras minúsculas se encuentran también agrupadas consecutivamente en otra parte de la tabla en una ubicación posterior a la de las letras mayúsculas (esto último si es importante).

Al estar agrupadas las letras, se tendrá que si tomamos una letra minúscula cualquiera y su correspondiente letra mayúscula y restamos sus códigos ASCII siempre obtendremos el mismo valor. Esto es fácil de demostrar:

… A B C D … X Y Z … a b c d … X Y z … ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑

P P+1 P+2 P+3 P+23 P+24 P+26 T T+1 T+2 T+3 T+23 T+24 T+26

Según esto:

El código ASCII de ‘a’ menos el código ASCII de ‘A’ = T – P El código ASCII de ‘d’ menos el código ASCII de ‘D’ = (T + 3) – (P + 3) = T - P El código ASCII de ‘y’ menos el código ASCII de ‘Y’ = (T + 24) – (P + 24) = T - P

Luego, si tenemos una letra mayúscula será suficiente sumarle a su código ASCII, la diferencia entre los códigos ASCII de la ‘a’ y la ‘A’ y obtendremos su correspondiente letra minúscula.

Por otro lado, la respuesta a este problema tiene que ir por un camino similar al del problema anterior, esto debido a que hay una condición que debemos evaluar, esto es que si el caracter no es una letra minúscula debemos mantenerlo inalterado, y sólo se debe cambiar las mayúsculas.

Finalmente, como se trata de caracteres se tendrá que hacer una conversión del caracter a un entero y luego de aplicarle la expresión correspondiente volverlo a convertir a caracter.

La expresión que se obtiene es la siguiente:

car := chr( ord(car) + ( ord(‘a’) – ord(‘A’) ) * ord( (car >= ’A’) and (car <= ‘Z’) ) );

En la expresión se puede apreciar que si el caracter que contiene la variable car no es una letra mayúscula se anula la suma y el caracter no se altera, pero si es una letra mayúscula se le aplica la suma y se convierte en una minúscula.

Código ASCII de la ‘A’ = P Código ASCII de la ‘a’ = T

Page 64: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

64

Funciones de Valor Lógico

Función Argumento Tipo de res. Resultado odd(n) entero Lógico Nos indica si el argumento es impar o no. eof(n) variable de archivo Lógico Nos indica si llegamos al final de un archivo12. eoln(n) variable de archivo Lógico Nos indica si llegamos al final de una línea en un archivo.

Ejemplos: program usoDeFuncionesDeValorLogico; var i1, i2 : Integer;

b1, b2: Boolean; begin

i1 := 56; i2 := 15; b1 := odd(i1); la variable b1 recibe el valor de false b2 := odd(i2); la variable b2 recibe el valor de true

end.

Funciones aleatorias:

Función Argumento Tipo de resultado random - real en el rango: [0,1[ random(n) entero entero en el rango: [0,n-1]

Ejemplos: program usoDeFuncionesAleatorias1; var i: Integer;

r: Real; begin

r := random; la variable r recibe un valor aleatorio p. e.: 0.5435682 i := random(10); la variable i recibe un valor aleatorio entre 0 y 9, p. e.: 7

end.

Ejercicio: Determinar un valor aleatorio entre 12 y 28

Solución: Como los valores aleatorios no se pueden definir en un rango dado, tendremos que determinar un valor aleatorio en el rango [0, n-1], donde el tamaño del rango sea igual al del rango [12, 28], de este modo sólo se tendrá que agregar al valor obtenido por la función random el límite inferior del rango para obtener el valor deseado. Así:

program usoDeFuncionesAleatorias2; var limInf, limSup, valorAleat, tamRango: Integer; begin

limInf : = 12; limSup := 28; tamRango := limSup – limInf + 1; la variable tamRango recibe el valor de 17 valorAleat := random(tamRango); la variable valorAleat recibe el valor ente 0 y 16 valorAleat := valorAleat + limInf; la variable valorAleat recibe el valor ente 12 y 28

end.

12 El manejo de archivos se verá más adelante

Page 65: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

65

Ejercicio:

Determinar un número aleatorio entero, múltiplo de 3, que se encuentre entre un límite inferior y uno superior. Si los límites no han sido dados correctamente, esto es que límite inferior sea mayor que el superior la respuesta debe ser cero.

Solución: La última condición del problema es muy fácil de solucionar, ya que la respuesta que obtendremos se anulará si no cumple las condiciones, esto ya se ha hecho en ejercicios anteriores.

En el ejercicio anterior se solucionó el problema de determinar un número aleatorio dentro de un rango dado, el problema está en que el número aleatorio debe ser múltiplo de 3. La manera de conseguirlo es determinar otro rango, a partir de los límites, que permita determinar un número aleatorio el cual al multiplicase por 3 de un valor dentro de los límites iniciales. Por ejemplo si los límites fueran [7, 20], podemos determinar un rango como [3,6], cualquier valor que tomemos de este último rango al ser multiplicado por 3 estará entre 7 y 20 y será múltiplo de 3.

En estos términos se presenta la siguiente solución:

program usoDeFuncionesAleatorias3; var limInf, limSup, nuevoLimInf, nuevoLimSup, valorAleat, tamRango: Integer;

respuestaValida: Boolean; begin

randomize; limInf := ; limSup := ; indica un valor dado inicialmente nuevoLimInf := limInf * ord(limInf mod 3 = 0) +

(limInf + 1) * ord( (limInf + 1) mod 3 = 0) + (limInf + 2) * ord( (limInf + 2) mod 3 = 0); se calcula el menor valor dentro del rango que es múltiplo de 3

nuevoLimInf := nuevoLimInf div 3; se calcula el límite inferior del nuevo rango

nuevoLimSup := limSup * ord(limSup mod 3 = 0) + (limSup - 1) * ord( (limSup - 1) mod 3 = 0) + (limSup - 2) * ord( (limSup - 2) mod 3 = 0); se calcula el mayor valor dentro del rango que es múltiplo de 3

nuevoLimSup := nuevoLimSup div 3; se calcula el límite superior del nuevo rango

tamRango := nuevoLimSup – nuevoLimInf + 1; valorAleat := random(tamRango) + nuevoLimInf; se obtiene un valor aleatorio en

el segundo rango valorAleat := valorAleat * 3; se obtiene un valor aleatorio, múltiplo de 3, en el

rango original

respuestaValida := limSup > limInf; valorAleat := valorAleat * ord(respuestaValida); se anula o no el resultado

end.

Page 66: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

66

Procedimientos Aritméticos Estándar

Aunque estos elementos no se pueden colocar dentro de una expresión ya que son procedimientos y no funciones, y por lo tanto no devuelven resultados, es bueno conocerlos porque son muy útiles.

Procedimiento Argumento Descripción ramdomize Inicializa la semilla para la función random dec(x) Valor ordinal modifica x con su predecesor dec(x, n) x debe ser ordinal, n entero modifica x con su n-ésimo predecesor inc(x) Valor ordinal modifica x con su sucesor inc(x, n) x debe ser ordinal, n entero modifica x con su n-ésimo sucesor

Ejemplos: program usoDeProcedimientosAritmeticos; var i: Integer; car: Char; begin

i := 34; inc(i); la variable i se modifica con el valor de 35 i := 34; dec(i); la variable i se modifica con el valor de 33 i := 34; inc(i,5); la variable i se modifica con el valor de 39 i := 34; dec(i,7); la variable i se modifica con el valor de 27 car := ‘K’; inc(car); la variable car se modifica con el valor de ‘L’ car := ‘K’; dec(car); la variable car se modifica con el valor de ‘J’ car := ‘K’; inc(car,5); la variable i se modifica con el valor de ‘P’ car := ‘K’; dec(car,7); la variable i se modifica con el valor de ‘D’

end.

Instrucciones que permiten la salida y el ingreso de datos Cuando se escribe un programa se debe pensar que lo hacemos para obtener resultados. Los ejemplos y ejercicios que hemos mostrado hasta ahora están incompletos, esto debido a que si los escribimos en un entorno de desarrollo y los compilamos, si es cierto que es seguro que no encontremos errores, si los ejecutamos no veremos los resultados. La razón para este comportamiento es que no hemos colocado en los programa instrucciones que nos permitan ver el contenido de las variables que tienen los resultados esperados.

Por otro lado, si los programas que presentamos los ejecutamos una y otra vez vamos a obtener los mismos resultados (salvo el caso de las funciones aleatorias). La razón es que los valores que se asignan a las variables se dan en tiempo de compilación y no en tiempo de ejecución. Esto quiere decir que el programa ya compilado tiene la orden de asignar un valor predeterminado a una variable, y este valor no cambiará. Por eso se requieren órdenes que permitan dar valores a las variables cuando el programa se esté ejecutando, estos valores se deben dar por un medio externo al programa.

Lo que se va a estudiar en esta sección son instrucciones que permiten el ingreso y salida de datos, básicamente se estudiarán las instrucciones de salida y entrada de datos del Pascal que son los procedimientos writeln, write, read y readln.

Page 67: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

67

Procedimiento writeln

Este procedimiento permite mostrar el contenido de las variables y de expresiones en forma de texto en el medio estándar de salida (la consola o el monitor del computador). Luego que termina de realizar la tarea imprimir todas las variables y expresiones que se le han entregado por medio de parámetros, la instrucción efectúa un cambio de línea de modo que la siguiente orden de entrada o salida que se ejecute en el programa se realicen un línea por debajo de los que acaban de aparecer.

La sintaxis para esta orden se presenta a continuación:

Ejemplos:

1° Empleando número enteros:

program usoDelProcedimientoWritelnConNumerosEnteros; var a, b, c: Integer; begin

a:= 2; b:= -34; c:=123; Imprimiendo un valor a la vez writeln(a); writeln(b); writeln(c); Imprimiendo valores con etiquetas uno a la vez writeln(‘A= ‘,a); writeln(‘B= ‘,b); writeln(‘C= ‘,c); Imprimiendo valores en la misma orden writeln(a, b, c); Igual pero con etiquetas writeln(‘A= ‘,a, ‘B= ‘,b, ‘C= ‘,c); Colocando más espacios en las writeln(‘A= ‘,a, ‘ B= ‘,b, ‘ C= ‘,c);

A= 2B= -34C= 123 _

( ) writeln expresión

tamaño de campo

,

;

2 -34 123 _

En este punto la pantalla se verá así:

Cursor

A= 2 B= -34 C =123 _

Cursor

Los valore se pegan unos con otros

2-34123 _

espacios

A= 2 B= -34 C= 123 _ espacios

Page 68: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

68

Colocando un tamaño de campo. Mediante un valor entero se define el número mínimo da caracteres que se emplearán para imprimir un valor. Si el valor tiene menos caracteres que del tamaño dado, se completa con espacios en blanco, si tiene más, se ignora el tamaño. writeln(a:7, b:7, c:7); writeln(a:2, b:2; c:2);

Imprimiendo valores tabulados writeln(a,b,c); writeln(b,c,a); writeln(c,a,b); writeln(a,' ',b,' ',c); writeln(b,' ',c,' ',a); writeln(c,' ',a,' ',b); writeln(a:9,b:9,c:9); writeln(b:9,c:9,a:9); writeln(c:9,a:9,b:9);

end.

2° Empleando número reales:

program usoDelProcedimientoWritelnConNumerosReales; var r, s: Real; begin

r:= 34.67; s:=-123.0236587; writeln(r); writeln(s); writeln(‘R= ‘,r); writeln(‘S= ‘,s); writeln(r:10:3); writeln(s:10:3); writeln(s:10:4);

end.

2-34123 -341232 1232-34 _

2 -34 123 -34 123 2 123 2 -34 _

2 -34 123 -34 123 2 123 2 -34

_

2 -34 123 _ 4 espacios + 3 dígitos

6 espacios +1 dígitos

2-34123 _

1 espacios +1 dígitos

3 dígitos

3.4670000000E+01 -1.2302365870E+02 _

R= 3.4670000000E+01 S= -1.2302365870E+02 _

34.670 -123.024 -123.0237

_ Redondeo

Page 69: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

69

Procedimiento write

Es un procedimiento similar a writeln pero con la diferencia que luego de realizar su tarea no efectúa un cambio de línea.

Ejemplos:

program usoDelProcedimientoWrite; var a, b, c: Integer; begin

a := 2; b:= -34; c:=123; write(a); write(b); write(c); writeln; pone el cursor en la siguiente línea Cambio de línea: el efecto del cambio de línea se produce cuando se imprimen los caracteres cuyo código ASCII en 10 (line feed) y 13 (carriage return) write(c,#10,a); Al imprimir el caracter #10 el cursor baja writeln; write(c,#13,a); Al imprimir el caracter #13 el cursor retrocede al inicio de la línea writeln; write(c,#13,#10,a); La combinación del caracter #13 y el #10 producen el efecto del cambio de línea completo writeln;

Por lo tanto hacer writeln(a) es equivalente a hacer write(a, #13, #10);

Procedimiento read

Este procedimiento permite ingresar datos al programa, esto es asignar a las variables del programa valores que se ingresan desde la unidad estándar de entrada del sistema que en la mayoría de casos corresponde a la consola.

Cuando se ejecuta este procedimiento por primera vez, el programa se detiene para permitir al usuario escribir en la consola un flujo de texto con los datos que desea ingresar. Luego de escribirlos el usuario presiona la tecla ENTER y en ese momento los datos pasan a una zona de memoria temporal denominada “buffer de entrada”, es allí

En este punto la pantalla se verá así:

2-34123_

Cursor

123 2_

Cursor

223 Cursor

123 2_

Cursor

Page 70: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

70

donde el procedimiento read comienza a explorar uno a uno cada caracter transformándolo en una representación binaria y almacenándolo en las variables del programa de acuerdo al tipo de éstas. Luego de realizar la operación, la información que no se utilizó queda almacenada en al “buffer” para ser leídas en otras operaciones de lectura.

La siguiente vez que se ejecute la orden read, primero verá si hay algo en el buffer, si lo hay toma los datos que requiere de allí, si no detiene el programa y el proceso anterior se repite.

Si la cantidad de datos ingresados es menor al que contiene la orden read, el proceso se detiene luego de asignar el último dato para solicitar el resto.

Ejemplos:

program usoDelProcedimientoRead; var i1, i2: Integer;

r1: Real; begin

read(i1, r1, i2); El ‘57’ se convierte en una representación binaria y se asigna a la variable i1, lo mismo pasa con el ’23.45’ y ‘145’ que se asignan a r1 e i2 respectivamente read(i1); El ‘391’ se convierte en una representación binaria y se asigna a la variable i1, el resto del texto se queda en el ‘buffer’ read(r1); read(i2); El ‘programa no se detuvo, asignó el ‘0.8367’ y el ’5’ a las variables r1 e i2 respectivamente’

end.

Procedimiento readln

Es similar al procedimiento read, pero a diferencia de éste último, luego de realizar la operación, la información que no se utilizó es eliminada del “buffer”.

Ejemplos:

Aquí el programa se detiene, el usuario debe ingresa el siguiente texto:

Como el ‘buffer’ está vacío el programa se detiene, el usuario ingresa el siguiente texto:

391 0.8367 5 ↵

Enter

57 23.45 145 ↵

Enter

espacios

( ) read variable

,

;

Page 71: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

71

program usoDelProcedimientoReadln; var i1, i2: Integer;

r1: Real; begin

readln(i1); El ‘57’ se convierte en una representación binaria y se asigna a la variable i1, el resto de información se borra del ‘buffer’ readln(r1) Aquí se vuelve a detener el programa para esperar un dato para r1, cuando lo obtiene borra el resto del ‘buffer’ readln(i2); Aquí se vuelve a detener el programa para esperar un dato para i2, cuando lo obtiene borra el resto del ‘buffer’

end.

Lectura de Caracteres y Cadenas de Caracteres

La lectura de caracteres y cadenas de caracteres son casos especiales en el proceso de lectura, en primer lugar, en estos casos no se transforman los datos del buffer como se hacen con los valores numéricos. Por otro lado su comportamiento es peculiar, los ejemplos siguientes ilustran estos detalles.

Ejemplos:

1° Lectura de caracteres:

program lecturaDeCaracteresYCadenas; var c1, c2, c3: Char;

s1: String; i1, i2: Integer;

begin Lectura de caracteres readln(c1, c2, c3); Lo que se asigna es el caracter ‘A’ a la variable c1, el ‘ ‘ (espacio) a la variable c2 y la ‘B’ a c3 el resto se borra’ readln(c1, c2, c3); Lo que se asigna es el caracter ‘A’ a la variable c1, el ‘B‘ a la variable c2 y el ‘C’ a c3’ readln(c1, i1); Se asigna es el caracter ‘A’ a la variable c1 y el 123 a i1’

Aquí el programa se detiene, el usuario debe ingresa el siguiente texto:

57 23.45 145 ↵

Enter

Si se ingresan los caracteres como sigue:

A B C ↵

Enter

espacios

Si se ingresan los caracteres como sigue:

ABC ↵

Enter

La lectura es correcta si primero se pretende leer el caracter y luego el número:

A 123 ↵

Enter

Page 72: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

72

readln(i1, c1); Se asigna el 123 a i1’ y el caracter ‘ ’ (espacio) a la variable c1 readln(c1, i1); Se produce un error porque 123A no es un formato válido para un entero. Se interrumpe el programa’ Lectura de cadenas de caraacteres readln(s1); Lectura es correcta se asigna ‘Juan López Pérez’ a la variable s1 readln(s1, i1, i2); Lectura NO es correcta se asigna ‘Juan López Pérez 123 47’ a la variable s1 y se detiene el programa a la espera de ingresar valores para i1 e i2 readln(i1, i2, S1); Lectura NO es correcta se asigna 123 a la variable i1 y 47 a la variable i2, pero a la variable se le asigna ‘ Juan López Pérez’ con un espacio al inicio y eso está mal La manera correcta de leer cadenas de caracteres es: readln(S1); readln(i1, i2); Cada variable de tipo String debe ir en una orden readln por separado

end.

Ejemplos de programas secuenciales: A continuación presentamos algunos ejemplos de problemas que se solucionan con programas que emplean instrucciones de entrada y salida así como expresiones.

1. Escriba un programa en Pascal que permita leer las coordenadas de dos puntos en un plano y que calcule la distancia entre ellos y el ángulo que forma el segmento de recta con la horizontal.

Datos: Se parte de las coordenadas de dos puntos P1, y P2. Por lo tanto se tiene como datos, que serán leídos, los valores de X1 e Y1 del punto P1 y X2 e Y2 del punto P2.

La lectura no es correcta si primero se pretende leer el número y luego el caracter:

123 A ↵

Enter

123A ↵

Enter

Juan López Pérez ↵

Enter

Juan López Pérez 123 47↵

Enter

123 47 Juan López Pérez↵ Enter

espacio

Page 73: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

73

Resultados: Lo que se espera del programa es que nos determine la distancia (D) entre los puntos P1 y P2, y por otro lado el ángulo (β) que hace el segmento de recta que une P1 y P2 con la horizontal.

Cálculos:

Diagrama de Flujo: Un diagrama de flujo puede ayudar a visualizar la solución al problema.

Programa:

Program calculaLaDistanciaYAnguloEntreDosPuntos; var x1, x2, y1, y2, d, ang: Real; begin

Lectura de datos: write(‘Ingrese las coordenadas de P1: ‘); readln(x1, y1); write(‘Ingrese las coordenadas de P2: ‘); readln(x2, y2); Calculamos resultados: d := sqrt( sqr(x2-x1) + sqr(y2-y1) ); ang := arcTan( (y2-y1) / (x2-x1) ) * 180 / pi; Mostramos resultados: writeln(‘La distancia entre P1 y P2 es: ‘, D:7:3); writeln(‘EL ángulo que forma el segmento con la horizontal es: ‘, ang:7:2, ‘°’);

end.

P2=(X2, Y3)

( ) ( )22 1212 YYXXD −+−=

P1=(X1, Y1)

D

Y2 - Y1

X2 - X1

ß

−−

= −

12121

XXYYTanβ

Inicio

P1, P2

D = f1 ß = f2

D, ß

Fin

… f1

… f2

Page 74: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

74

2. Escriba un programa que permita leer los datos que se requieran para calcular e imprimir el valor de α

Datos: El valor de α se calcula a partir de φ, δ y Ω, por lo que estos últimos serán los datos del programa. Como el lenguaje no permite el uso de estos símbolos se les tendrá que dar nombres alternativos como alfa, phi, delta y omega.

Resultados: Lo que se espera del programa es que nos determine el valor de α.

Cálculos: La solución se debe dar en partes, esto quiere decir que no vamos a calcular todo en una sala expresión, si no que partiremos la expresión de modo que el programa sea más claro y ordenado. Los cálculos podrían hacerse en el siguiente orden:

(1)… 5.2

φ (2)… ( )1φ (3)… ( ))2(cos 1−

(4)… Ω+δ (5)… ( ))4(senh (6)… )5(δ

(7)… [ ])6(log5 (8)… 5 )7(

(9)… 3 φ (10)… )9(4+δ (11)… ( ))10(tan

(12)… )11(

)8()3( × (13)… 5 )12(

Programa:

program calculoDeFormulaCompleja; var alfa, phi, delta, omega: Real; variables principales

1 phi25, 2 phiphi25, 3 cos_1, 4 deltaMasOmega, 5 senh, 6 divRDelta5, 7 log5, 8 raiz5, 9 raiz3Phi, 10 delta4Mas9, 11 tan10, 12 div3x8_11: Real;

begin Lectura de datos: write('Ingrese los valores de phi, delta y omega: '); readln(phi, delta, omega); Cálculos parciales: 1 phi25 := exp(2.5*ln(phi)); 2 phiphi25 := exp(phi25*ln(phi)); 3 cos_1 := arcTan ( sqrt(1-sqr(phiphi25))/phiphi25);

−=

===

2

5

xx

-1

eesenh(x)

tangentetan5baseenlogaritmolog

cosenoarcocos

:donde

( ) ( )( )

5

4

5 51

3

5.2

tan

senhlogcos

φ

φ

δ

δδφ

α+

Ω+

×

=

Page 75: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

75

4 deltaMasOmega := delta + omega; 5 senh := (exp(deltaMasOmega)-exp(-deltaMasOmega))/2; 6 divRDelta5 := sqrt(delta)/senh; 7 log5 := ln(divRDelta5)/ln(5); 8 raiz5 := exp( (1/5)*ln(log5)); 9 raiz3Phi := exp( (1/3)*ln(phi)); 10 delta4Mas9 := exp((4+raiz3Phi)*ln(delta)); 11 tan10 := sin(delta4Mas9) / cos(delta4Mas9); 12 div3x8_11 := (cos_1*raiz5)/tan10; Cálculo de alfa: alfa := exp((1/5)*ln(div3x8_11)); Mostramos resultados writeln('El valor de alfa es: ',alfa:10:5);

end.

Prueba: Al ejecutar este progarma observaremos el siguiente resultado:

Ingrese los valores de phi, delta y omega: 0.8374 0.5432 0.13159 ↵

El valor de alfa es: 1.29685

3. Escriba un programa que permita ingresar el nombre de un alumno, su código y sus notas de un curso (cuatro prácticas, las notas de los exámenes 1, 2 y del examen especial). El programa deberá calcular la nota que obtendrá el alumno en el curso.

La nota se calculará de la siguiente manera:

1024133 exexppnota ×+×+×

=

Donde: pp es el promedio de prácticas eliminando la nota más baja. ex1 es la nota del primer examen ex2 es la nota del segundo examen El examen especial reemplaza a uno de los exámenes en el caso que uno de ellos no se hubiera rendido.

Datos: Se tiene como datos el nombre del alumno y el código, sus notas a las que llamaremos pr1, pr2, pr3, pr4, ex1, ex2, exE. Las notas estarán entre 0 y 20, si un examen no se dio, se ingresará en su lugar el valor -1, si no se rindieron los dos exámenes, el examen especial reemplazará al segundo examen. Tareas que se han de realizar en el programa: - Determinar la nota más baja de prácticas. - Calcula pp: Sumar las cuatro notas de práctica y restarle la menor, el resultado

dividirlo entre tres. - Verificar si se rindió el examen 2, si no es así reemplazarlo por el ex. espacial. - Verificar si se rindió el examen 1, si no es así reemplazarlo por el ex. espacial si

es que se rindió el examen 2. - Calcular nota del alumno.

Resultado: Nota del alumno

Page 76: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

76

Programa:

program promedioDeNotas; var pr1, pr2, pr3, pr4, ex1, ex2, exE,

prMin, nota: Integer; promPr : Real; nombre, codigo: String;

begin Lectura de datos: write('Ingrese el código del alumno: '); readln(codigo); write('Ingrese el nombre del alumno: '); readln(nombre); write('Ingrese las notas de práctica (4): '); readln(pr1, pr2, pr3, pr4); write('Ingrese las notas de los exámenes 1, 2 y especial: '); readln(ex1, ex2, exE); Cálculos: 1) Tomamos la menor entre pr1 y pr2

prMin := ord(pr1<=pr2)*pr1 + ord(pr2<pr1)*pr2; 2) Tomamos la menor entre la anterior y pr3

prMin := ord(prMin<=pr3)*prMin + ord(pr3<prMin)*pr3; 3) Tomamos la menor entre la anterior y pr4

prMin := ord(prMin<=pr4)*prMin + ord(pr4<prMin)*pr4; promPr := (pr1 + pr2 + pr3 + pr4 - prMin)/3;

Decidimos reemplazar o no el examen especial con el examen 1 ex1 := ord(ex1<>-1)*ex1 + ord((ex1=-1) and (ex2<>-1))*exE; Decidimos reemplazar o no el examen especial con el examen 2 ex2 := ord(ex2<>-1)*ex2 + ord(ex2=-1)*exE; nota := round((promPr*3 + ex1*3 + ex2*4) / 10); Mostrar resultados: writeln('EL alumno: ', codigo, ' ', nombre, ' obtuvo de nota: ', nota);

end.

4. Escriba un programa que reciba como dato un valor entero de 4 bytes. El programa deberá manejar el número como si tuviera la forma ABCD, donde A, B, C, D son las representaciones binarias de cada uno de los bytes del número. El programa deberá generar otro número a partir del primero que tengan la forma de C’A’D’B’. Donde C’ contiene lo mismo que C pero con los bits pares invertidos. A’ contiene lo mismo que A pero los 4 bits más significativos se colocan como los menos significativos y los 4 menos significativos como más significativos. D’ contiene lo mismo que D pero con los bits impares invertidos. B’ contiene lo mismo que B pero con los dos bits menos significativos intercambiados en posición. Ejemplo:

Page 77: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

77

Datos: A B C D X = 902661613 00110101 11001101 10000101 11101101

C C’ 10000101 11010000 A A’ 00110101 01010011 D D’ 11101101 01000111 B B’ 11001101 11001110

Resultado: C’ A’ D’ B’ S = -799848498 11010000 01010011 01000111 11001110

Tareas que se han de realizar en el programa: - Leer el número. - Separar cada byte del número y colocarlas en cuatro variables diferentes. - Tomar la 3ra. variable (C) y proceder a invertir los bits pares. - Tomar la 1ra. variable (A) e intercambiar los cuatro bits más significativos con los

cuatro menos significativos - Tomar la 4ta. variable (D) y proceder a invertir los bits impares. - Tomar la 2da. variable (B) e intercambiar los dos bits menos significativos. - Formar nuevamente el número con los cambios hechos, en el orden como se

muestran las tareas indicadas arriba.

Programa: program EjemploDelManejodeBits; var valor: Integer; // Variable de 4 bytes.

a, b, c, d: Integer; a1, b1, c1, d1: Integer; nuevoValor: Integer; mascara, aux: Integer;

begin Leer el número, p. e.: 902661613 ==> 00110101 11001101 10000101 11101101 write('Ingrese un numero entero grande: '); readln(valor); Separar cada byte del número y colocarlas en cuatro variables diferentes: a := valor; Recibe el valor 00110101 11001101 10000101 11101101 a := a shr 24; Al desplazar los bits a la derecha, la variable 'a' toma el valor de

00000000 00000000 00000000 00110101, valor esperado.

b := valor; Recibe el valor 00110101 11001101 10000101 11101101 b := b shl 8; Al desplazar los bits a la izquierda, la variable 'b' toma el valor de

11001101 10000101 11101101 00000000 b := b shr 24; Al desplazar los bits a la derecha, la variable 'a' toma el valor de

00000000 00000000 00000000 00110101, valor esperado.

Page 78: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

78

c := valor; Recibe el valor 00110101 11001101 10000101 11101101 c := c shl 16; Al desplazar los bits a la izquierda, la variable 'c' toma el valor de

10000101 11101101 00000000 00000000 c := c shr 24; Al desplazar los bits a la derecha, la variable 'c' toma el valor de

00000000 00000000 00000000 10000101, valor esperado.

d := valor; Recibe el valor 00110101 11001101 10000101 11101101 d := d shl 24; Al desplazar los bits a la izquierda, la variable 'd' toma el valor de

11101101 00000000 00000000 00000000 d := d shr 24; Al desplazar los bits a la derecha, la variable 'd' toma el valor de

00000000 00000000 00000000 11101101, valor esperado.

Tomar la 3ra. variable (C) y proceder a invertir los bits pares.

Para invertir el valor de un bit emplearemos las siguientes reglas:

0 xor 0 = 0 1 xor 0 = 1 ==> Bit xor 0 = Bit 0 xor 1 = 1 1 xor 1 = 0 ==> Bit xor 1 = Bit invertido

Entonces debido a que quiere invertir solo los bits pares, formaremos el valor (máscara) 0101 0101 y le aplicaremos la operación xor para conseguir la meta esperada.

Formamos la máscara mascara := 1; // 00000000 00000000 00000000 00000001 mascara := mascara shl 2; // 00000000 00000000 00000000 00000100 mascara := mascara + 1; // 00000000 00000000 00000000 00000101 mascara := mascara shl 2; // 00000000 00000000 00000000 00010100 mascara := mascara + 1; // 00000000 00000000 00000000 00010101 mascara := mascara shl 2; // 00000000 00000000 00000000 01010100 mascara := mascara + 1; // 00000000 00000000 00000000 01010101, OK Invertimos los bits pares de C c1 := c xor mascara; 00000000 00000000 00000000 10000101 xor

00000000 00000000 00000000 01010101 = 00000000 00000000 00000000 11010000

Tomar la 4ta. variable (D) y proceder a invertir los bits impares.

Se cambia el orden de las tareas para aprovechar que la máscara nos permite obtener D' fácilmente, luego determinaremos A'

mascara := mascara shl 1; // 00000000 00000000 00000000 10101010, OK

Invertimos los bits impares de D d1 := d xor mascara; // 00000000 00000000 00000000 11101101 xor

// 00000000 00000000 00000000 10101010 = // 00000000 00000000 00000000 01000111 OK

Tomar la 1ra. variable (A) e intercambiar los cuatro bits más significativos con los cuatro menos significativos: Para intercambiar los bits más y menos significativos emplearemos la siguiente regla:

0 or 0 = 0 1 or 0 = 1 ==> Bit or 0 = Bit

Page 79: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

79

0 or 1 = 1 1 or 1 = 1 ==> Bit or 1 = 1

0 and 0 = 0 1 and 0 = 0 ==> Bit and 0 = 0 0 and 1 = 0 1 and 1 = 1 ==> Bit and 1 = Bit

Formamos la máscara mascara := 1; // 00000000 00000000 00000000 00000001 mascara := mascara shl 1; // 00000000 00000000 00000000 00000010 mascara := mascara + 1; // 00000000 00000000 00000000 00000011 mascara := mascara shl 1; // 00000000 00000000 00000000 00000110 mascara := mascara + 1; // 00000000 00000000 00000000 00000111 mascara := mascara shl 1; // 00000000 00000000 00000000 00001110 mascara := mascara + 1; // 00000000 00000000 00000000 00001111, OK

// Tomamos los bits menos significativos y borramos los otros a1 := a and mascara; // 00000000 00000000 00000000 00110101 and

// 00000000 00000000 00000000 00001111 = // 00000000 00000000 00000000 00000101

Desplazamos los bits a la izquierda a1 := a1 shl 4; // 00000000 00000000 00000000 01010000

Desplazamos los bits de la máscara mascara := mascara shl 4; // 00000000 00000000 00000000 11110000

Tomamos los bits más significativos y borramos los otros aux := a and mascara; // 00000000 00000000 00000000 00110101 and

// 00000000 00000000 00000000 11110000 = // 00000000 00000000 00000000 00110000

Desplazamos los bits a la derecha aux := aux shr 4; // 00000000 00000000 00000000 00000011

Juntamos los bits más y menos significativos invertidos a1 := a1 or aux; // 00000000 00000000 00000000 01010011

Tomar la 2da. variable (B) e intercambiar los dos bits menos significativos

Tomamos los dos bits menos significativos aux := b and 3; // 00000000 00000000 00000000 00110101 and

// 00000000 00000000 00000000 00000011 (3) = // 00000000 00000000 00000000 00000001

Borramos los dos bits menos significativos mascara := 3; // 00000000 00000000 00000000 00000011 mascara := not mascara; // 11111111 11111111 11111111 11111100 b1 := b and mascara; // 00000000 00000000 00000000 00110100

Page 80: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

80

Colocamos el 2do. bit en la primera posición b1 := b1 or (aux shr 1); // 00000000 00000000 00000000 00000001 aux

// 00000000 00000000 00000000 00000000 shr 1 // 00000000 00000000 00000000 00110100 b1 // 00000000 00000000 00000000 00110100 b1 or...

Borramos el 2d. bit de la variable auxiliar y movemos el 1ro. a la segunda posición. mascara := 2; // 00000000 00000000 00000000 00000010 mascara := not mascara; // 11111111 11111111 11111111 11111101 aux := aux and mascara; // 00000000 00000000 00000000 00000001 and

// 11111111 11111111 11111111 11111101 = // 00000000 00000000 00000000 00000001

aux := aux shl 1; // 00000000 00000000 00000000 00000010

Colocamos el bit en la variable b1 b1 := b1 or aux; // 00000000 00000000 00000000 00110100 or

// 00000000 00000000 00000000 00000010 = // 00000000 00000000 00000000 00110110 OK

Formar nuevamente el número con los cambios hechos, en el orden como se piden:C'A'D'B'

c1 := c1 shl 24; // 11010000 00000000 00000000 00000000 a1 := a1 shl 16; // 00000000 01010011 00000000 00000000 d1 := d1 shl 8; // 00000000 00000000 01000111 00000000

nuevoValor := c1 or a1 or d1 or b1; // 11010000 01010011 01000111 00110110 writeln('Valor inicial: ',valor:12); writeln('Valor final: ', nuevoValor:12);

end.

Introducción al uso de archivos de texto Un archivo de textos es una colección de caracteres almacenados en la memoria secundaria del computador. La idea de emplear archivos de textos es poder almacenar información que podrá ser empleada cada vez que se ejecute el programa o por diferentes programas, esto significa que podemos almacenar en un archivo de textos los datos que vamos a introducir al programa y hacer que el programa los tome de allí sin necesidad que los tengamos que digitar cada vez que queremos ejecutar el programa. La ventaja de emplear archivos de textos es que la consola del computador ha sido diseñada de manera similar a la de un archivo de textos, de modo que introducir datos a un programa desde la consola o desde un archivo de textos es casi lo mismo, las instrucciones que utilizaremos en uno u otro caso serán muy parecidas.

Un archivo de textos se puede crear mediante instrucciones de programa, pero la forma más fácil de hacerlo es empleando un procesador de palabras, por lo general se emplea el que se proporciona en el entorno de desarrollo para escribir los programas, pero se puede usar cualquiera.

No obstante lo expuesto en los párrafos anteriores, existen algunas diferencias en el ingreso de datos por consola que por un archivo de textos, las cuales analizaremos a continuación:

Page 81: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

81

Variable de archivo:

Para poder utilizar un archivo de textos se requiere relacionar de manera lógica el archivo con el programa, la manera de hacerlo es definiendo una variable especial en el programa denominada variable de archivo la cual será conectada al mismo archivo.

La sintaxis para declarar una variable de archivo es la siguiente:

Por ejemplo:

program variableDeArchivo; var archivo: Text;

datosDeEntrada, reporte: Text; begin

… end.

Asignación de un archivo a una variable de archivo:

Una vez definida la variable de archivo, hay que asignarle la información necesaria para que la variable se conecte con el archivo físico, esta operación se realiza mediante un procedimiento denominado assign, su sintaxis se muestra a continuación:

Por ejemplo: … begin

assign(archivo, ‘datos.txt’); el nombre del archivo debe estar entre comillas simples assign(reporte, ‘c:\ejemplos\reporte.txt’); también se puede colocar la ruta

Apertura de archivos:

Luego de conectar la variable de archivo, debemos realizar una operación que nos permita poder acceder a la información que tiene el archivo o, en el caso que queramos guardar datos en un archivo, que nos permita poder escribir la información requerida en el archivo. Esta operación se denomina apertura del archivo, debe entenderse que esta operación no lee ni escribe información, solo da las condiciones necesarias para poder realizarlas.

La apertura de un archivo de textos implica el establecer el tipo de operación que se realizará con el archivo, esto es, establecer si se leerá del archivo la información que hay en él o si se escribirá información en él. En los archivos de textos estas tareas son excluyentes, esto quiere decir que se abre un archivo sólo para leer información de él o

:

,

; var Identificador text

, ( ; Variable de archivo

Nombre del archivo

) assign

Page 82: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

82

sólo para escribir en él, no se puede abrir un archivo de textos para realizar ambas operaciones, como se hacen con otro tipo de archivos.

Estas operaciones se realizan mediante tres procedimientos diferentes, los que detallamos a continuación:

1° Procedimiento reset

Este procedimiento permite abrir un archivo de modo que se puedan realizar sólo operaciones de lectura a partir del inicio del archivo. El archivo debe existir de lo contrario se producirá un error y se interrumpirá la ejecución del programa.

La sintaxis de este procedimiento es la siguiente:

Por ejemplo: … begin

… reset(archivo); …

2° Procedimiento rewrite

Rewrite permite abrir un archivo de modo que se puedan realizar sólo operaciones de escritura a partir del inicio del archivo. Si el archivo no existe lo crea, de lo contrario borra el contenido del archivo.

La sintaxis de este procedimiento es la siguiente:

Por ejemplo:

… begin

… rewrite(reporte); …

3° Procedimiento append

Permite abrir un archivo de modo que se puedan realizar sólo operaciones de escritura a partir del final del archivo, en otras palabras nos permite agregar información a un archivo. El archivo debe existir, de lo contrario se produce un error y se interrumpirá la ejecución del programa.

La sintaxis de este procedimiento es la siguiente:

Por ejemplo:

( ; Variable de archivo

) reset

( ; Variable de archivo

) rewrite

( ; Variable de archivo

) append

Page 83: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

83

… begin

… append(datosdeEntrada); …

Cierre de un archivo

Inmediatamente se termine de realizar operaciones con un archivo, éste debe cerrarse. De no hacerse se podría perder información importante del archivo. La razón de esto es que cuando se realizan operaciones de entrada o salida de un archivo, la operación no desplaza la información directamente desde el archivo hacia las variables o viceversa, lo que sucede es que en el momento de abrir el archivo para leer de él, parte de la información contenida en el archivo pasa a un espacio temporal de memoria denominado ‘buffer’, cuando se hace una operación de lectura se toman los datos del buffer y no del archivo, cuando toda la información del buffer se ha leído, nuevamente se le carga otra parte del archivo y el proceso continua. Cuando se escribe en un archivo, la información se envía al buffer, cuando el buffer se llena, se descarga la información que contiene al archivo. Si el programa se interrumpiera abruptamente antes de cerrar el archivo, el contenido del buffer no pasaría al archivo, y por lo tanto se perdería la información que contiene. Esto es un suceso crítico en operaciones de escritura, ya que cuando se lee, lo que hay en el buffer es una copia de lo que hay en el archivo así que si se interrumpiera al programa es difícil que se pierda información del archivo.

El esquema siguiente muestra este proceso:

Esta forma de manejar los datos se da para evitar demasiadas operaciones directamente sobre el disco, de no hacerlo así se perdería mucho tiempo en el proceso ya que el disco es un dispositivo mucho más lento en sus procesos que la memoria principal del computador, por otro lado se evita un desgaste innecesario del disco.

Luego de cerrar un archivo, se puede volver a abrir empleando cualquiera de los procedimientos explicados anteriormente, sin necesidad de volver a ejecutar la orden assign.

Para cerrar un archivo se emplea el procedimiento close, cuya sintaxis se muestra a continuación.

variables buffer

disco Operaciones de escritura

Memoria principal

variables buffer disco

Operaciones de lectura

Memoria principal

( ; Variable de archivo

) close

Page 84: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

84

Por ejemplo: … begin

… close(datosdeEntrada); …

Entrada y Salida de datos desde archivos de texto

Una vez abierto el archivo, se podrán realizar operaciones de escritura o de lectura, según como fue abierto, y estas operaciones se tornan muy sencillas ya que para hacerlo se emplean los mismos procedimientos que se emplean para leer o escribir datos en la consola, esto es: read, readln, write y writeln.

Estos procedimientos sólo tienen una variante cuando se trata de archivos de textos, se debe colocar como primer parámetro la variable de archivo relacionada al archivo con el que queremos trabajar, el resto de la sintaxis, así como la forma como trabaja es idéntico a la forma como se trabaja desde la consola.

La sintaxis de estos procedimientos se muestra a continuación:

Por ejemplo: … begin

… read(archivo, base, altura); writeln(reporte, ‘Área = ‘, base*altura/2); …

Ejemplos de programas que emplean archivos de textos: A continuación presentamos algunos ejemplos sencillos de problemas que se solucionarán leyendo los datos de un archivo de textos o guardando los resultados en otro archivo de textos.

1. Escriba un programa en Pascal que permita leer las coordenadas de dos puntos en un plano y que calcule la distancia entre ellos y el ángulo que forma el segmento de recta con la horizontal. Los datos deberán ser leídos de un archivo de textos.

) write/writeln expresión

tamaño de campo

,

; ( variable de archivo

) read/readln

,

; variable de

archivo ( variable

Page 85: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

85

Solución: Como se trata del mismo ejemplo que presentamos en el acápite anterior, nos centraremos en la parte de la lectura de datos, que se realizará desde un archivo de textos.

Preparación de los datos: Primero vamos a crear varios archivos de textos en donde pondremos diferentes juegos de datos que serán la fuente de ingreso.

Para realizar esto hay que cargar en el computador un editor de palabras como el bloc de notas de Microsoft®, o el mismo editor de textos donde escribe sus programas. Luego escriba en ellos un juego de datos de la manera siguiente:

ó

Luego guárdelo con el nombre de datos-t1.txt en la misma carpeta donde está escribiendo sus programas.

De igual manera cree otros dos archivos de textos (datos-t2.txt y datos-t3.txt) con juegos de datos diferentes como por ejemplo 50, 50, 200, 200 y 184, 56, 25, 159.

Programa: El programa que presentamos a continuación es el mismo que presentamos en el ejemplo del acápite anterior, sólo le agregamos las instruccionenes referidas a los archivos de textos (recuadros):

Program calculaLaDistanciaYAnguloEntreDosPuntos; var x1, x2, y1, y2, d, ang: Real;

nombreDelArchivo: String; archDatos: Text;

begin Lectura de datos: write(‘Ingrese el nombre del archivo que contiene los datos: ‘); readln(nombreDelArchivo); assign(archDatos, nombreDelArchivo); reset(archDatos); readln(archDatos, x2, y2); readln(archDatos, x2, y2); Calculamos resultados: d := sqrt( sqr(x2-x1) + sqr(y2-y1) ); ang := arcTan( (y2-y1) / (x2-x1) ) * 180 / pi; Mostramos resultados: writeln(‘La distancia entre P1 y P2 es: ‘, D:7:3); writeln(‘EL ángulo que forma el segmento con la horizontal es: ‘, ang:7:2, ‘°’); close(archDatos);

end.

Variable de archivo

Asignación, apertura para leer y lectura de

datos del archivo.

Observe que se eliminan los mensajes para el ingreso de

las coordenadas

Page 86: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

86

Resultado: Al ejecutar el programa, primero le pedirá el nombre del archivo que contiene los datos, usted deberá escribir el nombre completo del archivo (p. e.: datos-t1.txt), si el archivo de datos no se encuentra en la misma carpeta que la del programa, deberá colocar la ruta completa para poderlo ubicar (p. e.: c:\ejercicios\tp\datos-t1.txt). Inmediatamente después de haber ingresado el nombre verá los resultados en pantalla como se ve a continuación:

Usted podrá ejecutar el programa varias veces ingresando los nombres de los otros archivos de textos y obtendrá los resultados para esos datos.

2. Escriba un programa que permita ingresar el nombre de un alumno, su código y sus notas de un curso (cuatro prácticas, las notas de los exámenes 1, 2 y del examen especial). El programa deberá calcular la nota que obtendrá el alumno en el curso de manera similar al ejemplo del acápite anterior. Los datos deberán ser leídos de un archivo de textos, los resultados también serán guardados en otro archivo de textos.

Solución: de manera similar al programa anterior, nos concentraremos en la lectura de los datos y la emisión del reporte con los resultados.

Datos: Prepare tres archivos de textos con los siguientes datos:

Programa: El programa que presentamos a continuación es el mismo que presentamos en el ejemplo del acápite anterior, sólo le agregamos las instruccionenes referidas a los archivos de textos (recuadros):

program promedioDeNotas; var pr1, pr2, pr3, pr4, ex1, ex2, exE, prMin, nota: Integer;

promPr : Real; nombre, codigo, nombArchDatos, nombArchReporte: String; archDatos, archReporte: Text;

begin

PaulaRod.txt

20040107 Paula Rodríguez 18 15 17 16 15 18 -1

AnaRonc.txt

20051101 Ana Roncal 13 12 14 15 11 -1 15

NaomiGuz.txt

20071003 Naomi Guzmán 15 9 15 17 -1 16 18

Page 87: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

87

Lectura de datos: write('Ingrese el nombre del archivo con los datos: '); readln(nombArchDatos); write('Ingrese el nombre del archivo que guardará el reporte: '); readln(nombArchReporte);

assign(archDatos, nombArchDatos); assign(archReporte, nombArchReporte); reset(archDatos); rewrite(archReporte); crea un nuevo archivo

readln(archDatos, codigo); readln(archDatos, nombre); readln(archDatos, pr1, pr2, pr3, pr4); readln(archDatos, ex1, ex2, exE); Cálculos: 1) Tomamos la menor entre pr1 y pr2

prMin := ord(pr1<=pr2)*pr1 + ord(pr2<pr1)*pr2; 2) Tomamos la menor entre la anterior y pr3

prMin := ord(prMin<=pr3)*prMin + ord(pr3<prMin)*pr3; 3) Tomamos la menor entre la anterior y pr4

prMin := ord(prMin<=pr4)*prMin + ord(pr4<prMin)*pr4; promPr := (pr1 + pr2 + pr3 + pr4 - prMin)/3;

Decidimos reemplazar o no el examen especial con el examen 1 ex1 := ord(ex1<>-1)*ex1 + ord((ex1=-1) and (ex2<>-1))*exE;

Decidimos reemplazar o no el examen especial con el examen 2 ex2 := ord(ex2<>-1)*ex2 + ord(ex2=-1)*exE;

nota := round((promPr*3 + ex1*3 + ex2*4) / 10);

Mostrar resultados: writeln(archReporte, 'EL alumno: ', codigo, ' ', nombre, ' obtuvo de nota: ', nota); close(archDatos); close(archReporte);

end.

Resultado: Ejecute el programa tres veces, dándole los nombres de los diferentes archivos de texto que creó. En cada ejecución dele un nombre diferente al archivo que contendrá el reporte, luego abra los reportes y obsérvelos.

Vuelva a ejecutar tres veces el programa, dele en cada ejecución el nombre de cada uno de los archivos de datos, pero cuando le pida el nombre del archivo del reporte dele el mismo nombre en las tres ejecuciones. Cuando abra el archivo de reporte verá que en él está la repuesta sólo de la última ejecución, esto se debe a que la orden rewrite borra el contenido del archivo al abrirlo.

Por último cree un archivo de textos con el nombre de reporte.txt que esté vacío, es decir que no contenga información alguna. Luego reemplace, en el programa, la línea que

Page 88: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

88

dice rewrite(archReporte); por la orden append(archReporte); finalmente vuelva a ejecutar tres veces el programa dándole como nombre para el archivo de reporte el que creo vacío, podrá observar que el contenido del archivo será similar al que se muestra a continuación.

reporte.txt

EL alumno: 20040107 Paula Rodríguez obtuvo de nota: 17 EL alumno: 20051101 Ana Roncal obtuvo de nota: 13 EL alumno: 20071003 Naomi Guzmán obtuvo de nota: 17

Page 89: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

89

CAPÍTULO 5: Programación estructurada

Cuando en 1958 aparece el primer lenguaje de programación de alto nivel, el FORTRAN (del acrónimo FORmula TRANslation) y posteriormente el COBOL (COmmon Business-Oriented Language) en año 1960, la programación de computadoras se empezó a popularizar. Cada vez, más y más personas se introducía en el mundo de la programación de computadores y sobre todo cada vez se pretendía desarrolla programas más y más complejos.

Sin embargo, las características de estos lenguajes, aun muy cerca a la manera cómo trabaja el computador y muy alejada de la forma cómo se comunican y expresan las personas, hizo que los programas desarrollados en esa época no fueran muy claros y por el contrario eran altamente desordenados. Entre otras, existía una orden en los lenguajes denominada GOTO, esta polémica instrucción hacía que en el momento de ejecutarse se transfería el control del programa a una línea que no necesariamente fuera la que estuviera a continuación. En otras palabras, si la orden “goto 100” se encontraba en la línea 15, al ejecutarse, la siguiente orden que se ejecutaría en el programa sería la que se encontrase en la línea 100 o que tuviera como etiqueta es número. El esquema No.1 muestra cómo trabajaba esta instrucción.

El uso indiscriminado que se le dio a esta instrucción trajo consigo la proliferación de programas muy difíciles de mantener, actualizar o simplemente corregir. Para que pueda darse cuenta de esto, si observa el esquema anterior podrá ver que un grupo de instrucciones no se ejecuta en el programa, para que se puedan ejecutar habrá que poner otra sentencia GOTO que transfiera el control del programa a esa zona, por ejemplo se puede poner una instrucción GOTO 50 antes de la sentencia END.

Pero al hacer esto, la ejecución del programa seguirá línea a línea desde la sentencia que tienen la etiqueta 50 pasando por la que tiene la etiqueta 100 las cuales se volverán a ejecutar. Pero esto no es lo más grave, ya que cuando se llegue a ejecutar nuevamente la sentencia GOTO 50 el control del programa volverá a la línea que tiene la etiqueta 50, repitiéndose el proceso indefinidamente, tal como se muestra en el esquema No. 2.

GOTO 100

100

Programa

END

Se ejecutan todas las instrucciones hasta la que tiene el GOTO.

Flujo del programa

Se transfiere el control del programa a la instrucción con etiqueta 100. No se ejecutan las instrucciones intermedias.

Se continúa con la ejecución de todas las instrucciones restantes.

Esquema No.1

Page 90: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

90

Para solucionar esto se debía colocar otra sentencia GOTO una línea antes de la que contiene la etiqueta 100, que transfiera el control a la sentencia END, como se muestra en el esquema No. 3.

Durante esa época se llegó a extremos en los que cuando se encontraban errores en un programa no se tomaban la molestia de borra o modificar las líneas erróneas, sino que se hacía uso de la orden GOTO como se muestra en el esquema No. 4.

A fines de los años 60 la programación de computadoras se había vuelto caótica, cada quien programaba como mejor le parecía, muchas veces era mejor rehacer un programa desde cero en vez de tener que corregirlo o actualizarlo. Por esa época aparecen estudiosos de la computación que se dedicaron a analizar los problemas que existían en este sentido, uno de los más famosos fue el artículo publicado por Edsger W. Dijkstra, “Go To Statement Considered Harmful”.

GOTO 100

100

Programa

END

Flujo del programa

GOTO 50

50

Esquema No.2

GOTO 100

100

Programa

200 END

Flujo del programa

GOTO 50

50

Esquema No.3

GOTO 200

GOTO 200

100

Programa

150 END

Flujo del programa

= Esquema No.4

200

Page 91: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

91

Por el año 1966, los científicos Corrado Böhm y Giuseppe Jacopini formularon un teorema, que hoy lleva sus nombres, por medio del cual se sentó las bases de lo que hoy en día se conoce como “Programación estructurada”. El Teorema de Böhm-Jacopini, en forma simplificada nos dice que “Todo algoritmo propio puede expresarse en base a tres estructuras elementales que son la estructura secuencial, la estructura selectiva y la estructura iterativa.

Estas estructuras se pueden representar mediante los siguientes diagramas de flujo:

Estas estructuras están formadas por un conjunto de instrucciones que forman un bloque de sentencias, como se puede apreciar en las líneas punteadas del gráfico anterior. Dentro de las consideraciones dadas en la programación estructurada se indica que estos bloques deben tener un único punto de entrada y un único punto de salida, prohibiéndose la posibilidad de interrumpir la ejecución del bloque en un punto intermedio o de poder llegar a una instrucción del bloque por un camino que no pase por el punto de entrada. Esto eliminó la posibilidad de emplear sentencias como GOTO lo que ordenó la manera de programar.

Es a partir de esto que empiezan a aparecer lenguajes de programación que se adaptaron a esta nueva forma de programación. Es así como aparecen el Algol, el Pascal, el C, el Ada, etc.

Estructura Secuencial El efecto de una estructura secuencial se aprecia cuando se encuentran una serie de sentencias que se ejecutan una a continuación que la otra desde la primera hasta la última. Todos los programas que se desarrollaron en el capítulo anterior se basan en una estructura secuencial. Es por esto que se considera la estructura más simple de las tres, por esto no desarrollaremos más ejemplos en este sentido y más bien pasaremos a explicar las otras estructuras.

Estructura Selectiva La estructura selectiva nos permite elegir entre dos caminos, mutuamente excluyentes, en el flujo de un programa. Para esto, la instrucción que implementa esta estructura, como primer paso debe evaluar una expresión que en la mayoría de casos es una

Estructura Secuencial Estructura Selectiva Estructura Iterativa

Page 92: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

92

expresión lógica (sin embargo en lenguajes como el C simplemente se evalúa una expresión). El resultado de esta expresión nos indicará cuál es el camino que seguirá el flujo del programa, si el resultado es “verdadero” o “true” se seguirá por un camino, en caso contrario si el resultado es “falso” o “false” se seguirá por el otro camino. En el caso del C el camino se decide si el resultado es cero (que equivale al “falso”) o diferente de cero.

En Pascal la estructura selectiva se implementa mediante la instrucción denominada IF…THEN…ELSE, cuya sintaxis se presenta a continuación:

Un camino en esta instrucción se sigue al ejecutar el bloque de instrucciones que está a continuación de la palabra reservada then, que se toma en el caso que el resultado de la expresión lógica sea true, y el otro camino está a continuación de la palabra reservada else. Si observa la sintaxis verá que tanto la palabra reservada else como el bloque de instrucciones que le siguen están en una zona opcional dentro de la instrucción, eso quiere decir que si se desea se les puede obviar, por lo tanto la instrucción IF…THEN…ELSE también puede servir para que en un programa se opte por ejecutar un grupo de instrucciones o no.

Algunos ejemplos de cómo emplear esta instrucción se muestran a continuación:

if a > b then writeln (“Error en los datos”);

Aquí, si el valor que contiene la variable a es por ejemplo 3 y el valor que contiene b fuera de 1, entonces al ejecutarse la orden anterior será verdadero que a es mayor que b por lo tanto se mostrará en la pantalla el mensaje “Error en datos”, pero si por el contrario la variable a tuviera 3 y la variable b tuviera 8 no se ejecutaría la orden writeln y se continuaría ejecutando las siguientes instrucciones que le siguen al if en el programa.

if a > b then writeln (“Error en los datos”) else c := sqrt(b-a);

En esta orden se trata de calcular la raíz cuadrada de b-a, sin embargo sabemos que si se intenta obtener la raíz cuadrada de un número negativo se produce un error en el programa y se interrumpe éste. Para evitar esto colocamos una orden condicional de modo que se seleccione camino a seguir dependiendo de los valores de las variables, de este modo se evita ejecutar una orden que produzca un error crítico en el programa.

if a > b then writeln (“Error en los datos”) else

begin c := sqrt(b-a); writeln(“El resultado es: “, c:8:2);

end;

Bloque de Instrucciones

Bloque de Instrucciones

if then

else

Expresión lógica

Page 93: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

93

Este caso es similar al anterior, pero aquí se quiere ejecutar dos instrucciones para el caso que a no sea mayor que b. Las cláusulas then y else sólo pueden influenciar sólo a la instrucción que le sigue por eso para que su influencia llegue a más de una sentencia, éstas deben ser enmarcadas en un bloque que comience con la palabra reservada begin y que termine con la palabra reservada end como se ve en el ejemplo anterior.

Así, se puede escribir una porción de código como en el ejemplo siguiente:

if a > b then begin

writeln (“Error: a debe ser menor que b, ingréselos nuevamente:”); readln(a, b);

end else begin

c := sqrt(b-a); writeln(“El resultado es: “, c:8:2);

end;

Ejemplos de programas que emplean la estructura selectiva: 1. Se desea escribir un programa que permita calcular cuánto debe pagar por impuesto a

la renta un ciudadano según la cantidad de dinero que ganó en un año. El impuesto se debe calcular según la siguiente tabla:

Renta Neta Global Tasa Hasta 27 UIT 15% Por el exceso de 27 UIT y hasta 54 UIT 21% Por el exceso de 54 UIT 30%

Sabiendo que este año una UIT tiene un valor de S/. 3,550.00

Solución: Este es un problema típico en el que se emplean estructuras selectivas en su solución. Sin embargo la solución que se pueda plantear no es única, se puede establecer varios caminos con los que se obtengan la misma respuesta. Para que se pueda apreciar esto, se planteará dos formas de presentar el programa que de solución al problema planteado, el primero se empleará varias instrucciones IF sin la cláusula ELSE, y en la segunda se empleará una técnica que se denomina de “anidación”.

Datos: Se ingresará como dato la cantidad de dinero en nuevos soles que un ciudadano ganó en un año. El valor de la UIT es una cantidad que no cambia muy a menudo, por lo general cambia una vez al año, por lo que no tiene sentido que lo ingresemos como dato cada vez que se ingrese el monto ganado por un ciudadano por lo tanto lo tomaremos como una constante del programa, cuando este valor cambie tendremos que modificar el valor en el programa y compilarlo nuevamente13. Otros datos que se deben considerar son los valores de los porcentajes y los límites dados, estos datos

13 Otra solución puede ser colocar el valor de la UIT en un archivo de texto y leerlo cada vez que se ejecute el programa, así que cada vez que cambie su valor, sólo tendríamos que modificar el archivo. Esta solución queda como ejercicio para el lector.

Page 94: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

94

son fijos y se podrían manejar como valores constantes, pero podrían cambiar en algún momento, por esto también los tomaremos como constantes.

Resultado: El impuesto que debe pagar el ciudadano.

Tareas que se han de realizar en el programa:

- Ingresar la cantidad ganada en el año. - Verificar si la cantidad es menor o igual a 27 UIT, si es así el impuesto será el

15% de la cantidad ingresada. - Verificar si la cantidad está entere 27 UIT y 54 UIT, si es así el impuesto se

calculará, ya que se indica “por exceso de..”, de la siguiente manera: 27 UIT x 15% + (la cantidad – 27 UIT) x 21%

- Verificar si la cantidad es mayor que 54 UIT, si es así el impuesto se calculará como a continuación se indica:

27 UIT x 15% + (54 UIT – 27 UIT) x 21% + (la cantidad – 54 UIT) x 30%

Programa: Como se indicó se presentarán dos programas que solucionene el problema.

1) program calculaElImpuestoALaRentaDeUnCiudadano; Esta solución se presenta empleando la instrucción if varias veces sin emplear la cláusula else

const UIT = 3550.0; se debe tomar como un valor real PORCENT_1 = 15.0; LIMITE_1 = 27.0; PORCENT_2 = 21.0; LIMITE_2 = 54.0; PORCENT_3 = 30.0;

var cantidadGanada, impuesto: Real;

begin write('Ingresa la cantidad que ganó en el año: '); readln(cantidadGanada);

if cantidadGanada <= LIMITE_1 * UIT then impuesto := cantidadGanada * PORCENT_1/100;

if (cantidadGanada > LIMITE_1 * UIT) and (cantidadGanada <= LIMITE_2 * UIT) then

impuesto := LIMITE_1 * UIT * PORCENT_1/100 + (cantidadGanada - LIMITE_1 * UIT) * PORCENT_2/100;

if cantidadGanada > LIMITE_2 * UIT then impuesto := LIMITE_1 * UIT * PORCENT_1/100 +

(LIMITE_2 - LIMITE_1) * UIT * PORCENT_2/100+ (cantidadGanada - LIMITE_2 * UIT) * PORCENT_3/100;

Observe que de las tres instrucciones if que aparecen, sólo una expresión lógica dará verdadero, por lo tanto una sola vez se calcular el impuesto.

writeln( 'El ciudadano debe pagar: S/. ', impuesto:10:2, 'de impuesto a la renta.');

end.

Page 95: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

95

2) program calculaElImpuestoALaRentaDeUnCiudadano; Esta solución se presenta empleando la instrucción if en forma anidada

const UIT = 3550.0; se debe tomar como un valor real PORCENT_1 = 15.0; LIMITE_1 = 27.0; PORCENT_2 = 21.0; LIMITE_2 = 54.0; PORCENT_3 = 30.0;

var cantidadGanada, impuesto : Real;

begin write('Ingresa la cantidad que ganó en el año: '); readln(cantidadGanada);

if cantidadGanada <= LIMITE_1 * UIT then impuesto := cantidadGanada * PORCENT_1/100

else if cantidadGanada <= LIMITE_2 * UIT then

impuesto := LIMITE_1 * UIT * PORCENT_1/100 + (cantidadGanada - LIMITE_1 * UIT) * PORCENT_2/100

else impuesto := LIMITE_1 * UIT * PORCENT_1/100 +

(LIMITE_2 - LIMITE_1) * UIT * PORCENT_2/100+ (cantidadGanada - LIMITE_2 * UIT) * PORCENT_3/100;

Aquí se observa lo que se denomina un if anidado (un if dentro de otro if). Si la cantidad es menor o igual al primer límite, se calcula el impuesto con la primera fórmula y no se ejecutan las sentencias que le siguen al else. Por el contrario si la cantidad es mayor que ese límite se ejecuta el segundo if, aquí las expresiones lógicas se simplifican debido a que si se llega a ese punto la cantidad no puede ser menor que el primer límite.

writeln('El ciudadano debe pagar: S/. ', impuesto:10:2, ' de impuesto a la renta.'); end.

2. Escriba un programa que permita hallar las raíces de una ecuación de segundo grado, como se muestra a continuación: ax2 + bx + c = 0

Datos: Se ingresará como dato los coeficientes a, b y c.

Resultados: Se deberá obtener, de ser posible, ambas raíces de la ecuación, sea el valor que sea.

Solución: Apliquemos una tormenta de ideas para determinar la solución del problema.

Lo primero que se nos viene a la mente cuando vemos la ecuación de segundo grado es la fórmula para determinar sus raíces y esta es: ( )

aacbbxxraíces

24,

2

21−±−

= Si es cierto que esta fórmula nos da las raíces de la ecuación, no se pude aplicar siempre, por un lado si el valor del coeficiente “a” fuera cero no se puede hacer la división, y por otro lado si el radical acb 42 − nos diera un valor negativo, la raíz cuadrada nos daría un valor complejo o imaginario, y muchos lenguajes de

Page 96: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

96

programación no soportan el manejo de números complejos, por lo que debemos analizar estos casos para dar una respuesta adecuada.

Si la variable “a” tiene un valor cero, la ecuación quedaría de la siguiente manera:

Por lo tanto nos encontramos en el caso que la ecuación no es de segundo grado, la

raíz por consiguiente será bcxraíz −=)( . Aquí encontramos la posibilidad que también

la variable “b” tenga un valor de cero cuando simultáneamente la variable “a” tiene el valor de cero, en este caso nos encontraríamos ante la expresión c = 0, la cual no es una ecuación, por lo tanto no se puede determinar una raíz.

En el caso que el valor de la variable “a” no sea cero y el radical acb 42 − sea negativo, como se indicó, las raíces serían valores complejos y el resultado se tiene

que expresar en estos términos: ( ) ia

bacabxxraices

24

2,

2

21−

±−

=

Esta solución, producto de una tormenta de ideas da solución al problema, pero es muy desordenada, lo que viene ahora es ordenar estas ideas de modo que podamos encajarlo en un programa claro y sencillo. Se empezará verificando el valor de la variable “a”, según sea el caso se verificará el valor de la variable “b” o del radical para dar una respuesta en el programa.

Programa: El programa resultante se muestra a continuación:

program raicesDeEcuacionDe2doGrado; var a, b, c, radical, raiz1, raiz2, parteReal, parteImag: Real; begin

write(‘Ingrese los coeficientes a, b, c: ’); read(a, b, c); if a = 0 then begin

if b = 0 then writeln(‘No es una ecuación de segundo grado’) else begin

writeln(‘La ecuación es de primer grado:’); raiz1 := -c/b; writeln(‘X = ’, raiz1:8:3);

end end

else begin radical := b*b – 4*a*c; if radical >= 0 then begin

raiz1 := (-b + sqrt(radical))/2/a; raiz2 := (-b - sqrt(radical))/2/a; writeln( ‘X1 = ‘, raiz1:8:3, #13,#10, ‘X2 = ‘, raiz2:8:3);

end else begin

parteReal := -b/2/a; parteImag := sqrt(-radical)/2/abs(a);

ax2 + bx + c =0 ⇒ bx + c = 0 0

Page 97: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

97

writeln( ‘X1 = ‘, parteReal:8:3,’ +’, parteImag:8:3,’i’); writeln( ‘X2 = ‘, parteReal:8:3,’ -’, parteImag:8:3,’i’);

end end

end.

Estructura Iterativa Con la estructura iterativa se busca repetir la ejecución de un grupo de instrucciones varias veces.

Las iteraciones en los programas es algo muy común, casi todos los algoritmos requieren de la repetición de la ejecución de grupos de instrucciones para conseguir sus objetivos, sin embargo se debe tener en cuenta que un algoritmo no puede aceptar que un grupo de instrucciones se ejecute de manera repetitiva un número infinito de veces, toda iteración debe llegar a un fin en algún momento. Por lo tanto cuando se itera en un programa se debe colocar un control que permita dar por terminada la iteración en algún momento. Este control se puede colocar al inicio de las sentencias que se iterarán, en cuyo caso se le denominará “iteración o bucle con entrada controlada”, o se puede colocar al final, denominándosele “iteración o bucle con salida controlada”. La figura siguiente muestra estos conceptos:

También, en los diferentes lenguajes de programación se implementan otras formas de iteración, como aquellas que controlan la salida mediante un contador, en cada iteración se incrementa o decrementa un índice, cuando ese índice llega a un límite se terminas la iteración.

Las iteraciones también se pueden controlar en un punto intermedio del bloque de instrucciones, esto es una práctica común sin embargo no se conlleva con los lineamientos de la programación estructurada. A pesar de esto, en algunos casos esta forma de iterar puede hacer más claro el código de un programa por lo que lo presentaremos más adelante.

A continuación se describen las diferentes formas en que se presentan las estructuras iterativas:

Bucle con entrada controlada Bucle con salida controlada

Page 98: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

98

Bucle con entrada controlada

Esta estructura se implementa en la mayoría de lenguajes de programación mediante una instrucción denominada WHILE…. La idea es que mientras una condición que es evaluada al inicio de un bloque de instrucciones dé cómo resultado un valor verdadero, el boque de instrucciones se volverá a ejecutar, en el momento que el resultado de la expresión dé un valor falso se dará como finalizada la iteración.

En Pascal, la sintaxis de la instrucción WHILE… es como a continuación se muestra:

Un ejemplo de cómo emplear esta instrucción se muestra a continuación:

cálculo de los diez primeros valores enteros entero := 0; suma := 0; while entero < 10 do begin

inc(entero); suma := suma + entero;

end;

Allí se puede apreciar cómo se debe inicializar las variables que intervienen en la condición que controla la salida del bucle, de modo que la condición se cumpla y así se pueda empezar a iterar. En este caso se asigna el valor de cero a la variable entero de modo que la expresión entero < 10 de cómo resultado verdadero. Luego, dentro de bloque de instrucciones, se deben modificar los valores de las variables que intervienen en la expresión de control de modo que la iteración llegue a su fin en algún momento, en este caso se coloca la instrucción inc(entero).

Ejemplos de programas que emplean la instrucción while: 1. A usted le regalan una pareja de conejos recién nacidos (una hembra y un macho). Los

conejos sólo pueden aparearse cuando llegan a ser adultos, y esto se da cuando cumplen un mes de vida. Si el tiempo de gestación es de 1 mes, recién al final del segundo mes la hembra puede parir nuevos conejos. Suponiendo que los conejos no mueren y cada hebra produce una pareja de conejos (una hembra y un macho) cada mes a partir de su segundo mes de vida. ¿Cuántas parejas de conejos tendremos al final del primer año?, ¿Cuántas a los 6 meses?, etc.…

Datos: Se ingresará como dato el número de meses para los que se quiere hacer el control.

Resultados: Se deberá obtenerla cantidad de parejas que se tendrá al final del período.

Expresión lógica

Bloque de Instrucciones while do

Page 99: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

99

Solución: En la figura siguiente se puede apreciar cómo irá creciendo la población de conejos en términos de parejas. Allí se aprecia con una letra P de color rojo, las parejas recién nacidas o que no han llegado a ser adultas, y en color verde las parejas adultas. Las flechas azules indican el nacimiento de una pareja de conejos

Note que la cantidad de conejos se incrementa siguiendo las reglas de la famosa serie de Fibonacci. Un término de esta serie se calcula sumando los dos términos anteriores al que se quiere calcular, esto es fibonacci(n) = fibonacci(n-1) + fibonacci(n-2).

De acuerdo a esto se tiene que:

Mes 0 1 2 3 4 5 6 7 8 9 10 11 … Número de parejas 1 1 2 3 5 8 13 21 34 55 89 144 …

El algoritmo que se emplee para solucionar este problema debe contemplar el hecho que no se puede definir una variable por cada término de la serie, en primer lugar porque el usuario puede solicitar cualquier término de la serie. Lo que se hace en estos casos es tratar de manejar un número pequeño de variables que guarden los últimos valores de la serie en un momento determinado, esto debido a que un término de la serie se calcula en función de los dos términos anteriores.

En otras palabras si tenemos que para el mes 0 el valor de la serie es 1 (M (0) = 1) y para el mese 1 el valor de la serie es 1 (M (1) = 1) podemos calcular el valor de la serie para el mes 2 como M(2) = M(1) + M(0) = 2.

Para el mes 3 no se requiere del mes 0 ya que M(3) = M(2) + M(1) = 2 + 1 = 3

Para el mes 4 no se requiere del mes 0 ni del mes 1: M(4) = M(3) + M(2) = 3 + 2 = 3

Y así sucesivamente…

Por lo tanto sólo se requiere de tres variables para determinar un valor de la serie, sea cual sea el término que se desea calcular. La razón para esta conjetura es que una variable se puede inicializar con el primer término de la serie, la segunda con el segundo término de la serie, la tercera variable servirá para calcular el siguiente término de la serie (en este caso el tercero), esto es sumando la primera con la

Mes 0 ⇒ 1 pareja P

P

P P

P P P

P P P P

P P

P

P P P P P P

Mes 1 ⇒ 1 pareja

Mes 2 ⇒ 2 parejas

Mes 3 ⇒ 3 parejas

Mes 4 ⇒ 5 parejas

Mes 5 ⇒ 8 parejas

Page 100: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

100

segunda variable. Para calcular el siguiente término de la serie (el cuarto), podemos pasar el segundo término de la serie a la primera variable (ya que no nos interesa el primer término de la serie) y el termino tres calculado, lo pasamos a la segunda variable, luego el cuarto término de la serie lo calculamos sumando la primera con la segunda variable y lo almacenamos en la tercera variable.

Nótese que en este punto el cálculo de la serie ya se vuelve monótono, esto es, se pasa el contenido de la segunda variable a la primera y el contenido de la tercera en la segunda variable y se asigna a la tercera la suma de las otras dos. Esto es una iteración controlada por el número de meses o términos que se desea calcular en la serie. A continuación se muestra este algoritmo en un programa.

Programa: El programa resultante se muestra a continuación: program serieFibonacci; o número de parejas de conejos var fib, fib_2, fib_1, mes, numMeses: Longint; begin

write('Ingrese el número de meses: '); readln(numMeses); if numMeses < 2 then writeln('El número de parejas es: 1') else begin

fib_1 := 1; inicializamos las variables fib_2 := 1; mes:=2; while mes <= numMeses do begin

fib := fib_2 + fib_1; fib_1 := fib_2; fib_2 := fib; inc(mes);

end; writeln('El número de parejas es: ', fib);

end end.

2. Escriba un programa que permita imprimir un número, dado en base 10, en otra base mayor o igual a 2.

Datos: Se ingresará como dato un número en base 10 y la base en la que se quiere convertir el número.

Resultados: Se imprimirá el número dado en la base solicitada de una manera sencilla, que se pueda entender fácilmente sin que el usuario deba realizar algún proceso.

Solución: En el párrafo anterior se enfatiza que el resultado no debe llevar al usuario tratar de interpretar el resultado, esto es que no debe realizar ningún proceso para entender el resultado. La razón para esto es que la solución no puede ir por el lado fácil para el programador, se sabe que para cambiar un número a otra base sólo hay que dividir el número sucesivas veces entre la base, e ir tomando cada residuo de la división, estos residuos son las cifras del número en la base deseada. Sin embargo si aplicamos este algoritmo en un programa el resultado será la impresión del número al

Page 101: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

101

revés. Por otro lado, al igual que en el problema anterior, no se puede pretender guardar cada cifra en una variable porque no se sabe cuántas cifras tendrá la solución. Finalmente cuando la base solicitada fuese mayor a 10, los valores de los residuos obtenidos en cada división pueden ser mayores que 9 por lo que el programa los debe convertir a caracteres (10 → A, 11 → B, etc.).

Para obtener las cifras en el orden correcto, se empezará por determinar un factor que sea el mayor número que sea una potencia de la base, pero que no sea mayor que el número que se quiere convertir. Por ejemplo si se quiere convertir el número 247 a la base 2, se determinan las potencias de 2: 1, 2, 4, 8, 16, 32, 64, 128, 256, etc. y se toma el valor 128 como factor. Luego se divide el número entre el factor (247 ÷ 128) obteniéndose como cociente 1 y como residuo 119. El cociente es la primera cifra del número 247 en base 2. A continuación se toma el residuo y se divide entre la potencia de 2 inmediatamente inferior a la que determinamos como factor, en este caso 64. El cociente será la segunda cifra que buscamos. El proceso se repite hasta que se tome como factor el valor de 1. Al final se obtiene el numero 1110111.

En el caso que se elija una base mayor a 10 el proceso es el mismo, con la salvedad que los cocientes podrán salir mayores a 9 por lo que se debe reemplazar este valor por una letra. Por ejemplo si el número 247 se quiere convertir a base 16, se determina como factor (1, 16, 256, etc.) el número 16, al dividirlo el número entre este factor se obtiene como cociente 15 y como residuo 7, por lo que el 15 debe convertirse a la letra F. Finalmente se obtendrá como resultado el valor F7.

Programa: El programa resultante se muestra a continuación: program cambioDeBase; var num, factor: Longint;

base, cifra: Integer; begin

write ('Ingrese un número en base 10: '); readln (num); Ingresamos la base verificando que ésta sea mayor o igual a 2 base := 0; while base <2 do begin

write ('La base a la que lo quiere convertir: '); readln (base);

end; Se determina el factor, el proceso obtendrá un valor por encima del deseado factor := 1; while factor<=num do factor:= factor*base; Imprimimos cada una de las cifras en orden while (factor div base) <> 0 do begin

factor:=factor div base; cifra := num div factor; if cifra <10 then write(cifra) else write(chr(cifra-10+ord('A'))); num := num mod factor

end;

Page 102: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

102

writeln; end.

Bucle con salida controlada

En este caso, a diferencia de la instrucción WHILE…, el bucle con salida controlada se implementa empleando una sintaxis muy diferente entre los lenguajes de programación, así por ejemplo en Pascal se emplea la orden REPEAT…UNTIL…, en C, C++ y Java se emplea la orden do…while…, en Basic la orden se da con la sentencia DO…LOOP. En este caso la palabra reservada REPEAT (o DO en el caso de los otros lenguajes) sólo define el inicio del bloque que se va a iterar, aquí no vemos restricción alguna para ingresar y ejecutar el bloque de instrucciones. Es por esto que, a diferencia del WHILE, el bloque siempre se ejecutará por lo menos una vez. Luego de ejecutar el bloque de instrucciones se evalúa una condición, en el caso de la orden REPEAT esta condición está precedida por la cláusula UNTIL que indica que la iteración se repetirá hasta que la condición dé cómo resultado un valor verdadero. En el caso del C, Java, etc. la iteración se repetirá mientras la condición de cómo resultado un valor verdadero, en el caso del Basic se pueden emplear cualquiera de las dos alternativas.

A continuación mostramos la sintaxis de la instrucción REPEAT…UNTIL…dada en al lenguaje Pascal:

Un ejemplo de cómo emplear la instrucción REPEAT…UNTIL… se muestra a continuación:

cálculo de los diez primeros valores enteros Compara con la versión hecha con while entero := 0; suma := 0; repeat

inc(entero); suma := suma + entero;

until entero >= 10;

Observe que allí, la inicialización de las variables son necesarias para que el proceso de cálculo se dé correctamente, sin embargo si se diera un valor inicial de 10 a la variable entero, las órdenes inc(entero); y suma := suma + entero; se ejecutarán, a diferencia de WHILE, en donde no se ingresaría al bloque de iteración.

Ejemplos de programas que emplean la instrucción repeat: 1. Como sabemos de los capítulos anteriores, Pascal no tiene un operador que permita

elevar un número a una potencia dada u obtener la raíz n_ésima de un número. Este ejemplo permitirá calcular la raíz n_ésima de un número ( n Q en donde n será entero) empleando un proceso iterativo, en particular emplearemos una variante del método de Newton para determinar raíces de una ecuación. La raíz n_ésima de un número puede ser determinada empleando la siguiente expresión:

Bloque de instrucciones

until Expresión lógica repeat

Page 103: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

103

( ) Ε∈

+−= − ndonde

XQXn

nX nn 1

0011

El proceso consiste en dar un valor cualquiera a X0 y determinar mediante la expresión el valor de Xn. Si X0 es igual a Xn se habrá obtenido la raíz, de lo contrario se reemplaza el valor de X0 por el valor obtenido para Xn y se vuelve a calcular la expresión. El proceso se repite hasta llegar a la convergencia.

Datos: Se ingresará como dato el número del que se quiere obtener la raíz (Q) y el valor de la raíz (n).

Resultados: Se deberá obtenerla el valor de n Q .

Solución: La explicación de la solución prácticamente se detalló en el planteamiento del problema, por lo que en esta parte se analizarán algunos de los problemas que se pueden presentar en la implementación del problema.

Lo primero que hay que tomar en cuenta es que la iteración debe terminar cuando X0 se igual a Xn, sin embargo esto no puede darse en un programa debido a que ambas variables tendrán que ser de un tipo de dato numérico de punto flotante y, como vimos en los primeros capítulos, la representación de un número de punto flotante en la memoria del computador no dará un valor exacto. Esto quiere decir que si empleamos una expresión para calcular un número y el resultado debería dar por ejemplo 3, en la memoria del computador se puede estar almacenando el valor 2.999999, pero si por otro lado volvemos a evaluar la misma expresión empleando otros valores, pero la respuesta debiera volver a dar 3, es posible que se almacene en la memoria el valor de 3.000001. Para una persona común 2.999999 es prácticamente igual a 3.00001 sin embargo para el computador no lo es, es suficiente que un bit sea diferente para que la respuesta sea un valor falso. Es por esta razón que si colocamos en un programa un expresión como until X0 = Xn; es probable que el programa entre en una iteración infinita.

Para solucionar este problema debemos establecer cuán precisa debe ser nuestra respuesta, esto es si queremos una precisión de 3, 4, 5 etc. dígitos decimales, una vez determinado esto, calculamos el error máximo permitido (para 3 dígitos decimales el error será 0.001, para 4 será 0.0001, etc.), luego restamos los dos valores (X0 - Xn) y si la diferencia diera un valor menos al error permitido se habrá encontrado la raíz buscada.

En segundo lugar, vamos a tener que evaluar un exponente (X0n-1), debido a que el

exponente es un número entero, lo calcularemos por multiplicaciones sucesivas.

Finalmente se presenta el problema de la inicialización de las variables. En el planteamiento del problema se dice que primero debemos dar un valor a X0, con ese valor calculamos Xn, si seguimos rigurosamente esto vamos a encontrarnos con un problema al emplear una iteración con salida controlada, veamos esto en el siguiente pseudo código:

X0 ← 1 (el enunciado dice cualquier valor) Repetir

Xn ← f(X0) (f es la expresión para calcular Xn en función de X0)

Page 104: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

104

… aquí tendríamos que colocar una condicional que diga que si X0 es diferente de Xn, reemplazar el valor de X0 por el valor de Xn

(*). Hasta que X0 sea similar a Xn.

Allí podemos apreciar una incongruencia, si reemplazamos en valor de X0 por el valor de Xn (*) dará como resultado que ambas variables tendrán el mismo valor, por lo tantos se habrá llegado a la condición de salida de ciclo iterativo, esto se producirá en la primera iteración sin haberse hallado la raíz.

Por eso que, para solucionar el problema, se sugiere cambiar ligeramente el proceso. A continuación presentamos es pseudo código sugerido (en rojo se indicarán los cambios):

Xn ← 1 (el enunciado dice cualquier valor) Repetir

X0 ← Xn Xn ← f(X0) (f es la expresión para calcular Xn en función de X0)

Hasta que X0 sea similar a Xn.

En el proceso se aprecia que en lugar de inicializar X0, se inicializa Xn. Luego, la primera instrucción dentro del ciclo iterativo es reemplazar en valor de X0 por el valor de Xn para luego calcular el nuevo valor de Xn. En ese punto, en las primeras iteraciones los valores de X0 y Xn serán sustancialmente diferentes a la hora de evaluar la salida del bucle, lo que resultará en una nueva iteración. Finalmente el proceso concluirá satisfactoriamente.

Programa: El programa resultante se muestra a continuación:

program deteminacionDelaRaiz_n_esima; const PRECISION=0.00001; var n, n1: Integer;

q, x0, xn, exp: Real; begin

write ('Ingrese los valores de n y Q: '); readln(n, q); Xn := 1; repeat

x0 := xn; Aquí calculamos el exponente exp := 1; n1 := 0; repeat

exp := exp*x0; inc(n1);

until n1 = (n-1); Aquí ya podemos calcular Xn xn := ( (n-1)*x0 + q/exp)/n;

until abs(x0-xn) < PRECISION; writeln('El resultado es : ',x0:10:4);

end.

Page 105: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

105

Variante: Se puede agregar al programa la verificación que los valores ingresados sean positivos, para esto el ingreso de datos podría modificarse como se indica a continuación:

repeat write ('Ingrese los valores de n y Q: '); readln(n, q);

until (n>0) and (q>0);

Observe que no hay que dar valores iniciales a las variables.

Otras Estructuras Los diferentes lenguajes de programación definen otras estructuras iterativas y selectivas con la finalidad de hacer más fácil la codificación de un programa, sin embargo si es cierto que el concepto de estas estructuras es similar, la implementación que se hace en cada lenguaje de programación es muy diferente. Las estructuras que se estudiarán en este punto son la iteración con contador implícito y la selección múltiple.

Bucle con contador implícito

La idea de esta estructura es la de tener una variable a la que se le pueda dar un valor inicial y que se incremente automáticamente en cada ciclo, cuando su valor llegue a un tope preestablecido el ciclo iterativo concluye. En Pascal se emplea la instrucción FOR…TO/DOWNTO…DO, en C/C++, Java y C# se emplea un instrucción parecida denominada for(…,…,…), Basic utiliza la instrucción For…To…Step…. Todas con diferencias sustanciales.

La sintaxis de este bucle en lenguaje Pascal se muestra a continuación:

Esta instrucción trabaja con una única variable denominada índice, esta variable debe ser de tipo ordinal (Integer, Char, Boolean, etc.), no permite variables de tipo real ni cadenas de caracteres. Al final de cada ciclo se incrementa en una unidad el valor de índice (si se emplea la cláusula TO) o se decrementa en una unidad (de usar DOWNTO). Cuando la variable sobrepasa el valor del límite da por concluida la iteración.

En los lenguajes C, C++, Java y C# el funcionamiento es diferente, se puede emplear más de una variable índice, y estas pueden ser de cualquier tipo. Otra diferencia es que la instrucción no incrementa ni decrementa automáticamente sino que al final de cada ciclo ejecuta la sentencia que se coloca expresamente en la instrucción for, y ésta no tiene porqué ser un incremento o decremento.

El For de Basic, permite el uso de cualquier tipo de dato numérico para la variable índice, que debe ser única, pero si se ejecuta un incremento o decremento obligatorio al final de cada ciclo, dependiendo del valor colocado en la cláusula STEP.

for Inicialización del índice

downto

to

Límite do Bloque de

instrucciones

Page 106: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

106

Algunos ejemplos del uso de de esta instrucción se presentan a continuación:

1) for x:= 1 to 10 do writeln(x, ’^2 = ‘, sqr(x));

Este ejemplo muestra los valores del 1 al 10 acompañados sus respectivos cuadrados, el resultado será similar al cuadro siguiente:

1^2 = 1 2^2 = 4 3^2 = 9 4^2 = 16 5^2 = 25 6^2 = 36 7^2 = 79 8^2 = 64 9^2 = 81 10^2 = 100

2) for x:= 10 downto 1 do writeln(x, ’^2 = ‘, sqr(x));

Este segundo ejemplo es similar al anterior pero la impresión se hace al revés. 10^2 = 100 9^2 = 81 8^2 = 64 7^2 = 79 6^2 = 36 5^2 = 25 4^2 = 16 3^2 = 9 2^2 = 4 1^2 = 1

3) for letra:= ‘A’ to ‘Z’ do writeln(‘ASCII de ‘, letra, ’ = ‘, ord(letra));

Aquí el índice es una variable de tipo char y se pretende imprimir las letras mayúsculas acompañadas de su código ASCII.

ASCII de A = 65 ASCII de B = 66 ASCII de C = 67 ASCII de D = 68 … ASCII de X = 88 ASCII de Y = 89 ASCII de Z = 90

Selección múltiple

Cuando se emplea la instrucción IF sólo se pueden seguir dos caminos diferentes, con la selección múltiple se pretende tener la posibilidad de seguir varios caminos. Un esquema en diagrama de flujo se muestra a continuación:

Page 107: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

107

A igual que con la instrucción FOR, la implementación de esta estructura es diferente en los lenguajes de programación, el lenguaje Pascal emplea la cláusula CASE…OF, C, C++, Java y C# emplean la orden switch, mientras que en Basic se emplea la instrucción SELECT CASE.

La sintaxis para la orden CASE…OF se muestra a continuación:

Esta instrucción empieza con la evaluación de una expresión de tipo ordinal, el resultado de esta evaluación va a ser buscado en las diferentes listas o rangos de datos. Si se encuentra, se ejecuta el bloque ligado a ella y se da por terminada la selección. Si no se encuentra y existe la cláusula else entonces se ejecuta ese bloque, de lo contrario se ignora la sentencia case y se continua con la ejecución del programa.

A continuación se muestran algunos ejemplos del manejo de la instrucción CASE…OF:

1) El siguiente programa lee las notas de un alumno y le da una calificación cualitativa. program notaCualitativa; var p1, p2, p3, ex1, ex2: Integer; begin

write('Ingrese Notas del Alumno : '); readln(p1, p2, p3, ex1, ex2); case round(((P1+P2+P3)/3+2*Ex1+3*Ex2)/6) of

0, 1, 2: writeln('Pésimo'); 3, 4, 5, 6: writeln('Muy Mal'); 7, 8, 9, 10: writeln('Mal'); 11, 12: writeln('Regular'); 13, 14, 15: writeln('Bien'); 16, 17, 18: writeln('Muy Bien'); 19, 20: writeln('Excelente'); else writeln('Error en Datos');

end end.

Selección múltiple

?

Expresión ordinal

else

end : Bloque de instrucciones

Bloque de instrucciones

Lista o rango de datos

of case

Page 108: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

108

En este ejemplo se ingresa como dato las notas de 3 prácticas y dos exámenes, luego el programa ingresa a la instrucción SELECT CASE, se evalúa el promedio del alumno, para luego, buscar el resultado en las listas de la orden, al encontrarlo se imprime un mensaje con el calificativo cualitativo.

Así si se ingresa como dato los valores 12 16 19 18 17 se imprimirá el mensaje “Muy Bien” ya que al calcular el promedio se obtiene el valor 17.

2) El siguiente programa simula el uso de una calculadora elemental que sume, reste, multiplique y divida.

program calculadoraElemental; var num1, num2: Real;

operador: char; begin

write('Dato : '); readln(num1); repeat

write('Operación : '); readln(operador); if operador <> '=' then begin

write('Dato : '); readln(num2);

end; case operador of '+': num1 := num1 + num2; '-': num1 := num1 - num2; '*','x', 'X’: num1 := num1 * num2; '/',’÷’: num1 := num1 / num2; end; writeln('-----------------------'); writeln(num1:21:3);

until operador = '=' end.

Al ejecutar el programa usted observará algo similar a lo siguiente: Dato : 34.5 Operación : * Dato : 2.41 ----------------------- 83.145 Operación : / Dato : 3.9 ----------------------- 21.319 Operación : + Dato : 12 ----------------------- 33.319 Operación : = ----------------------- 33.319

Page 109: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

109

Solución de problemas con estructuras de control Técnicas para la resolución de programas que requieren el ingreso de una gran cantidad de datos:

La idea en esta sección es la de poder apreciar que un programa que resuelve un problema puede desarrollarse de muchas maneras diferentes, sin embargo, a pesar que todas darán un resultado correcto, no todas las soluciones serán igualmente eficientes ni serán igualmente aceptadas.

Se planteará un problema sencillo, con la finalidad de analizar los detalles de la eficiencia del programa. El problema que se plantea es el de calcular un promedio que se obtendrá a partir de una gran cantidad de datos que se ingrese al programa.

Alternativa A: Inicialmente se ingresa el número de datos que se va a procesar y a continuación los datos para hallar el promedio:

Program promedioDeDatosVs01; var dato, numDat, suma, d: integer;

prom: real; begin

write(‘Ingrese el número de datos: ‘); readln(numDat); suma := 0; for d:= 1 to numDat do begin

readln(dato); suma:= suma + dato;

end; prom:=suma/numDat; writeln(‘Promedio = ‘, prom:8:1);

end.

Con la premisa del ingreso del número de datos, el programa se torna muy sencillo, la respuesta es casi inmediata. Sin embargo, pongámonos a pensar en el usuario que ejecutará el programa. Esa persona tiene el trabajo de digitar cada uno de los datos que se ingresará al programa, y según el planteamiento del problema son muchos. La manera en la que se ha diseñado el programa hace que además de los datos, el usuario tenga la tarea de contar cuántos datos tiene.

Las computadoras se han diseñado para aliviar los problemas, no para incrementarlos. Póngase a pensar que pasaría si el usuario se equivoca al contar, luego de ingresar todos los datos el programa le sigue pidiendo más datos, o luego de ingresar un gran número de datos el programa ya no le deja ingresar más datos y peor aun da una respuesta antes de concluir con el ingreso de todos los datos.

Esto no se puede concebir, el usuario no debe procesar manualmente datos para que el programa trabaje correctamente.

Alternativa B: El programa irá pidiendo un dato a la vez, el usuario deberá confirmar si se ingresa o no un dato más cada vez:

Page 110: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

110

program promedioDeDatosVs02; var dato, numDat, suma : Integer;

prom : Real; opcion : Char;

begin numDat:=0; suma :=0; writeln(‘Ingrese los datos:’); repeat

readln(dato); suma := suma + dato; write(‘¿Desea ingresar un nuevo dato (s/n)?’); readln(opcion);

until opcion = ‘N’; prom:=suma/numDat; writeln(‘Promedio = ‘, prom:8:1);

end.

Esta solución, como puede apreciar, es más elaborada que la primera, aquí se puede ver que el usuario ya no contará los datos sino que es el programa el que lo hará. A pesar de esto no es una buena solución, sólo hay que darse cuenta cómo será la ejecución del programa en cuanto al ingreso de los datos.

Primero sale el mensaje Ingrese los datos:, luego el usuario digita su primer dato y presiona enter, inmediatamente le aparece el mensaje ¿Desea ingresar un nuevo dato (s/n)? con lo que se verá obligado escribir la letra S seguido del enter. El proceso se repite hasta que el usuario ingrese una letra N (mayúscula). Observe que el usuario tendrá que ingresar el doble de información (el dato y la letra) que la que debe ingresar; más aun qué pasará si cuando ingresó todos los datos ingresa por error una n (minúscula), pues tendrá que volver a empezar.

Alternativa C: El programa controlará el ingreso de información mediante una condición de fin de datos.

La idea en esta alternativa es la de ingresar uno a uno los datos y cuando se terminen se ingresa un valor adicional con un valor que no se encuentre en el rango en que se encuentren los datos. Por ejemplo si los datos son las notas de los alumnos en un curso, luego de ingresar las notas se ingresa un valor como 21, de modo que el programa se dé cuenta que ya se terminaron los datos, si los datos son positivos se puede ingresar un valor como -1 al final. La limitación en esta solución es que hay que estudiar los datos que se ingresarán al programa para definir una buena condición de salida. A continuación se muestra este programa:

program promedioDeDatosVs03; var dato, numDat, suma: Integer;

prom : Real;

Page 111: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

111

begin writeln('Ingrese los datos, para terminar ingrese un

número negativo: '); numDat:=0; suma:=0; repeat

read(dato); if dato >=0 then begin

suma := suma + dato; inc(numDat);

end until dato < 0; prom:= suma/numDat; writeln('El promedio es : ',prom:8:2);

end.

Se puede ver que es un programa más elaborado que los anteriores, aunque no es perfecto, es una buena alternativa.

Un defecto que tiene esta solución es que existe la posibilidad que el usuario se equivoque al ingresar un dato. Luego de ingresarlo ya no podrá corregirlo, lo que hará que se tenga que ejecutar el programa nuevamente, por otro lado si luego de un tiempo quisiera volver a ejecutar el programa con los mismos datos tendría que ingresarlos uno a uno nuevamente. La solución a este problema es el uso de archivos de texto; esta alternativa se presentará más adelante luego de detallar la forma en que se manejan los archivos de texto.

Programas que emplean archivos de texto y estructuras de control

En el capítulo anterior ya se estudió cómo se puede acceder a un archivo de texto, en esta sección vamos a ampliar los conocimientos acerca de estos archivos y vamos a mostrar diferentes maneras de manipular estos archivos.

Los archivos de texto son una secuencia de caracteres almacenado en el computador. Esta secuencia de caracteres está dividida o agrupada en “líneas” o “registros”, cada línea puede tener un número diferente de caracteres y la cantidad de caracteres que puede tener una línea no tiene límite. Por esta razón, para poder determinar dónde empieza y dónde termina una línea en un archivo, entre línea y línea se colocan dos caracteres especiales que marcan esta delimitación. Estos caracteres especiales, como lo indicamos en el capítulo anterior corresponden al “carriage return” (cuyo código ASCII es 13) y al “line feed” (con código ASCII 10). Un procesador de palabras al cargar un archivo de textos mostrará cada uno de los caracteres del archivo, cuando encuentra estos caracteres especiales, los siguientes caracteres los muestra en la línea siguiente, de allí el nombre de “líneas” que se da para los registros de este tipo de archivos.

Esta secuencia de caracteres y líneas que contiene un archivo de textos tampoco tiene límites, por eso se requiere poner otra marca en el archivo de modo tal que se pueda

Page 112: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

112

saber dónde termina este archivo. Esta marca se establece con el caracter cuyo código ASCII en el 26 (end of file).

A continuación se muestra un programa que permite apreciar estas características. Pero antes habrá que crear un archivo de textos, para esto abra el “bloc de notas” del Windows y escriba lo siguiente:

Luego guárdelo con el nombre de archtext.txt en la misma carpeta donde escribirá el siguiente programa.

program leeArchivoCaracterPorCaracter; Detecta los cambios de línea y el fin del archivo var c: Char;

arch: Text; begin

assign(arch, 'archtext.txt'); reset(arch); while true do begin no se pone condición de salida aquí

read(arch,c); Si el caracter es un “caracter imprimible” (su código ASCII es mayor o igual a 32) se muestra como tal, de lo contrario se muestra su código ASCII precedido por el caracter # if ord(c) >= 32 then write(c) else write('#',ord(c)); if ord(c) = 26 then break; condición de salida

end end.

Luego de ejecutar el programa podrá ver como resultado algo similar a lo que se muestra en la figura siguiente, observe la presencia de los caracteres que marcan el cambio de línea y el fin de archivo.

Los caracteres de cambio de línea y fin de archivo pueden ser detectados fácilmente cuando en el programa se lee el archivo caracter por caracter, sin embargo no se puede detectar si lo que se lee se maneja con otro tipo de variables (entera, reales, cadenas de caracteres, etc.), simplemente el programa se las saltará, pudiendo entrar en iteraciones indefinidas en el caso que se encuentre al final del archivo. Para

Page 113: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

113

poder hacerlo, se debe hacer uso de funciones diseñadas para este fin en los diferentes lenguajes de programación, en Pascal estas funciones son eof y eoln.

Las funciones eof y eoln tienen como parámetro la variable de archivo conectada con el archivo que se está leyendo, esto es eof(arch) y eoln(arch), y devuelven un valor lógico (boolean) que será verdadero (true) si el indicador del archivo se encuentra en el fin de archivo, para el caso de eof, o en el fin de una línea, para eoln. Para otro caso se devuelve el valor falso (false).

Con estas funciones se podrá escribir algoritmos que permitan realizar las siguientes tareas

…mientras no sea el fin del archivo… leer datos del archivo y procesarlos… o

…mientras no sea el fin de la línea… leer datos del archivo y procesarlos…

Alternativa D: Aquí presentamos la última alternativa al problema planteado en la sección anterior, de lo que se trata esta vez es que el usuario copie sus datos en un archivo de textos, el programa leerá del archivo los datos e imprimirá el resultado, de esta forma el usuario podrá ejecutar el programa con los mismos datos todas las veces que quiera sin tener que digitarlos nuevamente, y si se diera cuenta que hay errores en los datos, sólo tendría que abrir el archivo con los datos, corregir los datos defectuosos, grabarlo nuevamente y ejecutar el programa.

program promedioDeDatosVs04; Var dato, numDat, suma: Integer;

prom: Real; arch: Text; nombArch: String;

begin writeln('Ingrese el nombre del archivo de datos'); readln(nombArch); assign(arch, nombArch); reset(arch); numDat := 0; suma := 0; while not eof(arch) do begin

read(arch, dato); suma := suma + dato; inc(numDat);

end; prom:= suma/numDat; writeln('El promedio es : ',prom:8:2);

end.

Observe que el programa pide al usuario el nombre del archivo, esto para permitirle al usuario mantener varios archivos con diferentes datos.

Page 114: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

114

Ejercicios diversos

1. Escriba un programa que permita contar la cantidad de valores que hay en cada línea de un archivo similar al que se muestra a continuación:

12.34 129.345 23 87.01 102.3 13 12 18 22 45.098 78.45 100 205.345 …

Observe que el cada dato puede estar separado por más de un espacio en blanco o tabulador.

program centaDeDatosPorLinea; var numDat, linea: Integer;

dato: Real; arch: Text; nombArch: String;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch, nombArch); reset(arch); linea:=1; while not eof(arch) do begin

numDat:=0; while not eoln(arch) do begin

read(arch, dato); inc(numDat);

end; readln(arch); writeln('Linea ', linea, ' : ', numDat); inc(linea);

end; end.

Al ejecutar este programa, con los datos que presenta en el planteamiento del problema, obtendremos el siguiente resultado:

2. Escriba un programa que permita determinar el promedio de ventas que ha realizado cada uno de los vendedores de una compañía, los datos se encuentran en un archivo de texto con el siguiente formato:

Page 115: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

115

Paula Gómez Roca 1309.34 1200.50 180 13 1450.0 Juan García Castro 456.09 102.24 333.33 Ana Rodríguez Narváez 7801.45 1000.0 2075.34 12345.67 …

En la primera línea del archivo se encuentra en nombre de un vendedor, en la segunda línea se encuentra la lista de los montos facturados por ese vendedor. En la línea siguiente aparece otro vendedor y en la siguiente lo que facturó, y así sucesivamente.

Solución: La solución estará encaminada a generar un ciclo iterativo, en cada uno de los ciclos debe procesarse un vendedor, las iteraciones se terminan cuando se llegue al final del archivo. El procesamiento de un vendedor se debe realizar leyendo primero el nombre del vendedor del archivo de textos, luego se leen una a una las cantidades numéricas y se acumulan, llevando la cuenta del número de valores, este proceso se realiza en una nueva iteración que concluye con el cambio de línea. Al salir de esta iteración se calcula el promedio y se imprime el nombre del vendedor y el promedio.

Programa: El programa resultante se muestra a continuación:

program promedioPorVendedor; var numDat: integer;

venta, suma, prom: real; arch: text; nombArch, vendedor: String;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch, nombArch); reset(arch); while not eof(arch) do begin

readln(arch, vendedor); numDat:=0; suma := 0; while not eoln(arch) do begin

read(arch, venta); suma := suma + venta; inc(numDat);

end; readln(arch); writeln(vendedor:35, ' : ', suma/numDat:10:2);

end; end.

Este programa, luego de ejecutarse mostrará un resultado similar al siguiente:

Page 116: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

116

3. Escriba un programa que permita contar la cantidad de caracteres “no blancos” en un archivo de textos. Los caracteres “no bancos” corresponde a los espacios en blanco, cambios de línea y tabulaciones.

Solución: La idea para solucionar este problema es ir leyendo uno a uno los caracteres de un archivo y se irán contado uno a uno, se descartarán todos aquellos que se consideren “no blancos”.

program cuentaCaracteresNoBlancos; var cont: integer;

arch: text; nombArch: String; c: char;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch,nombArch); reset(arch); cont:=0; while not eof(arch) do begin

read(arch,c); if not (c in [' ',#9, #13, #10]) then inc(cont); ‘ ‘ -> espacio en blanco, #9 -> código ASCII del caracter de tabulación, #13 -> código ASCII del caracter de retorno de carro, #10 -> código ASCII del caracter de cambio de línea

end; writeln('Número de caracteres no blancos: ',cont);

end. Al ejecutar el programa, empleando un archivo como vemos a continuación:

Obtendremos el siguiente resultado:

4. Escriba un programa que permita contar el número de palabras que se encuentran en

un archivo de texto. Las palabras pueden estar separadas por uno o más espacios en blanco, tabuladores o cambios de línea.

Solución: el programa deberá leer caracter por caracter el archivo, cuando detecte el inicio de una palabra la contará. El inicio de una palabra se detecta cuando el caracter que se ha leído es un caracter “no blanco” y el caracter leído inmediatamente antes es un caracter “blanco”.

Page 117: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

117

program cuentaPalabras; var cont: integer;

arch: text; nombArch: String; c, cAnt: char;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch,nombArch); reset(arch); cont := 0; cAnt := ' '; se inicializa con un espacio para poder contra la primera palabra while not eof(arch) do begin

read(arch,c); if (cAnt in [' ',#9, #13, #10]) and

not (c in [' ',#9, #13, #10]) then inc(cont); cAnt:=c;

end; writeln('Número de palabras: ', cont);

end. Si empleando un archivo como el que vemos a continuación para ejecutar el programa:

La salida del programa será como sigue:

Escriba un programa que permita modificar el contenido de un archivo de textos. El archivo será similar al siguiente:

En el presente mes se vendieron aproximadamente 250 artículos del tipo A y 300 del tipo B obteniéndose ingresos de $4768 y $ 7251 respectivamente con lo que la ganancia acumulada hasta el momento es de $25547, esto es, alrededor de 57% más que el año pasado.

El programa ingresará el nombre de un archivo con textos y el nombre del archivo de salida, además un porcentaje de incremento.

El programa deberá un archivo de salida con el mismo texto pero modificando todos los valores numéricos que encuentre, incrementándolos en el porcentaje dado.

Page 118: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

118

Para el ejemplo si se ingresa 17%, se obtendrá: En el presente mes se vendieron aproximadamente 292 artículos del tipo A y 351 del tipo B obteniéndose ingresos de $5578 y $ 8483 respectivamente con lo que la ganancia acumulada hasta el momento es de $29889, esto es, alrededor de 66% más que el año pasado.

Sólo se trabajará con valores enteros.

Solución: Como en el archivo de textos, los números no están colocados de acuerdo a un patrón determinado, el programa deberá leer el archivo caracter por caracter. Conforme lee un caracter lo imprimirá inmediatamente en el archivo de salida, pero si en la lectura de un caracter se detecta el inicio de un número, este caracter junto con los que le siguen se tomarán y se transformarán en una representación numérica. Una vez obtenido el número se incrementará en el porcentaje dado y se escribirá en el archivo de salida.

program modificaNumerosEnUnTexto; var nombArchIn, nombArchOut: String;

archIn, archOut: Text; porcent, numero: Integer; car: Char;

begin write('Ingrese el nombre del archivo de entrada: '); readln(nombArchIn); assign(archIn,nombArchIn); reset(archIn);

write('Ingrese el nombnre del archivo d esalida: '); readln(nombArchOut); assign(archOut,nombArchOut); rewrite(archOut);

write('Ingrese el porcentaje: '); readln(porcent); numero:=0;

while not eof(archIn) do begin read(archIn,car); while car in ['0'..'9'] do begin

Si caracter es un dígito, procesamos el número numero := numero * 10; numero := numero + ord(car)-ord('0'); leemos un nuevo caracter read(archIn, car); if not (car in ['0'..'9'] ) then begin

Si caracter no es un dígito indica que se terminó de procesar el número, entonces lo incrementamos por el porcentaje y lo imprimimos

numero := trunc(numero*(1+porcent/100));

Page 119: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

119

write(archOut,numero); numero:=0;

end end; write(archOut,car);

end; close(archOut);

end.

Page 120: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

120

CAPÍTULO 6: Programación modular:

Como vimos en el capítulo 3, cuando empezamos a analizar un problema muchas veces tendremos la sensación que el problema es muy complicado, que es tan grande que nos va a aplastar.

También vimos que una manera de resolver el problema es empezar por dividir el problema en partes, tratando de no entrar en los detalles de la solución. En ese capítulo se menciona el problema de cómo generar un listado ordenado descendentemente por notas, de un determinado curso. Se vio allí que el problema se podía dividir en las siguientes tareas:

Tareas: 1- Obtener los datos 2- Separar a los alumnos que llevan el curso 3- Obtener los promedios de cada alumno 4- Ordenar a los alumnos por promedio 5- Imprimir datos

Observe que las tareas que se han llegado a establecer, además de darnos una idea general de lo que tenemos que hacer para solucionar el problema, tienen algunas características.

En primer lugar cada tarea es independiente de la otra, esto quiere decir que si tomamos una de las tareas podemos tratar de resolverla independientemente si se tiene o no una solución para las otras. Por ejemplo la tarea 2, “Separar los alumnos que llevan el curso”, teniendo la lista de alumnos podemos separarlos independientemente de saber cómo se obtuvo la lista (por el teclado, por un archivo de textos, etc.), tampoco interesa aquí qué se va a hacer con la lista seleccionada (se va a ordenar por nombres, por promedio ponderado o por un curso, o si se va o no a imprimir y en qué medio). Esta característica hace que, como habrá podido darse cuenta, podamos empezar a solucionar el problema por cualquiera de sus tareas, sin necesidad que sea en un orden determinado, y más aun podemos solucionar una parte del problema sin tener idea de cómo se va a solucionar alguna otra tarea.

Otra característica de esto es que las tareas que se describen no están detalladas, no se menciona por ejemplo el nombre del archivo de donde se obtendrán los datos, qué método se va a emplear para ordenar los datos, ni la forma cómo se va a obtener la nota del curso. Esos detalles se irán colocando conforme ataquemos cada uno de los problemas.

Una vez descritas las tareas que tiene el problema, se coge una de las tareas y se le empieza a analizar. Es muy probable que esa tarea sea aun muy compleja, pues bien, a esa tarea le podemos aplicar el método anterior, esto es subdividirla en una serie de subtareas, por ejemplo:

Problema

Page 121: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

121

Tarea: Obtener los promedios de cada alumno 1- Tomar un alumno 2- Calcular el promedio de prácticas 3- Calcular el promedio de laboratorios 4- Aplicar la fórmula (2xPPr + PLab + 3xEx1 + 4Ex2)/10 para obtener la nota del curso 5- Si hay más alumnos, repetir los pasos 1 al 5

Aquí podrá observar que dentro de la lista de tareas hay alguna que es muy trivial y que con sólo una línea de código en algún lenguaje de programación estará solucionado, otras no y tendrán que aplicárseles nuevamente el método, esto hasta que todas las tareas sean muy simples de solucionar.

Esta metodología de solución de problemas ha sido utilizada por muchos años con gran éxito y se denomina “Diseño descendente”, se espera, en este texto, que usted pueda llegar a dominar esta metodología.

Otra característica de esta metodología es que una vez definida la lista de tareas de un problema se puede conformar varios equipos de personas, a cada equipo se le puede entregar una tarea que deberá resolver. Esto hará que la solución del problema se dé más rápidamente, porque estarán trabajando en paralelo. Cada grupo dará solución a una tarea, que entregará en lo que se denomina un “módulo” con todas las subtareas resueltas, luego se juntarán los módulos y se tendrá la solución para el problema inicial, esto se conoce con el nombre de “programación modular”.

Otra ventaja que se puede apreciar en esta forma de trabajar es que si luego de implementar la solución nos damos cuenta que una parte del programa no es muy eficiente, por ejemplo se demora mucho en mostrar los resultados, podemos reemplazar el módulo por otro que resuelva la misma tarea, pero más eficiente, sin tener que modificar todo el programa.

Implementación de la programación modular Los diferentes lenguajes de programación modernos permiten implementar programas haciendo uso de la técnica de programación modular. Las unidades básicas que nos brindan los lenguajes de programación para este fin se denominan “funciones” y “procedimientos”, estas unidades básicas también reciben el nombre de “subprogramas”.

Una función se puede definir como un conjunto de instrucciones que tiene como finalidad obtener un valor resultante. Si nosotros vemos dentro la biblioteca de funciones del Pascal, podemos observar que por ejemplo que en la instrucción

b := cos(0.5236);

Se encuentra una función, cos. Aquí vemos claramente porqué se le denomina función, a la función se le entrega un valor (0.5236) y con él se realizan internamente una serie de operaciones que conducen a determinar el valor del coseno de ese número. Una función sólo puede devolver un valor.

Un procedimiento también se puede definir como un conjunto de instrucciones, pero a diferencia de las funciones, los procedimientos no buscan obtener un valor resultante,

Page 122: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

122

sino que su finalidad es la de realizar un proceso. Veamos por ejemplo writeln, si lo colocamos en una instrucción como la siguiente:

writeln( ‘X1 = ‘, parteReal:8:3,’ +’, parteImag:8:3,’i’);

Se puede ver que aquí no se busca obtener un resultado, primero porque writeln no devuelve valores; lo que se pretende aquí realizar el proceso de mostrar en la pantalla del computador el contenido de las variables parteReal y parteImag acompañados de un texto de explicación y con un formato dado.

Si se concibe un proceso en el que se deba devolver más de un resultado se debe pensar más en un procedimiento que en una función, por ejemplo si se quisiera realice un proceso para determinar las coordenadas X, Y de un punto determinado.

Se debe aclarar que en el lenguaje Pascal las funciones y los procedimientos se declaran de una manera diferente, sin embargo en otros lenguajes como el C, C++, Java, etc. se emplea la misma sintaxis para declarar ambos, es por eso que en estos lenguajes se les denomina indistintamente funciones a ambos procesos.

Sintaxis de una función en Pascal:

Sintaxis de un procedimiento en Pascal:

A continuación se mostrarán ejemplos de cómo implementar estos elementos en un programa.

1. Escriba un programa que permita imprimir cinco valores aleatorios entre 5 y 15.

Solución: La solución del problema es sencilla, sólo hay que calcular el valor aleatorio, imprimirlo y repetir estos pasos 5 veces. Para el cálculo del valor aleatorio, sabemos que en Pascal existe una función que me devuelva el valor aleatorio entre 0 y n-1, pero no existe una función que nos de un valor entre 5 y 15; si es cierto que el problema se puede plantear sin usar subprogramas (y de hecho ya lo hicimos en capítulos anteriores), esta vez vamos a implementar una función que permita calcular un valor aleatorio entre 5 y 15 de la siguiente manera:

Primero implementaremos el programa principal, la idea aquí es suponer que el lenguaje Pascal tiene dentro de su biblioteca de funciones, una que se llame, por ejemplo, alatorio5_15 (de la misma manera existen random, sin, sqrt, etc.) y que nos da el

Identificador con el nombre de la función

Tipo de resultado

Bloque de instrucciones function

) Lista de parámetros

(

: ;

Declaración de identificadores

locales

Identificador con el nombre del procedimiento

Bloque de instrucciones procedure

) Lista de parámetros

( Declaración de identificadores

locales

;

Page 123: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

123

valor que necesitamos. Bajo este supuesto podemos escribir nuestro programa de la siguiente manera:

program valoresAleatoriosEntre_5_y_15; var i, valor: Integer; begin

randomize; for i := 1 to 5 do begin

valor := alatorio5_15; writeln(i:2,') ', valor:2);

end; end.

Si la función alatorio5_15 existiera, nuestro trabajo habría terminado, pero como no es así, debemos implementar esa función, por lo que a continuación mostramos el programa completo

program valoresAleatoriosEntre_5_y_15; function alatorio5_15: Integer; begin

alatorio5_15 := random(15-5+1) + 5; end;

var i, valor: Integer; begin

randomize; for i := 1 to 5 do begin

valor := alatorio5_15; writeln(i:2,') ', valor:2);

end; end.

Observe que en la implementación de la función, la manera de devolver el valor resultante de la función es asignarlo al identificador con el nombre de la función (en otros lenguajes de programación como C, C++, etc. la devolución del valor se hace a través de una instrucción denominada return).

La salida del programa será como sigue:

2. El siguiente programa muestra el uso de un procedimiento en un programa.

Solución: Lo que se busca en este ejemplo es que usted pueda observar que el programa principal, se va transformar en una lista de tareas, las que serán resueltas en el desarrollo de los procedimientos. Al igual que se hizo con la función del ejemplo anterior se debe escribir el programa pensando, en este caso, que los procedimientos existen, de modo que la solución planteada se base a la existencia de estos

Page 124: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

124

procedimientos y sea por tanto muy simple. En ese sentido, podemos plantear un programa de la siguiente manera:

program implementaciondeProcedimientos; begin

randomize; imprimeEncabezados; muestraValoresAleatorios; imprimeFinal;

end.

Luego de dar la solución general, se procede a desarrollar el nivel en el diseño descendente, y así hasta acabar con el problema.

A continuación se muestra el programa luego de haberse completado los niveles:

program implementaciondeProcedimientos; procedure imprimeEncabezados; begin

writeln('Este es un ejemplo del uso de'); writeln('procedimientos en un programa'); writeln; writeln('Resultados esperados:');

end; procedure muestraValoresAleatorios; begin

writeln('1) ',random(10)+1); writeln('2) ',random(10)+1); writeln('3) ',random(10)+1); writeln('4) ',random(10)+1); writeln('5) ',random(10)+1);

end; procedure imprimeFinal; begin

writeln('Fin del programa'); writeln('Fecha: 26/08/2009');

end; begin

randomize; imprimeEncabezados; muestraValoresAleatorios; imprimeFinal;

end.

Page 125: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

125

Como se puede observar, las funciones y procedimientos son muy parecidos, sin embargo a diferencia de las funciones, los procedimientos, como no devuelven resultados, no requieren asignar un resultado al identificador que lleva su nombre.

Al ejecutar el programa obtendremos el siguiente resultado:

Variables globales, locales y estáticas Variables Globales: Una variable global es aquella variable que ha sido declarada fuera del ámbito de los módulos de un programa, tienen como característica principal que, dependiendo de la ubicación de su declaración, pueden ser utilizadas dentro del código del programa principal y también dentro del código de las funciones y procedimientos del programa.

Observe el siguiente programa:

01 program usoDeVariablesGlobales; 02 var vGlobal: Integer; 03 function f1: Integer; 04 begin 05 vGlobal := 17; 06 f1 := 3 * vGlobal; 07 end; 08 procedure p1; 09 begin 10 writeln('Dentro del procedimiento p1:'); 11 vGlobal := 2 * vGlobal; 12 writeln(' La variable vGobal vale ahora: ', vGlobal); 13 writeln(' Fin del procedimiento p1'); writeln; 14 end; 15 Programa principal 16 var a: Integer; 17 begin 18 vGlobal := 5; 19 writeln('Al inicio del programa principal, vGlobal = ',vGlobal); 20 a:= f1; writeln; 21 writeln('Luego de ejecutar la función f1, vGlobal = ',vGlobal); 22 writeln('A = ',a); writeln; 23 p1; 24 writeln('Luego de ejecutar el procedimiento p1, vGlobal = ',vGlobal); 25 end.

Ámbito de la variable vGlobal

Ámbito de la variable a

Page 126: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

126

La variable vGlobal ha sido declarada al inicio del programa, antes de la declaración de funciones y procedimientos, esta variable podrá ser utilizada en cualquier parte del programa, incluso dentro de los subprogramas, es por eso que se denominan globales.

Esta característica global de las variables tiene validez desde el punto en que son declaradas hacia delante en el programa, esto quiere decir que si la variable vGlobal hubiera sido declarada entre las líneas 07 y 08 se le podría utilizar en el procedimiento p1 y en el programa principal pero no en la función f1. A esta característica se le denomina ámbito de la variable. Esta propiedad se da por la forma cómo trabajan los compiladores, en el proceso de compilación las líneas de un programa se van traduciendo secuencialmente (desde la primer a la última), por eso, una línea en la que se encuentre la variable, no podrá ser traducida si es que antes el compilador no tradujo la orden en la que se define las propiedades de esa variable (declaración).

En el programa anterior se puede apreciar el ámbito de la variable vGlobal así como el de la variable a. La ejecución del programa empieza en el programa principal, en la línea 18 se le asigna a la variable vGlobal el valor de 5, luego se llama a la función f1 en la línea 20, trasladándose el control del programa a la línea 05, allí se cambia el valor de vGlobal por 17, manteniéndose ese valor incluso luego de terminada la ejecución de la función. Cuando f1 termine, se retorna a la línea 20. Luego al llegar a la línea 22 se llama al procedimiento p1, transfiriéndose el control a la línea 10, en la línea siguiente se vuelve a cambiar el valor de vGlobal a 34, valor que se mantiene hasta terminar el programa. La ejecución del programa es como sigue.

Las variables globales, si es cierto que son muy prácticas para elaborar un programa, nos pueden dar más de un dolor de cabeza cuando trabajamos con muchos subprogramas y sobre todo cuando los subprogramas están en otros archivos y han sido elaborados por diferentes personas. Esto se da precisamente porque no se tiene el control de la variable. Por ejemplo analice esta porción de código:

01 var base, altura, area: Integer; 02 begin 03 base := 5; altura := 6 04 imprimeEncabezados; 05 area := base * altura / 2 ; 06 writeln('El área del triángulo es = ', area:10:2); 07 end.

Si las variables base, altura y area fueran declaradas como variables globales, nadie nos podría garantizar que el área que calculemos sea 15 (5*6/2). Esto simplemente porque entre la asignación de los valores y el cálculo del área se encuentra la invocación a un procedimiento llamado imprimeEncabezados. Este procedimiento,

Page 127: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

127

además de imprimir el encabezado del reporte final, podría hacer uso de las variables globales y por lo tanto podría modificar los valores de esas variables por cualquier otro que se le ocurra, y esto no lo podríamos controlar o evitar porque el procedimiento pudo haber sido elaborado por otro grupo de personas y podría ser muy extenso, y esto traería como consecuencia que el área que queremos calcular no sería la esperada, produciendo un error grave en el resultado del programa y que no nos daríamos cuenta de ello hasta que sea demasiado tarde.

Esto se debe evitar, la programación moderna busca independizar los módulos en un programa, encapsulando la información que manejan, de modo que lo que se haga en un módulo no afecta a otros. Es por esta razón que un programa no debe tener variables globales, en Pascal esto se puede lograr definiendo las variables del programa principal después de todos los módulos del programa, así el ámbito de la variable sólo afectará al programa principal y las variables no podrán ser manipuladas por los subprogramas.

Las variables globales son inicializadas por lo general en cero (o con el caracter nulo o con una cadena nula en el caso de otros datos).

Variables Locales: Las variables locales, son variables que se definen o declaran dentro de un subprograma, estas variables se crean en el instante en que se empieza a ejecutar el subprograma y se destruyen cuando éste termina. Si el subprograma es invocado varias veces en un programa, las variables locales declaradas en él, se crearán y destruirán tantas veces como el subprograma sea invocado.

Esta característica hace que las variables definidas como locales no puedan ser utilizadas por otros subprogramas ni tampoco por el programa principal, incluso dos subprogramas podrían definir sus propias variables locales y emplear los mismos nombres sin que se afecten unas contra otras.

El siguiente programa muestra el empleo de variables locales; allí se va a calcular el promedio y la desviación estándar de los valores contenidos en un archivo de textos (datosVL.txt).

Primero se muestra el programa principal, que es lo que primero debe escribirse en un programa, para poder darse cuenta qué se pretende con el programa.

program usoDeVariablesLocales; var prom, dE: Real; begin

Calculamos el promedio de los datos de un archivo prom := promedio; llamamos a la función promedio dE := desvEst; llamamos a la función desvEst writeln('Calculos realizados'); writeln('Promedio : ', prom:10:2); writeln('Desviación Estándar : ', dE:10:2);

end. A continuación se completa el programa agregándole la implementación de las funciones. Observe que dentro de cada función se han definido variables, las cuales sólo podrán ser utilizadas dentro de la función que la declaró, esa es la razón por la

Page 128: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

128

que se ha tenido que declara por ejemplo la variable suma en las dos funciones ya que en ambas funciones se tienen que acumular una serie de valores. Esta doble declaración puede parecer un fastidio envés de una ayuda, sin embargo piense por un momento que ese programa puede ser parte de otro mucho más complejo, que por el tamaño del programa se ha tenido que dividir éste entre varias personas o equipos, y que las funciones promedio y desvEst serán elaboradas por grupos diferentes; pues en cada una de ellas debe tener la facultad definir las variables (y los nombres de ellas) que se necesite para resolver su problema sin tener que preocuparse por la variables ( o los nombres de ellas) que se usan en otros módulos.

program usoDeVariablesLocales; function promedio: Real; variables locales de la función promedio var arch: Text; numDat: Integer; dato, suma: Real; begin

suma := 0; numDat:= 0; assign(arch, 'datosVL.txt'); reset(arch); while not eof(arch) do begin

read(arch, dato); inc(numDat); suma := suma + dato;

end; close(arch); promedio := suma / numDat;

end; function desvEst: Real; variables locales de la funcion desvEst var arch: Text; numDat: Integer; dato, suma, sumaCuad: Real; begin

suma := 0; sumaCuad := 0; numDat:= 0; assign(arch, 'datosVL.txt'); reset(arch); while not eof(arch) do begin

read(arch, dato); inc(numDat); suma := suma + dato; sumaCuad := sumaCuad + dato*dato;

end; close(arch); desvEst := sqrt((numDat*sumaCuad - sqr(suma))/(numDat*(numDat-1)));

end;

var prom, dE: Real; begin

calculamos el promedio de los datos de un archivo prom := promedio; dE := desvEst;

Page 129: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

129

writeln('Calculos realizados'); writeln('Promedio : ', prom:10:2); writeln('Desviación Estándar : ', dE:10:2);

end.

Variables Estáticas: Las variables estáticas son variables que, como las variables locales, sólo se pueden emplear en el subprograma que la declaró, pero a diferencia de éstas últimas no se destruyen cuando. Cuando el subprograma es nuevamente invocado mantiene el valor que tenía cuando termino la ejecución del subprograma la vez anterior.

La manera de declarar una variable estática es muy simple en la mayoría de lenguajes de programación, ya que sólo hay que anteponer la palabra “static” a la declaración de la variable; sin embargo en Pascal estas variables tienen una forma peculiar de definirse, la declaración se hace mediante la siguiente sintaxis:

El programa siguiente muestra el uso y el comportamiento de una variable estática, allí puede comparar estas variables con el comportamiento de una variable local.

program usoDeVariablesEstaticas; procedure pLocalYEstatica; const vEstatica : Integer = 1; var vLocal: Integer; begin

vLocal := 1; writeln(vEstatica:4, vLocal:9); inc(vEstatica); inc(vLocal);

end; var i:Integer; begin

writeln(' Estatica Local'); for i:=1 to 5 do begin

write(i:2, ') '); pLocalYEstatica;

end; end.

La ejecución de este programa dará como resultado lo siguiente:

:

,

; const Identificador Tipo de dato Valor =

Page 130: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

130

Observe que la variable estática mantiene su valor aun cuando se termine la ejecución del subprograma, esto no pasa con la variable local que se destruye cuando termine el procedimiento y se vuelve a crear cuando se le vuelve a llamar.

Parámetros por valor y por referencia Al igual que se requieren que se ingrese información a los programas para que estos se puedan ejecutar de acuerdo a las necesidades del momento, en la mayoría de los casos se requiere introducir información a los subprogramas para que estos puedan realizar su trabajo. Piense en la función sin o en procedimiento write, ambos requieren de datos para realizar su trabajo, sin requiere del valor del ángulo que queremos calcular, write a su vez necesita de los expresiones que deseamos mostrar en la pantalla. Esta información, que es introducida a los subprogramas se denomina parámetros o argumentos. Existen dos tipos de parámetros en un subprograma, los parámetros por valor y los parámetros por referencia, estos tipos de parámetros serán estudiados a continuación.

Parámetros por valor: un parámetro por valor se declara en un subprograma en Pascal de la siguiente manera:

Según esta sintaxis, estos parámetros se pueden definir como se muestran en los ejemplos siguientes:

function factorial (n: Integer): LongInt; var … begin …

end; procedure estadoDeCuentas(nombre, apellidoPat, apellidoMat: String); …

prodedure imprimeDatos(cod: LongInt, nomb, apellPat, apellMat: String; edad: Integer; sueldo: Real); …

Ahora veamos cómo trabaja y lo que significa un parámetro por valor. El siguiente programa permite calcular el factorial de un número:

01 program calculaElFactorialDeUnNumero; 02 function factorial(n: Integer): LongInt; 03 var i: Integer; 04 fact: LongInt; 05 begin 06 fact := 1; 07 for i := n downto 1 do 08 fact := fact * i; 09 factorial := fact; 10 end;

:

,

( Identificador Tipo de dato …

;

=

Page 131: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

131

11 var a: Integer; 12 f: LongInt; 13 begin 14 write('Ingrese un número: '); 15 readln(a); 16 f := factorial(a); 17 writeln('Factorial de ',a ,' = ', f); 18 end.

La ejecución del programa dará como resultado:

El programa se ejecuta de la siguiente manera:

1. La ejecución empieza en la línea 11 y 12, allí se declaran las variables a y f relacionándose con sendas direcciones de memoria, la siguiente gráfica muestra está situación:

2. Luego se sigue hasta la línea 15 en donde se lee un valor para la variable a, colocándose ese valor en la posición de memoria relacionada con a, en el ejemplo se lee el valor de 5. Entonces:

3. La siguiente línea (16) es el llamado a la función factorial, el programa toma el valor de la variable a y lo envía a la función. La función recibe ese valor y para poder manejarlo requiere almacenarlo en alguna parte de la memoria, por eso que en el encabezado de la función se colocan los parámetros, son allí donde se colocarán los valores enviados a la función. En el caso de la función factorial (function factorial(n: Integer): LongInt;), es el parámetro n el que recibe el valor de 5. El mapa de memoria del programa es ahora como se muestra a continuación:

El nombre que se da a los parámetros de un subprograma no depende del programa principal, por eso se puede dar al parámetro cualquier nombre, incluso se le puede dar el mismo nombre que el de la variable que envía el valor. Así por ejemplo, en

a

5

f

a

5

f

n

5

Ámbito de las variables del programa principal

Ámbito de las variables de la función factorial

f

∞ ∞

a

Page 132: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

132

vez de n se le pudo dar el nombre de a al parámetro y esto no va a cambiar el comportamiento del programa. Esta manera de manejar la información entre los subprogramas permite que cualquier modificación que se haga al contenido del parámetro, no afectará a la variable que envía el valor al subprograma, esto es si modificáramos el código de la función factorial por:

function factorial(a: Integer): LongInt; var i: Integer;

fact: LongInt; begin

fact := 1; for i := a downto 1 do

fact := fact * i; a := a + 100; factorial := fact;

end; El resultado que se obtendrá es el mismo a pesar que el valor de la variable a fue modificado.

Otra característica de los parámetros por valor es que a los subprogramas se les puede enviar no sólo el valor contenido en un variable, sino también se les puede enviar un valor constante o el producto de una expresión. Así, el programa principal podría modificarse de la siguiente forma:

var a: Integer; f: LongInt;

begin write('Ingrese un número: '); readln(a); f := factorial(a); writeln('Factorial de ', a,' = ', f); Empleando un valor constante f := factorial(7); writeln('Factorial de 7 = ', f); Empleando una expresión f := factorial((a + 3) div 2); writeln('Factorial de 4 = ', f);

end. Obteniéndose los siguientes resultados:

Page 133: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

133

Parámetros por referencia: el comportamiento de un parámetro por referencia es muy diferente al de un parámetro por valor; primero su declaración cambia, se declara en un subprograma en Pascal de la siguiente manera:

Según esta sintaxis, estos parámetros se pueden definir como se muestran en los ejemplos siguientes:

procedure incrementar(var valor: Integer); …

prodedure leerDatos(var arch: Text; cod: LongInt; nomb, apellPat, apellMat: String; var edad: Integer; var sueldo: Real); …

Ahora veamos cómo trabaja y lo que significa un parámetro por referencia. El siguiente programa permite incrementar el valor de una variable que se ingresa como parámetro:

01 Program ilustraElUsoDePrarametrosProReferencia; 02 procedure incrementar(var v: Integer); 03 var incremento: Integer; 04 begin 05 write('Ingrese el valor del incremento: '); 06 readln(incremento); 07 v := v + incremento; 08 end; 09 var valor: Integer; 10 begin 11 write('Ingrese un valor entero: '); 12 readln(valor); 13 incrementar(valor); 14 writeln('El nuevo valor es: ', valor); 15 end.

La ejecución del programa dará como resultado:

El programa se ejecuta de la siguiente manera:

1. La ejecución empieza en la línea 09, allí se declaran las variables valor relacionándola con una dirección de memoria, la siguiente gráfica muestra está situación:

:

,

( Identificador Tipo de dato …

;

) …

var

valor

Page 134: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

134

2. Luego se sigue hasta la línea 12 en donde se lee un valor para la variable valor, colocándose ese valor en la posición de memoria relacionada con esa variable. En el ejemplo se lee el valor de 259. Entonces:

3. La siguiente línea (13) es el llamado al procedimiento incrementar; en este punto el programa no envía el valor de la variable valor al procedimiento, sino envía la dirección de memoria relacionada con la variable valor. El procedimiento recibe esa dirección de memoria y con ella define el parámetro v, relacionándola con la dirección recibida (que coincide con la de la variable valor). En ese momento resulta que tanto la variable valor como la variable v estarán relacionadas al mismo espacio de memoria, es como si la variable valor reciba un “alias” y durante la ejecución del subprograma la variable valor se denominara v. Luego en la línea 03 se define una nueva variable, incremento. El mapa de memoria del programa es ahora como se muestra a continuación:

Al igual que con los parámetros por valor, el nombre que se da a los parámetros de un subprograma no depende del programa principal, por eso se puede dar al parámetro cualquier nombre, incluso se le puede dar el mismo nombre que el de la variable que envía el valor. Así por ejemplo, en vez de v se le pudo dar el nombre de valor al parámetro y esto no va a cambiar el comportamiento del programa.

4. En la línea 06 se lee un valor para la variable incremento, en el ejemplo se le asigna 87. Luego en la línea 07, se hace una modificación a la variable v, allí se le agrega el valor de incremento; aquí se modifica el contenido de la dirección de memoria relacionada con la variable v, quedando de la siguiente manera:

Al terminar la ejecución del subprograma, tanto el parámetro v como la variable incremento son destruidas por el sistema quedándose sólo la variable valor, con su

valor

259

valor

259

incremento

Ámbito de las variables del programa principal

Ámbito de las variables de la función factorial

v

valor

346

incremento

Ámbito de las variables del programa principal

Ámbito de las variables de la función factorial

v

Page 135: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

135

valor modificado; es por eso que cuando la variable valor es impresa en la línea 14 se muestra 346 y no el valor original de 259.

Los parámetros por referencia constituyen la manera de obtener más de un resultado en un subprograma.

Solución de problemas empleando diseño descendente y programación modular A continuación se presenta una serie de problemas que ilustran esta metodología.

1. Se desea escribir un programa que permita imprimir las facturas que se han elaborado por las ventas realizadas en una tienda.

Para realizar esta labor, se cuenta con tres archivos de texto con la información requerida. El primer archivo, denominado “producto.txt”, contiene la información de todos los productos que comercializa la tienda; este archivo al ser de tipo texto, y con la finalidad de simplificar la solución del problema, de modo que nos podamos concentrar en la metodología, es similar al que se muestra a continuación:

Archivo de Productos: 123456 Sillón de cuero rojo 1199.90 345678 Cocina de 4 hornillas 345.50 626262 Lavadora 689.99 …

Código del producto Descripción Precio unitario

En este archivo, agrupadas de tres en tres líneas, se encuentra el código del producto, la descripción del producto y el precio unitario del mismo.

El segundo archivo, denominado “clientes.txt”, contiene la información de todos los clientes que han comprado alguna vez algún producto en la tienda. De manera similar que en el archivo anterior, el archivo es parecido al que se muestra a continuación:

Archivo de Clientes: 23764590 Pedro García López 45667728 Manuel Paredes Gómez 81300045 Gustavo Tapia Ruiz …

DNI del cliente Nombre del cliente

En este archivo se encuentra en líneas independientes, el código del DNI del cliente y el nombre del mismo.

El tercer archivo contiene la información de las facturas que se han emitido en la tienda en un periodo de tiempo. A diferencia de los otros que siempre serán los mismos y que solamente se agregarán datos cada cierto tiempo, este archivo contiene los datos de las facturas emitidas en un período de tiempo, una vez que termina el período, se crea un nuevo archivo sin borrar el anterior, esto para poder tener un mejor control del los productos vendidos. Por esta razón no se conoce el nombre del

Page 136: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

136

archivo; cada vez que se ejecute la aplicación se deberá solicitar al usuario el nombre del archivo con el que se va a trabajar. El archivo contiene en cada línea la información necesaria para poder elaborar cada factura, esto es: en número de factura, el DNI del cliente que hizo la compra, y la lista de productos comprados, aquí sólo se consigna el código y la cantidad comprada de cada producto, pudiendo haber muchos productos en una línea. El archivo es similar al que se muestra a continuación:

Archivo de Facturas: 10001 81300045 445566 5 89098899 10 … 10002 23764590 345678 3 10036 11223344 626262 1 445566 21 … …

Código de la factura, DNI del cliente, código y cantidad del producto

Finalmente, el programa deberá imprimir las facturas de una manera similar a lo que se muestra a continuación:

No. De Factura: 10001 Cliente: Juan Pedro López Pérez Código Descripción Precio Unitario Cantidad Subtotal 123453 Pintura mate 25.40 4 101.60 545454 Brocha #3 3.80 2 7.60 101022 Escalera 2 m 156.30 1 156.30

Total: 265.50

Solución: Al tratarse de un problema complejo. La solución debe plantearse empleando un diseño descendente, modulando el programa mediante funciones y procedimientos. El aplicar el diseño descendente nos permitirá ir encontrando la solución en el camino e ir escribiendo el código del programa aun así no tengamos una idea muy clara de cada detalle de la solución completa.

Primero analicemos a grandes rasgos las tareas que hay que realizar para elaborar el problema; por un lado se tendrá que manejar esos archivos descritos en el problema, luego una tarea que debemos hacer es preparar los archivos (asignarlos, abrirlos, etc.) para que podamos trabajar con ellos, luego que los archivos estén abiertos habrá que procesar los datos para conseguir la impresión de las facturas, finalmente se deberá cerrar esos archivos. Entonces escribamos estas tareas como primer paso al desarrollo del problema:

Tareas: 1- Prepara los archivos 2- Procesar los datos e imprimir las facturas 3- Cerrar los archivos

Luego debemos analizar qué información se requiere para realizar cada tarea o qué información debe devolver la tarea para que se de por satisfecha su ejecución.

Entonces, para la primera tarea podemos apreciar que no se requiere enviarle información a la tarea ya que los datos que se requieren para abrir los archivos son constantes (los nombres de los archivos) o se solicitarán al usuario; lo que sí, la tarea debe devolver las variables de archivo debidamente asignadas para que las otras tareas se puedan realizar.

Page 137: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

137

La segunda y tercera tarea requiere de las variables de archivo, la primera para poder procesar los datos y la tercera para poder cerrar los archivos, luego se les deberá proporcionar estos datos.

Una vez hecha la anotación, pasaremos a formalizar las tareas escribiendo el código correspondiente, para esto cada tarea será considerada como un procedimiento o función:

program imprimeFacturas; Programa principal var archProd, archCli, archFact: Text; begin

prepararArchivos(archProd, archCli, archFact); imprimirFacturas(archProd, archCli, archFact); cerrarArchivos(archProd, archCli, archFact);

end.

En este momento el problema no ha sido solucionado en su totalidad, sin embargo ya se dividió en tres partes, la primera y tercera son tareas muy simples, la segunda es mucho más compleja, más aun, todavía no tenemos muy claro cómo la vamos a solucionar. Por esto nos vamos a concentrar primero en las más simples.

El procedimiento prepararArchivos se encargará de asignar a las variables de archivo la información necesaria para poder manejar los archivos, por regla general las variables de archivo siempre deben pasar por referencia. En el cuerpo del procedimiento, la tarea que se debe realizar es la asignación a las variables de archivo de las referencias a los archivos físicos, habrá que tomar en cuenta que dos de los archivos son siempre los mismos y el tercero se debe preguntar al usuario el nombre del archivo con el que se va a trabajar en ese momento. La tarea de apertura de los archivos no se considerará en este módulo debido a que esto, como se verá más adelante, se deberá realizar varias veces en el programa El código será el siguiente:

procedure prepararArchivos (var archProd, archCli, archFact: Text); var nombArchFact: String; begin

assign(archProd, ‘producto.txt’); assign(archCli, ‘clientes.txt’); write('Nombre del archivo de facturas: '); readln(nombArchFact); assign(archFact, nombArchFact);

end;

El procedimiento cerrarArchivos se encargará sólo del cierre de los archivos, la implementación es inmediata.

procedure cerrarArchivos(var archProd, archCli, archFact: Text); begin close(archProd); close(archCli); close(archFact);

end;

Page 138: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

138

El procedimiento imprimirFacturas es más complejo por lo que debemos aplicar un diseño descendente. Primero debemos analizar los archivos con los que contamos; al realizar esto vemos que los archivos “producto.txt” y “clientes.txt” son archivos que no tienen relación con los demás archivos, estos archivos se denominan entidades, por ejemplo clientes.txt guarda la información sólo de los clientes de la tienda, aquí no hay ningún campo que lo relacione con los productos, sólo con este archivo no se puede saber qué productos compró un cliente o en qué momento; esto es similar con el archivo de productos. Sin embargo, la información que guarda el archivo de facturas es diferente, al obtener los datos de una línea del archivo podemos saber quién la compró y qué productos compró; sin embargo no está completa, la información del cliente se da sólo por el DNI, mas no por su nombre (dirección, teléfono, etc.) y en cuanto a los productos que compró, sólo tiene el código y cantidad de los productos que compró, no está lo demás. Este tipo de archivo se conoce con el nombre de relaciones, ya que si es cierto que no están completos, la información que contienen es suficiente para poder, en conjunto con los otros, completar la factura.

Entonces, según lo expuesto, de nada nos sirve en el programa empezar a trabajar por los archivos de productos o clientes porque no vamos a poder relacionarlos con los otros, debemos empezar el análisis por el archivo de relación facturas.txt. Las tareas que vamos a realizar son:

Tareas: 1- Mientras hayan datos en el archivo de facturas

1.1- Leer el código de una factura y el DNI del cliente 1.2- Buscar en el archivo de clientes el nombre del cliente 1.3- Imprimir los datos de la factura y del cliente 1.4- Imprimir los productos que compró

Observe que estamos tratando de definir las tareas, no de dar solución completa al programa. Observe que la tarea 1.4 es compleja y que no hemos analizado aún cómo la vamos a solucionar.

La implementación de este módulo será como sigue: procedure imprimirFacturas(var archProd, archCli, archFact: Text); var numFact, dni: Longint; cliente: String; begin

reset(archFact); while not eof(archFact) do begin

read(archFact, numFact, dni); cliente := buscaCliente(dni, archCli); esta función devuelve el nombre del cliente, pero si hay un error en el DNI y no se encuentra éste en el archivo de clientes, se envía un texto que indique que la operación no fue satisfactoria if cliente <> ‘NOENCONTRADO’ then begin

Si lo encuentra, imprime encabezado de factura writeln('Factura No. ', numFact); writeln('Cliente: ', cliente); imprimeProductos(archFact, archProd);

end else

Page 139: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

139

writeln('DNI: ', dni, ‘ no se encuentra en el archivo’); readln(archFact); writeln(‘==============================’); end;

end;

En este módulo observamos dos tareas inconclusas, buscaCliente e imprimeProductos, habrá que darle solución a cada uno.

En el caso de buscaCliente, se deberá tomar desde el inicio del archivo de clientes uno a uno los datos hasta ubicar el DNI buscado, este proceso deberá ser secuencial porque no se sabe si está o no ordenado el archivo por DNI. La búsqueda debe hacerse siempre desde el inicio del archivo, por esto es que la apertura del archivo la realizamos dentro de esta función ya que de no hacerlo así, cuando se busque el cliente de la segunda factura, éste será buscado no desde el principio sino desde donde se quedó en la búsqueda anterior. El código ser

function buscaCliente(dni: Integer; var archCli: Text): string; var dniCli: Longint;

nombCli: String; encontrado: Boolean;

begin reset(archCli); nos colocamos al inicio del archivo encontrado := false; while not eof(archCli) and not encontrado do begin

readln(archCli, dniCli); readln(archCli, nombCli); encontrado := dniCli = dni; si el DNI buscado es igual al que leímos del archivo, se asigna true al la variable encontrado y provoca la salida del while, de lo contrario asigna false y se sigue iterando

end; if encontrado then buscaCliente := nombCli else buscaCliente := 'NOENCONTRO';

end;

El procedimiento imprimeProductos deberá continuar la lectura de los datos de la factura que estamos imprimiendo, luego habrá que leer el código y cantidad de un producto y con esos datos completar la información (descripción y precio unitario) para luego calcular el subtotal y finalmente poderlo imprimir en la factura. Este proceso se debe repetir hasta que se termine la línea de datos. La lista de tareas por lo tanto será:

Tareas: 1- Mientras hayan datos en la línea de la factura

1.1- Leer el código y la cantidad de un producto 1.2- Buscar en el archivo de productos la descripción y el precio unitario des producto 1.3- Calcular el subtotal del producto comprado (precio unitario x cantidad comprada) 1.4- Acumular el subtotal 1.5- Imprimir los datos del producto

El código correspondiente es el siguiente:

Page 140: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

140

procedure imprimeProductos(var archFact, archProd: Text); var codProd: Longint;

cant: Integer; precUnit, subTotal, total: Real; desc: String;

begin total := 0; while not eoln(archFact) do begin

read(archFact, codProd, cant); buscaDescYPrec(codProd, archProd, desc, precUnit); Observe aquí que la búsqueda se hace a través de un procedimiento y no de una función como en el caso del cliente, esto se hace debido a que la tarea requiere devolver dos datos (descripción y precio unitario) y no uno como en el cado del cliente if desc <> 'NOENCONTRO' then begin

subtotal := cant * precUnit; total := total + subTotal; writeln(codProd, desc:30, precUnit:8:2, cant:3, subTotal:10:2);

end else writeln(codProd,' **** No está en el archivo ***');

end; writeln('Total: ',total:10:2);

end;

Finalmente el proceso de búsqueda del producto se realizará de manera muy similar a la del cliente, con la salvedad que en este caso se trata de un procedimiento y no de una función. Los parámetros devueltos (desc y precUnit) deberán ser definidos como parámetros por referencia.

procedure buscaDescYPrec (codProd: Longint; var archProd: Text; var desc: String; var precio: Real);

var cProd: Longint; encontrado: Boolean;

begin reset(archProd); encontrado := false while not eof(archProd) and not encontrado do begin

readln(archProd, cProd); readln(archProd, desc); readln(archProd, precio); encontrado := cProd = codProd;

end; if not encontrado then desc := 'NOENCONTRO';

end;

2. Se tiene un archivo de datos que contiene las notas obtenidas por los alumnos en diferentes cursos. El archivo tiene una estructura similar a la siguiente:

Page 141: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

141

Técnicas de programación Nombre del curso 1

0 20 Rango de notas

Juan Pérez Ruiz Nombre del alumno 1

10 14 7 123 19 11 -12 9 1234 17 Notas del alumno 1

Cecilia Rojas Ramos Nombre del alumno 2

11 24 15 12 13 10 12 178 Notas del alumno 2

. . .

Paula Castillo Sánchez Nombre del alumno n

18 20 -12 34 14 1111 77 17 -21 Notas del alumno n

*** Tres asteriscos que indican el fin de los alumnos del curso 1

13.45293 Promedio del curso del semestre anterior

Lenguajes de Programación 1 Nombre del curso

15 58 Rango de notas

Alexandra Neyra Núñez Nombre del alumno 1

Se desea que usted elabore un programa que permita leer este archivo y que genere otro archivo en el que aparezca la información resumida del primero con la siguiente estructura:

Técnicas de programación Nombre del curso

0 20 Rango de notas

Juan Pérez Ruiz 11.460 Nombre y promedio del alumno 1

Cecilia Rojas Ramos 11.800 Nombre y promedio del alumno 2

. . .

Paula Castillo Sánchez 17.000 Nombre y Notas del alumno n

*** Tres asteriscos que indican el fin de los alumnos del curso 1

-2.367% Porcentaje de variación del promedio del curso con respecto al semestre anterior

Lenguajes de Programación 1 Nombre del curso

15 58 Rango de notas

Alexandra Neyra Núñez 17.346 Nombre del alumno 1

El promedio debe calcularse de acuerdo a lo siguiente: o Las notas que estén fuera de rango corresponden a evaluaciones especiales y no

deben ser consideradas en el promedio. o Cada alumno puede tener un número diferente de evaluaciones. o Las dos últimas evaluaciones válidas corresponderán al primer y segundo examen,

las demás notas válidas corresponden a las notas de prácticas. o La fórmula a emplear será: Prom = (3xPP + 3xEx1 + 4xEx2)/10, donde PP es el

promedio de notas de práctica descartando la menor nota.

Solución: Al igual que en el problema anterior, la solución se dará empleando un diseño descendente. Luego, analizando el problema, observamos que también se debe trabajar sobre archivos (para el ingreso y la salida de datos) por lo que una tarea será la de preparar estos archivos y al final otra tarea será la de cerrarlos. En el intermedio debemos procesar el archivo; esta tarea también es simple, debido a que el archivo está organizado en bloques por cursos y cada bloque empieza con el nombre del curso seguido por el rango de notas válidas y finalmente un bloque con los alumnos del curso, la secuencia de tareas sugeridas podrá ser la siguiente:

Tareas: 1- Preparar los archivos 2- Mientras hayan datos para un cursos en el archivo

2.1- Leer el nombre del curso e imprimirlo en el archivo de salida 1.2- Leer el rango de notas e imprimirla en el archivo de salida 1.3- Procesar las notas de cada alumnos del curso

3- Cerrar los archivos

Page 142: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

142

El código para estas tareas será:

program resumenDeCursos; var archIn, archOut: Text; ArchIn manejará el archivo de datos (archivo de ingreso), mientras archOut manejará el archivo que contendrá el reporte final solicitado

nombCur: String; limInf, limSup: Integer; promCur, promAnt, variac: Real;

begin prepararArchivos(archIn, archOut); while not eof(archIn) do begin

readln(archIn, nombCur); writeln(archOut, nombCur); readln(archIn, limInf, limSup); writeln(archOut, limInf:4, limSup:4); procesaCurso(archIn, archOut, limInf ,limSup);

end; cerrarArchivos(archIn, archOut);

end.

procedure preparaArchivos(var archIn, archOut: Text); var nombArchIn, nombArchOut: String; begin

No se indican los nombres de los archivos, por lo que se tendrán que solicita al usuario write (‘Ingrese el nombre del archive de datos: ‘); readln(nombArchIn); assign(archIn, nombArchIn); reset(archIn); write (‘Ingrese el nombre del archive para el reporte: ’); readln(nombArchOut); assign(archOut, nombArchOut); rewrite(archOut);

end;

procedure cerrarArchivos(var archIn, archOut: Text); begin

close(archIn); close(archOut);

end; Para realizar la tarea de procesar las notas de los alumnos del curso, vemos que en el archivo los datos de cada alumno se encuentran en dos líneas, en la primera está el nombre del alumno, y en la segunda la lista de valores de los cuales hay que sacar sólo las notas dentro de los límites. La lista de alumnos se marca con una línea en la que hay tres asteriscos (***).

Las tareas para realizar esta labor serán como se indican:

Page 143: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

143

Tareas: 1- Leer el nombre de un alumno y escribirlo en el archivo de salida 2- Si el nombre del alumno corresponde a ‘***’, terminar el proceso (ir a 7) 3- Procesar la línea de notas y calcular el promedio del alumno 4- Acumular el promedio para determinar el promedio general del curso 5- Incrementar la cuenta de alumnos del curso 6- Volver al paso 1 7- Calcular la variación del promedio con el anterior semestre

El procedimiento correspondiente a esta tarea será el siguiente:

procedure procesaCurso( var archIn, archOut: Text; limInf, limSup: Integer);

var nombAlum: String; numAlum: Integer; suma, prom, promCur, promAnt, variac: Real;

begin numAlum:=0; suma:=0; repeat

readln(archIn, nombAlum); write(archOut, nombAlum); if (nombAlum <> '***' ) then begin

prom := promedio(archIn, limInf, LimSup); Esta tarea se realizará con una función debido a que sólo interesa que se devuelva el promedio obtenido writeln(archOut, prom:9:3); suma := suma + prom; inc(numAlum);

end; until nombAlum = '***'; writeln(archOut); promCur := suma / numAlum; readln(archIn, promAnt); Leemos el promedio anterior variac := (promCur - promAnt) / promCur * 100; writeln(archOut,variac:9:3,'%');

end;

Para realizar la tarea de calcular el promedio se debe tomar en cuenta que los datos están todos en una línea. Por otro lado hay valores que estarán fuera del rango, y que por lo tanto no se deben tomar en cuenta para el cálculo del promedio. Finalmente está el caso de los exámenes, debido a que hay notas que pueden estar fuera del rango, no se puede afirmar que las dos últimas corresponderán a los exámenes, para poder determinar qué notas corresponden a los exámenes se empleará un algoritmo similar al que se usó para el cálculo de la serie Fibonacci, esto es, se guardarán los dos últimos valores válidos luego de cada lectura.

Las tareas para realizar este proceso serán como sigue: Tareas: 1- Inicializamos las notas del los exámenes en cero y la nota mínima de prácticas con el

Page 144: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

144

valor del límite superior 2- Mientras hayan valores en una línea

2.1- Leer una nota 2.2- Si el valor está dentro del rango

2.2.1- Incrementar el número de notas 2.2.2- La nota leída es la última leída hasta ese momento, por lo tanto podría

ser el examen final, por lo tanto la nota registrada hasta ese instante para el primer examen corresponde a una práctica, la nota registrada para el segundo examen corresponde al primer examen y la nota leída debe correspondes con la del segundo examen.

2.2.3- Si el numero de notas leído hasta ese momento es mayor a dos 2.2.3.1- Si la nota de práctica es menor que la mínima registrada

entonces se reemplaza la nota mínima por la de la práctica 2.2.3.2- Acumular la nota de práctica

3- Calcular el promedio del alumno con los datos determinados

function promedio(var archIn: Text; limInf, LimSup: Integer): Real; var ex1, ex2, pract, min, sumaPr, nota, numNotas: Integer; begin

numNotas := 0; ex1 := 0; ex2 := 0; sumaPr := 0; min := limSup; while not eoln(archIn) do begin

read(archIn, nota); if (nota >= limInf) and (nota <= limSup) then begin

inc(numNotas); pract := ex1; ex1 := ex2; ex2 := nota; if numNotas > 2 then begin

if pract < min then min := pract; sumaPr := sumaPr + pract;

end end;

end; readln(archIn); promedio := (3*(sumaPr-min)/(numNotas-3) + 3*ex1 + 4*ex2)/10; Se resta tres (3) al número de notas porque se han contado todas las notas, incluyendo la de los exámenes (2) y la de la nota mínima de prácticas

end;

3. Se tienen un archivo de textos similar al que se muestra a continuación:

Se desea que usted elabore un programa que permita leer inicialmente dos números enteros, ambos en el rango de [2, 30], los cuales deberá validar. Los dos números indicarán una base inicial y una base final. Lego el programa deberá leer caracter por caracter el archivo de textos y detectar todos aquellos números que estén escritos en la base inicial y descartar los que no lo estén.

123 3AF4 10H31 2457 152 D 876GRF 87 HOLA ABF G3214 67J 56FA XGEW 12345 98 14583 4B00F 6TYF 87AB …

Page 145: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

145

Una vez que se compruebe que el número está en la base inicial, deberá imprimirse éste en otro archivo, pero en la base final. Al término de cada línea deberá imprimir el promedio de los números válidos, también en la base final.

Por ejemplo, si la base inicial fuera 16 y la base final 8, la respuesta para el archivo anterior sería:

443 35364 22127 522 15 207 Promedio: 10143 5277 Promedio: 5277 53372 221505 230 242603 1130017 103653 Promedio: 252003 …

Se descartó: 10H31, 876GRF Se descartó: HOLA, G3214, 67J Se descartó: XGEW, 6TYF,

Si la base inicial hubiera sido 8 y la base final 16, la respuesta para el archivo anterior sería:

53 52F 6A Promedio: 1F9 Promedio: 0 14E5 Promedio: 14E5 …

Se descartó: 3AF4, 10H31, D, 876GRF, 87 Se descartó: HOLA, ABF, G3214, 67J Se descartó: 56FA, XGEW, 4B00F, 98, 14583, 6TYF, 87AB

Solución: Al igual que en el problema anterior vemos que también se debe trabajar sobre archivos por lo que se debe realizar tareas para preparar y cerrar los archivos. La tarea principal consistirá en leer las bases inicial y final, verificando que estén en el rango establecido, y luego empezar a leer las palabras del archivo; aquí se debe verificar si la palabra corresponde con un número en la base inicial, si no es así se descarta, de lo contrario se acumula este valor para el cálculo del promedio y se escribe el número en la base final. Al detectar el final de la línea se debe imprimir el promedio para la línea y seguir con la siguiente línea, esto hasta terminar con el archivo.

Las tareas, por lo tanto se muestran a continuación: Tareas: 1- Preparar los archivos 2- Leer la base inicial y final verificando el rango 3- Mientras hayan datos en el archivo

3.1- Leer una palabra 3.2- Si la palabra corresponde a un número en la base inicial imprimir el número en la

base final y acumularlo. 3.2- Si se llegó al final de la línea se imprimirá el promedio de la línea en la base final

y se reiniciarán los contadores. 4- Cerrar los archivos

El código para estas tareas será como sigue: Program promediosEnDiferentesBases; const limInf = 2;

limSup = 30; var archIn, archOut: Text;

baseIni, baseFin, numDat: Integer; num, suma: Longint; esCorrecto, hayFinDeLinea: Boolean;

begin preparaArchivos(archIn, archOut); writeln(‘Ingresar la base inicial: ‘); leerBase(baseIni); writeln(‘Ingresar la base final: ‘); leerBase(baseFin); suma:=0; numDat:=0; inicializamos las variables para el promedio

Page 146: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

146

while not eof(archIn) do begin leeNum(archIn, baseIni, num, esCorrecto, hayFinDeLinea); leeNum es un procedimiento que leerá una palabra del archivo, este procedimiento verificará si la palabra corresponde a un número en la base inicial y lo almacenará en la variable num. La variable esCorrecto devuelve el valor de verdadero si el número asignado estaba en la base inicial y falso en caso contrario. La variable hayFinDeLinea devolverá verdadero si se terminó la línea luego de leer la palabra if esCorrecto then begin

imprime(archOut, num, baseFin); suma := suma + num; inc(numDat);

end; if hayFinDeLinea then begin

write(archOut,' Promedio: '); if numDat = 0 then write(archOut, 0) else imprime(archOut, suma div numDat, baseFin); usamos el mismo procedimiento imprime que se usó para los números writeln(archOut); suma := 0; numDat := 0;

end; end; cerrarArchivos(archIn, archOut);

end.

procedure prepararArchivos(var archIn, archOut: Text); var nombArchIn, nombArchOut: String; begin

write(‘Ingrese el nombre del archivo de datos: ’); readln(nombArchIn); assign(archIn, nombArchIn); reset(archIn); write(‘Ingrese el nombre del archivo para el re´porte: ’); readln(nombArchOut); assign(archOut, nombArchOut); rewrite(archOut);

end.

procedure cerrarArchivos(var archIn, archOut: Text); begin

close(archIn); close(archOut); end.

Page 147: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

147

procedure leerBase(var base: Integer); begin

repeat write(‘Ingrese un numero entre ‘, limInf, ‘ y ‘, limSup, ‘: ’); readln(base);

until (base >= limInf) and (base <= limSup); end.

La tarea de leer una palabra y transformarla en un número debe realizarse caracter por caracter, luego de leer un caracter debe verificarse si éste corresponde a una cifra que pertenezca a la base inicial, si pertenece entonces se debe transformar el caracter en una cifra numérica y hacerlo formar parte del número. Si el caracter no está en la base inicial se debe verificar si este caracter corresponde a un espacio en blanco o un cambio de línea si es así el número terminó de leerse, si no se verifica que la palabra no corresponde a la base inicial. Si el caracter leído corresponde a un cambio de línea entonces se debe registrar en la variable correspondiente.

El código se presenta seguidamente:

procedure leeNum( var archIn: Text; base: Integer; var num: Longint; var esCorrecto, hayFinDeLinea: Boolean);

var c: Char; dig: Integer;

begin esCorrecto := true; hayFinDeLinea := false; num := 0; repeat

read(archIn, c); if not (c in [' ', #10, #13]) then begin

Convertimos el caracter en un número if c in ['0'..'9'] then dig := ord(c) - ord('0')

else dig := ord(c) - ord('A') + 10; if (dig >= 0) and (dig < base) then

num := num *base + dig else

esCorrecto := false; end;

until c in [' ', #10, #13]; if c in [#10, #13] then begin

hayFinDeLinea := true; read(archIn,c); Leemos el segundo caracter del cambio de línea

end; end;

La tarea de imprimir el número en la base final es similar al que se vio en capítulos anteriores, por eso sólo mostraremos el código:

procedure imprime(var arch: Text; num: Longint; base: Integer); var factor: Longint;

Page 148: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

148

cifra: Integer; begin

write(arch,' '); factor := 1; while factor <= num do factor := factor * base; while (factor div base) <> 0 do begin

factor := factor div base; cifra := num div factor; if cifra < 10 then write(arch, cifra)

else write(arch, chr(cifra – 10 + ord('A'))); num:= num mod factor;

end end;

Page 149: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

149

CAPÍTULO 7: Aplicaciones con arreglos

En este capítulo empezaremos a estudiar los tipos de datos estructurados, en particular, estudiaremos aquí los arreglos. Para recordar este concepto diremos que un dato estándar es aquel que define un solo elemento, por ejemplo si definimos una variable de la siguiente manera: var a: Integer; sabemos que en la variable a podemos almacenar sólo un valor, y así ha sido en todos los casos que hemos analizado hasta ahora. A diferencia de los datos estándar, los datos estructurados permiten definir variables en las que en vez de almacenar sólo un valor, podremos almacenar en ellas muchos valores.

La presencia de datos estructurados en los lenguajes de programación se debe a que existen muchos problemas en los que si sólo se emplearan datos estándar las soluciones serían muy ineficientes y difíciles de manejar y mantener.

Analicemos los siguientes casos que se pueden presentar:

- Supongamos que tenemos un conjunto de datos de los cuales deseamos obtener su

desviación estándar. Una fórmula que permite calcular este valor es: ( )1

1

2

−=∑ =

nxxn

i iσ

Esta fórmula nos obliga a calcular previamente el promedio ( )x de todos los datos para luego poder determinar la desviación estándar. Si es cierto que la solución la podemos plantear empleando únicamente variables estándar, esta solución no sería eficiente. La razón para esto se debe a que como debemos calcular primero el promedio, tendíamos que leer los datos uno a uno y acumularlos en una suma, como se muestra a continuación:

while not eof(arch) do begin read(arch, dato); suma := suma + dato; inc(numDat);

end; prom:= suma/numDat;

Una vez hecho esto, para poder ahora determinar la desviación estándar debemos calcular otra sumatoria en la que restemos cada dato del promedio, lo elevemos al cuadrado y finalmente se le agregue a la sumatoria. Sin embargo, como ya no tenemos los datos en memoria, no nos va a quedar otra cosa que hacer que volver a leer todos los datos. Esto ya torna ineficiente la solución. Si tuviéramos una forma de almacenar los datos leídos en memoria la primera vez que los leemos, entonces, luego de calcular el promedio, cuando queremos calcular la segunda sumatoria, los datos se tomarían directamente de la memoria y no los tendríamos que leer nuevamente, esto sería muchísimo más rápido y por lo tanto haría más eficiente el proceso.

- Otro problema que se presenta y que ilustra muy bien la necesidad del uso de variables estructuradas es el de la determinación de una distribución de frecuencias.

Supongamos que se tiene un archivo en el que se ha registrado la intención de voto de un grupo de personas en una futura elección, supongamos que hay cinco candidatos y cada uno se identifica en el archivo por un número entero entre 1 y 5. A partir de

Page 150: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

150

este archivo deseamos saber qué porcentaje quiere votar por el primer candidato, qué porcentaje para el segundo, etc.

Empleando variables estándar, la solución podría encaminarse a la definición de cinco variables, las cuales acumularían la cantidad de simpatizantes que votaría por cada candidato. El programa sería más o menos como sigue:

program distribucionDeFrecuemncias; var canad1, canad2, canad3, canad4, canad5: Integer;

voto, total: Integer; porc1, porc2, porc3, porc4, porc5: Real; …

begin … Inicializamos los contadores canad1 := 0; canad2 := 0; canad3 := 0; canad4 := 0; canad5 := 0; Leemos los datos y los acumulamos dependiendo del valor leído while not eof(arch) do begin

read(arch, voto); if voto = 1 then

inc(candid1); else if voto = 2 then

inc(candid2); if voto = 3 then

inc(candid3); if voto = 4 then

inc(candid4); if voto = 5 then

inc(candid5); end; total := canad1 + canad2 + canad3 + canad4 + canad5; porc1 := candid1 / total; …

end.

La solución, aunque parece sencilla y adecuada para este caso puntual, no se verá así cuando se quiera extender la solución. Por ejemplo que la cantidad de candidatos varíe, digamos que en lugar de ser cinco, sean 15 candidatos, y que una vez modificado el programa se presente otro caso en el que hay sólo nueve. Al presentarse estas situaciones no nos quedaría otro camino que modificar el programa para que se adapte al caso y compilarlo nuevamente, y si presenta un nuevo caso volver a hacerlo.

Un programa se debe plantear de modo que si las condiciones cambiaran en un futuro, sea el mismo programa el que se adapte a ellas y no tener que hacer modificaciones que impliquen modificaciones al código y re compilación de éste. Como veremos más

Page 151: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

151

adelante esto se puede lograr, en algunos casos empleando tipos de datos estructurados.

Definición Un arreglo se define como una colección o conjunto de datos, que tiene como característica que todos los elementos que lo conforman son de un mismo tipo de dato, además todos se identifican por el mismo nombre. La manera de diferenciar un elemento de otro en el conjunto se hace por medio de uno o más índices. Dependiendo el número de índices por los cuales se identifique a los elementos de un arreglo se dirá que éste es un arreglo de una dimensión, de dos dimensiones, etc. A un arreglo de una dimensión se le reconoce también con el nombre de “vector”, y a uno de dos dimensiones como “matriz”.

Implementación de arreglos unidimensionales La implementación o declaración de un arreglo en los diferentes lenguajes de programación no siempre es igual, sin embargo existen tres elementos en la declaración de un arreglo que aparecen en la mayoría de lenguajes. Primero aparece una indicación que le dice al compilador que la variable que estamos declarando es un arreglo y no un dato estándar, esta indicación es una palabra reservada (array) como lo hace el Pascal o un par de corchetes ( [ ] ) como en el lenguaje C. En segundo lugar se debe indicar el número de elementos del conjunto, en Pascal por ejemplo se emplea un rango de datos (1..10), en el caso de C sólo se indica la cantidad de elementos que tendrá (10). Finalmente se debe indicar el tipo de dato que podrá almacenar cada elemento, como ya se ha dicho, éste debe ser igual para todos.

En el caso del Pascal, se recomienda que primero se defina un nuevo tipo de dato con la descripción del arreglo que se quiere emplear, y luego se emplee este tipo de dato para la definición de las variables que requiera el programa. Esto porque el Pascal no permite describir los tipos de datos en los encabezados de las funciones o procedimientos cuando se declara los argumentos.

En el capítulo donde se estudia la estructura general de un programa se vio cómo se define un nuevo tipo de dato, según eso la declaración de un tipo de dato arreglo seguirá la siguiente sintaxis:

De acuerdo a esta sintaxis, un tipo de dato para un arreglo se puede definir de la siguiente manera:

type TipoVector = array [1..5] of Integer;

Esta expresión define el tipo de dato denominado TipoVector el cual permitirá definir variables de tipo arreglo, con cinco elementos cada una, en el que cada elemento será de tipo Integer. Estas variables se podrán definir de la siguiente manera:

var a, vector, lista: TipoVector;

type Nombre del tipo = array [ rango ] of tipo ;

Page 152: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

152

El arreglo a, por ejemplo, define cinco variables ([1..5]) que se identificarán como a[1], a[2], a[3], a[4] y a[5], todas estas son de tipo Integer y podrán ser manipuladas como cualquier variable.

Algo interesante en el manejo de arreglos es que el índice que diferencia a los elementos puede ser reemplazado por una variable cuyo valor esté dentro del rango definido para los índices del arreglo. En los siguientes ejemplos se muestra esta característica.

program ejemploDeManejoDeArreglos; type TipoVector = array [1..5] of Integer; var a: TipoVector;

i: Integer; begin

i := 1; a[i] := 23; al valer i = 1 se le asigna el valor 23 a la variable a[1] i := i + 2; a[i] := 51; al valer ahora i = 3 se le asigna 51 a la variable a[3]

for i := 1 to 5 do a[i] := i * i; aquí el valor de i cambiará en cada ciclo y por lo

tanto la asignación se hará a un elemento diferente del arreglo en cada ciclo, dependiendo del valor que tenga la variable i en el ciclo.

for i := 5 downto 1 do writeln('a[', i, '] = ', a[i]);

de igual manera, aquí la referencia al elemento del arreglo dependerá del valor de la variable i en cada ciclo.

end.

Al ejecutar este programa podremos observar el siguiente resultado:

Algunos ejemplos de cómo definir un arreglo de una dimensión se muestran a continuación:

type TipoVector2 = array [-5..10] of Char; var b: TipoVector2;

Aquí se definen las variables b[-5], b[-4], b[-3], b[-2], b[-1], b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9] y b[10]. Todas de tipo Char.

type TipoVector3 = array [‘a’..’j’] of Boolean; var c: TipoVector3;

Page 153: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

153

Definen las variables c[‘a’], c[‘b’], c[‘c’], c[‘d’], c[‘e’], c[’f’], c[‘g’], c[‘h’], c[‘i’] y c[‘j’]. De tipo Boolean.

type TipoVector4 = array [false..true] of Real; var d: TipoVector4;

Definen las variables d[false] y d[true]. De tipo Real.

Otro elemento que se debe tomar en cuenta cuando se implementa un arreglo es que cuando uno define un arreglo, el tamaño o la cantidad de elementos del arreglo se deben definir en tiempo de compilación y no en tiempo de ejecución. Esto quiere decir que no se puede, durante la ejecución de un programa, que asignemos un valor a una variable (asignándola directamente o por lectura) y a partir de ella se defina un arreglo. No se puede hacer que un programa pida al usuario que ingrese el tamaño del arreglo y a partir de eso crear un arreglo de ese tamaño.

La definición del arreglo, tanto en el tamaño como en el tipo de dato que va a manejar, debe estar definido antes que el programa se ejecute. Esto trae un problema que ilustro con un ejemplo.

Volvamos al problema de la desviación estándar, queremos elaborar un programa que solucione el problema. Aquí se debe tomar en cuenta que el programa no se ejecutará una sola vez y que cada vez que se ejecute, la muestra que se emplee para el cálculo será diferente. No se puede suponer que siempre las muestras serán del mismo tamaño. Entonces, debido a que el tamaño del arreglo debe estar definido antes de ejecutar el programa y que no es lógico que el código del programa tenga que ser modificado en cada ejecución, las dudas que se presentan a este nivel serán ¿cuál es el tamaño que debe tener el arreglo para que pueda servir en todas las ejecuciones del programa? y ¿cómo manejar la cantidad de elementos en un arreglo que siempre tendrá el mismo tamaño?

La primera incógnita debe manejarse analizando el ámbito en el que se ejecutará el programa, por ejemplo si la desviación estándar se va a aplicar sobre las evaluaciones de un curso, se debe examinar la fluctuación en el número de alumnos que ha tenido el curso, de repente un semestre tuvo 45 alumnos, otro 38, 65, 51 etc. otra cosa que podríamos averiguar es la capacidad máxima de las aulas en que se lleva a cabo el curso. En este sentido debe considerar que si decide definir el tamaño del arreglo con 65 elementos, es probable que en algún momento pueda tener más alumnos (p. e.: 69) y que por lo tanto el programa no se pueda ejecutar y haya que modificarlo y compilarlo nuevamente. Por otro lado si por asegurarnos damos al arreglo un tamaño como por ejemplo de 500, podemos estar ante una ineficiencia en el manejo de recursos, ya que nunca se llegará a ese número de alumnos en el curso. Hay entonces que buscar un equilibrio, quizás al ver que las aulas no pueden albergar más de 90 alumnos, ese sea el número que debemos manejar.

Luego de haber determinado el tamaño del arreglo, otra cosa que se debe tomar en cuenta es que este tamaño debe quedar fijo y no se puede o debe cambiar. Por lo tanto, en los programas se deben definir adicionalmente una variable o variables que nos indiquen cuántos datos hemos colocado en el arreglo, de modo que no cometamos

Page 154: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

154

el error de trabajar con el tamaño del arreglo en lugar de con el número de elementos que se han colocado en el arreglo.

Lectura e impresión de los datos en un arreglo unidimensional Para leer datos y asignarlos a un arreglo, se debe tener las mismas consideraciones que se tomaron en el capítulo de estructuras de control. Quiero decir con esto que se debe realizar esta operación pensando en el usuario, tratando que esta tarea sea rápida y sencilla, y que no tengamos que pedirle al usuario que introduzca datos más allá de los que son estrictamente necesarios.

La impresión de los datos también debe hacerse de modo que los datos estén tabulados de manera que se puedan entender fácilmente.

El siguiente ejemplo muestra una forma simple de ingresar datos a un arreglo desde la consola e imprimirlos luego:

program leeArrConsola; const MAXALUM = 90; Esta es la máxima cantidad de alumnos que se permite para el

programa, se tomará como tamaño del arreglo type TArrNotas = array [1..MAXALUM] of Integer; var nota :TArrNotas; Aquí se guardarán las notas. Las notas van de 0 a 20

numDatos: Integer; Esta variable controlará el número de datos que se almacenará en el arreglo

begin leerNotas(nota, numDatos); imprimeNotas(nota, numDatos);

end.

procedure leerNotas(var nota: TArrNotas; var numDatos: Integer); var aux: Integer; begin

numDatos := 0; writeln('Ingrese las notas, para terminar ingrese una nota con valor -1:'); repeat

read(aux); if aux in [0..20] then begin

inc(numDatos); nota[numDatos] := aux;

end; until aux = -1; readln;

end;

procedure imprimeNotas(var nota: TArrNotas; numDatos: Integer); var n: Integer; begin

for n:= 1 to numdatos do begin if (n-1) mod 5 = 0 then writeln;

Page 155: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

155

write(nota[n]:5); end; writeln;

end;

En el programa anterior se pueden apreciar algunos puntos importantes:

- Se definió primero una constante llamada MAXALUM, con la finalidad que a partir de ella se declare el tipo de dato TArrNotas que defina al arreglo. Esto se hace con la finalidad que en una eventual situación en la que se deba modificar el tamaño del arreglo, sea más fácil de encontrar. Por otro lado si en alguna parte del programa se necesitara recorrer completamente el arreglo se use esta constante para definir el límite, de modo que si el tamaño cambiara, el cambiar el valor de la constante sea suficiente para que el recorrido se haga correctamente.

- La manera en que sea planteado la lectura de datos permita al usuario elegir la forma en que ingresará los datos, esto es, uno seguido del otro, uno por uno, en grupos, etc., debido a que se emplea una orden read que mantiene el buffer de entrada. A continuación se muestran formas diferentes en que el usuario puede ingresar los datos:

- En cuanto a la impresión de los datos, ésta se hace tabulando los datos de modo

que salgan manteniendo un orden que permita leerlos e interpretarlos sin dificultad. Sin embargo hay un detalle en este procedimiento que es importante aclarar, se trata de la forma como se manejan los parámetros.

Se ha dicho que cuando uno pasa un parámetro por valor a un subprograma, el sistema toma el valor contenido en la variable o el resultado de la expresión y lo envía al subprograma, para esto se crea una nueva variable (argumento del subprograma) en donde se deposita el valor transmitido, cuando se termina de

Page 156: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

156

ejecutar el subprograma esta variable se destruye. El paso por referencia no hace esta operación sino que se envía a subprograma la “referencia” (dirección de memoria) de la variable.

En este sentido, cuando se pasa un arreglo como parámetro a un subprograma, si se hace por valor, el sistema tendría que crear un nuevo arreglo del mismo tamaño que el del parámetro, y luego asignar a cada uno de los elementos del arreglo, los valores enviados por el sistema. Este proceso es muy ineficiente, porque se duplica el uso de los recursos (se duplica la memoria empleada) y también retarda el proceso al tener que asignar todos los valores al nuevo arreglo.

En este sentido se recomienda que cuando se pase un arreglo como parámetro a un subprograma, éste se haga por referencia, ya que ni se creará un nuevo arreglo, ni se asignarán nuevamente los valores del arreglo.

La impresión de datos en el programa se diseñó en el ejemplo para que salga como a continuación se presenta:

Aplicaciones que emplean arreglos de una dimensión 1. El primer ejemplo que se presentará es el de la desviación estándar, en este

programa se leerán los datos desde un archivo de textos. El programa calculará además el promedio del curso. Solución:

program calculoDelaDesviasionEstandar; const MAX_DAT_ARR = 90; Tamaño del arreglo Type TipoNotas = array [1..MAX_DAT_ARR] of Integer; var nota: TipoNotas;

numDat: Integer; prom, desv: Real;

begin leedatos(nota, numDat); prom := promedio(nota, numDat); desv := desvEstandar(prom, nota, numDat); writeln('Promedio = ', prom:8:2); writeln('Desviasion estandar = ', desv:8:4);

end.

Page 157: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

157

procedure leedatos(var nota: TipoNotas; var numDat: Integer); var arch: Text;

nombArch: String; begin

write('Ingrese el nombre del archivo: '); readln(nombArch); assign(arch, nombArch); reset(arch); numDat := 0; Con numDat se controlará el numero de datos y el índice del

arreglo while not eof(arch) do begin

inc(numDat); read(arch, nota[numDat]);

end; close(arch);

end;

function promedio(var nota: TipoNotas; numDat: Integer): Real; var n: Integer;

suma: Real; begin

suma :=0; for n:=1 to numDat do

suma := suma + nota[n]; promedio := suma /numDat;

end;

function desvEstandar( prom: Real; var nota: TipoNotas; numDat: Integer): Real;

var n:Integer; suma : Real;

begin suma :=0; for n:=1 to numDat do

suma := suma + sqr(prom-nota[n]); desvEstandar := sqrt(suma/(numdat-1));

end;

2. En el siguiente ejemplo escribiremos un programa que permita determinar una distribución de frecuencias. Esta distribución de frecuencias se aplicará a una encuesta de intención de voto para una elección determinada. La distribución de frecuencias se mostrará mediante una gráfica de barras y para ver diferentes forma de manejar los arreglos se presentará primero como barras horizontales y luego como barras verticales. Partiremos de dos archivos de textos, uno contendrá los nombres de los candidatos ordenados por el número que lo identifica; el otro tendrá los datos obtenidos de la encuesta los cuales serán simplemente el número

Page 158: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

158

del candidato por el que votaría el ciudadano. Los archivos podrán ser similares a los siguientes:

Solución: De acuerdo a los datos, el programa deberá leer los archivos de texto pero el proceso será diferente en ambos casos. Los nombres de los candidatos no serán procesados, sólo se requiere almacenarlos en memoria para luego tomarlos para imprimirlos; como se trata de varios nombres, la mejor manera de almacenarlos es un arreglo. En cuanto a los votos, la cantidad de encuestados pueden ser miles; no tiene sentido y sería un desperdicio de recursos guardar esos miles de datos en un arreglo, hay que pensar qué es lo que realmente se requiere almacenar. La distribución de frecuencias busca saber cuántas personas desean votar por un determinado candidato, por lo tanto el programa debe contar los votos de cada candidato. De acuerdo a esto se requiere un contador por cada candidato y por lo tanto se requiere otro arreglo en el programa que cumpla esta función. Finalmente se requiere calcular el porcentaje con respecto a total de encuestados que tiene cada candidato, esto nos obliga a definir un tercer arreglo en el programa. Por último, a pesar que los tres arreglos trabajarán con la misma cantidad de datos, guardarán diferentes tipos de información, por lo que se requiere definir tres tipos de datos diferentes.

A continuación mostramos el programa principal que solucionará el problema.

program distribucionDeFrecuencias;

const MAX_CAND = 10; Máximo número de candidatos MAX_CAR = 50; Constantes que limitan el área de impresión MAX_LIN = 20; de las gráficas

type TArrCand = array [1..10] of String; TArrVotos = array [1..10] of Integer; TArrPorc = array [1..10] of Real;

var cand: TArrCand; votos: TArrVotos; porcent: TArrPorc; numCand: Integer;

begin leerCandidatos (cand, numCand); contarVotos (votos, numCand); calcularPorcent (votos, numCand, porcent); imprimirHistogHor (cand, votos, porcent, numCand); writeln; writeln; imprimirHistogVert (cand, votos, porcent, numCand);

end.

Page 159: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

159

Los procedimientos leerCandidatos y calcularPorcent son muy simples y no requieren mayor explicación:

procedure leerCandidatos (var cand: TArrCand; var numCand: Integer); var archCand: Text; begin

assign(archCand, 'Candidat.txt'); reset(archCand); numCand := 0; while not eof(archCand) do begin

inc(numCand); readln(archCand, cand[numCand]);

end; close(archCand);

end;

procedure calcularPorcent( var votos:TArrVotos; numCand:Integer; var porcent:TArrPorc);

var suma, v:Integer; begin

suma := 0; for v:= 1 to numCand do

suma := suma + votos[v]; for v:= 1 to numCand do

porcent[v] := votos[v]/suma*100; end;

En el procedimiento contarVotos se puede apreciar algunas de las ventajas del uso de los arreglos, aquí se ha considerado un arreglo en el que cada elemento llevará la cuenta de los votos de cada candidato. En este caso, a diferencia de lo que hicimos en el ejemplo al inicio de este capítulo, no se requiere que luego de leer la intención de voto de un elector tengamos que ejecutar una condicional por cada candidato para saber qué variable debemos incrementar. Aquí haremos que el índice de los elementos del arreglo coincida con el número del candidato, entonces luego de lee la intención de voto, ésta se usará como índice del elemento del arreglo que queremos incrementar. De este modo, sin importar cuántos candidatos haya, sólo se requiere una instrucción para realizar esta labor.

procedure contarVotos (var votos: TArrVotos; numCand: Integer); var archVotos: Text;

voto: Integer; begin

Inicializamos los contadores de votos for voto:=1 to numCand do votos[voto] :=0;

assign(archVotos, 'Eleccion.txt'); reset(archVotos);

Page 160: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

160

while not eof(archVotos) do begin read(archVotos, voto); Verificamos si el voto corresponde a un candidato e incrementamos los

contadores, usando el voto como índice del arreglo if voto in [1..numCand] then inc(votos[voto]);

end; close(archVotos);

end;

En los procedimientos que grafican los histogramas, como la salida será por la pantalla, deben contemplar algunos aspectos que tienen relación con la forma como se imprime en pantalla y el espacio que tenemos para escribir.

En primer lugar, como se trata de un gráfico de barras, y en la ventana de salida sólo se pueden colocar caracteres, las barras se dibujarán empleando el caracter ‘’, cuyo código ASCII en el 178. Al colocar varios de estos caracteres seguidos se verá como una barra: ‘’.

Luego, sólo 80 caracteres que podemos colocar en una línea de la pantalla, este valor no se puede modificar. Por lo tanto si queremos que en el histograma horizontal aparezca por cada candidato su nombre, el porcentaje que obtuvo, seguido por la barra que representa ese porcentaje, podemos ver que las barras tendrían una zona de aproximadamente 50 caracteres. Es por eso que se define en el programa principal una constante denominada MAX_CAR.

Según esto último y en vista que estamos hablando de miles de encuestas, cada candidato podría acumular cientos o miles de votos. Esto nos hacer ver que no podemos imprimir un caracter ‘’ por cada voto, sino que cada caracter debe representar un conjunto de votos. El problema es que no podemos dar un valor fijo a este conjunto, porque la muestra de encuestados puede cambiar mucho. Por lo tanto, lo que se ha decidido es determinar el tamaño de ese conjunto de manera variable, en función de la muestra. Esto quiere decir que luego de calcular la cantidad de votos que tiene cada candidato, se determina quién tiene más votos, luego se establece que esa cantidad será graficada en el área total destinada para las barras. Las otras cantidades, que son menores, tendrán un tamaño proporcional al mayor, será cuestión de aplicar una regla de tres simple.

El resto del código estará abocado a dar formato al reporte, que permita que éste se encuadre en la ventana.

procedure imprimirHistogHor( var cand:TArrCand; var votos:TArrVotos; var porcent:TArrPorc; numCand:Integer);

var max, v, c, numCar: Integer; begin

max := votos[1]; Determinamos la máxima cantidad de votos que recibió un candidato

Page 161: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

161

for v:=2 to numCand do if votos[v]>max then begin

max := votos[v]; end;

writeln(' Encuesta de opinion para las Elecciones'); writeln(' Histograma de Frecuencias Horizontal'); writeln; writeln('CANDIDATO':14,' % HISTOGRAMA'); writeln; for v := 1 to numCand do begin

write(cand[v]:14, porcent[v]:10:2,'% | '); Determinamos la cantidad de caracteres que se requieren para graficar

la barra en proporción al máximo numCar := round(votos[v] * MAX_CAR/max); Procedemos a imprimir la barra caracter por caracter for c := 1 to numcar do write(#178); writeln; writeln;

end; end;

Para el caso del histograma vertical, aquí se presenta el problema que la impresión de caracteres en la pantalla se hace horizontalmente, línea por línea, de arriba hacia abajo, y esto no se puede cambiar. Por esta razón no se puede imprimir los resultados completos de un candidato y luego pasar al otro, como se hizo en el caso anterior. Lo que se tiene que hacer es imprimir franjas horizontales que abarque parte de todas las barras a la vez, como se indica en la figura siguiente:

Entonces, para imprimir una franja se debe verificar para cada candidato si corresponde o no imprimir parte de la barra, por ejemplo en la cuarta franja, como se ve a continuación, sólo se tiene que imprimir parte de la barras que corresponden a los candidatos 2 y 5,la de los otros no.

Por lo tanto cuando se imprima esa franja, se deben imprimir espacios en blanco en aquellos candidatos cuya barra no llegue a la altura de la franja y los caracteres de la barra en aquellas que si lleguen. Veamos entonces el código.

Page 162: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

162

procedure imprimirHistogVert(var cand:TArrCand; var votos:TArrVotos; var porcent:TArrPorc; numCand:Integer);

var max, v, f, c, numCar: Integer; begin

Determinamos la máxima cantidad de votos que recibió un candidato max := votos[1]; for v:=2 to numCand do

if votos[v]>max then begin max := votos[v];

end; writeln(' Histograma de Frecuencias Vertical'); writeln; for f := MAX_LIN downto 1 do begin

En cada ciclo del for se imprime una franja write(' '); Imprimimos la parte de la barra correspondiente a cada candidato for v := 1 to numcand do begin

if f <= round(votos[v] * MAX_LIN /max) then Determinamos si se debe o no imprimir la barra write(' ', #178, #178, #178, ' ')

else write(' ');

end; Al pie de las barras colocamos una leyenda con los candidatos if f <= numCand then

write(numCand-f+1:3,') ', cand[numCand-f+1]); writeln;

end; write(' '); Imprimimos el número del candidato y su porcentaje for c:= 1 to numCand do

write(c:7); writeln; write(' '); for c:= 1 to numCand do

write(porcent[c]:6:2,'%'); writeln;

end;

El resultado final del programa se muestra a continuación:

Page 163: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

163

3. El siguiente ejemplo muestra una forma muy sencilla de generar un “FRACTAL”. Se

parte de un segmento de recta como se muestra en la figura 1. Teniendo las coordenadas de los extremos del segmento, se determina el punto medio del segmento y por allí se traza un segmento de recta “r” perpendicular al segmento original, de longitud aleatoria (figura 2). Luego se trazan los segmentos AP y PB, borrándose el segmento original (figura 3). Luego se toma cada uno de los nuevos segmentos y se les aplica a cada uno el mismo método, generándose 4 segmentos. Este proceso se repite muchas veces. Al final se obtienen formas similares las de la figura 4.

Se desea determinar las coordenadas de los vértices de un “fractal” similar al que se muestra en la figura 4, por lo que se quiere confeccionar un programa en Pascal en el que se determine y almacene en dos arreglos dichas coordenadas, como se muestran en la figura 5.

El programa leerá inicialmente las coordenadas de los extremos de un solo segmento de recta (coordenadas de A(X1, Y1) y B(X2, Y2)) y lo almacenara en las dos primeras celdas de los arreglos. Luego se leerá el número de veces que se desea se repita el método mencionado anteriormente.

Este programa, en el primer ciclo, deberá determinar la coordenada del punto P e insertarlas en el arreglo (entre las dos primeras). Luego tomar de dos en dos las coordenadas y realizar lo mismo. Este proceso se repite tantas veces como se indicó.

A(X1, Y1)

B(X2, Y2)

(Figura 1) (Figura 2)

A(X1, Y1)

B(X2, Y2) r

m

P

A(X1, Y1)

B(X2, Y2)

(Figura 3)

r

m

P

Page 164: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

164

Solución:

program calculaPuntosEnUnFractal; const MAX = 200; type TVector = array [1..MAX] of Real; var x, y: TVector;

numPuntos, numCiclos, i: Integer; nombArch: String; arch: Text;

begin randomize; write('Ingrese las coordenadas del primer punto: '); readln(x[1], y[1]); write('Ingrese las coordenadas del segundo punto: '); readln(x[2], y[2]); numPuntos := 2; cantidad de puntos inicial en el arreglo write('Ingrese el numero de ciclos: '); readln(numCiclos); write('Ingrese el nombre del archivo para los resultados: '); readln(nombArch); assign(arch, nombArch); rewrite(arch); Definimos los puntos en cada ciclo y los insertamos en el arreglo for i := 1 to numCiclos do

insertarPuntos(x, y, numPuntos); Imprimimos los puntos obtenidos for i := 1 to numPuntos do

writeln(arch, x[i]:8:3, y[i]:8:3); close(arch);

end.

En el procedimiento insertarPuntos vamos a tomar la pareja de puntos iniciales y calcularemos el punto P, luego lo insertaremos entre ellos, esto se hará en el primer ciclo. En el siguiente ciclo se tienen ahora tres puntos, aquí se tendrá que tomar una a una las parejas de puntos, comenzando desde el último, calcular el nuevo punto intermedio y luego insertarlo en el arreglo.

El proceso se hace desde el último elemento del arreglo porque así se hace más fácil la inserción de los puntos. Esto debido a que el arreglo crecerá hacia abajo,

(Figura 5)

Y1

Y2

Yn

X1

X2

Xn

Page 165: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

165

luego de insertar un punto, por lo tanto aquellos puntos que aun no se han procesado no se habrán movido de posición en el arreglo y por esto se encontrarán juntos a la hora de procesar la siguiente pareja.

procedure insertarPuntos(var x, y: TVector; var numPuntos: Integer); var i: Integer;

px, py: Real; begin

Tomamos una pareja de puntos en cada ciclo, comenzando desde el último, y calculamos el nuevo punto. Luego lo insertamos en el arreglo

for i := numPuntos - 1 downto 1 do begin calcularPunto(x[i], y[i], x[i+1], y[i+1], px, py); insertarPunto(x, y, numPuntos, i, px, py);

end; end;

El procedimiento insertarPunto es simple, sólo hay que desplazar los puntos hasta la ubicar posición del que vamos a insertar.

procedure insertarPunto( var x, y: TVector; var numPuntos, i: Integer; Px, Py: Real);

var k: Integer; begin

Desplazamos los puntos del arreglo hasta ubicar la posición del nuevo punto for k := numPuntos downto i + 1 do begin

x[k+1] := x[k]; y[k+1] := y[k];

end; x[i+1] := Px; y[i+1] := Py; inc(numPuntos);

end;

En el procedimiento calcularPunto tenemos el siguiente esquema geométrico, para dos puntos cualesquiera:

En consecuencia, el procedimiento se plantea de la siguiente manera:

procedure calculaP(xi, yi, xi1, yi1: Real; var px, py: Real); var r, alpha, a, ap, betha: Real; begin

r := random * 10; a := sqrt(sqr(yi1 - yi) + sqr(xi1 - xi))/2; ap := sqrt(a*a + r*r);

A(X1, Y1)

B(X2, Y2) r

a

P (XP,YP)

a

α β ∆y

∆x

2ABa =

∆+∆

222 xy

Page 166: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

166

alpha:=arctan((yi1-yi)/(xi1-xi)); betha:=arctan(r/a); px:=ap*cos(alpha + betha) + xi; py:=ap*sin(alpha + betha) + yi;

end;

4. Se desea hacer un programa en Pascal que permita reemplazar partes del contenido de un arreglo con otro. Para esto se cuenta con un archivo de textos con información similar al siguiente ejemplo:

24162424693596246529248557… 246 12345 113355 222 …

La primera línea del archivo consta de una serie de cifras, una a continuación de la otra, formando un número muy grande. No se sabe cuántos datos hay en esa línea pero en ningún caso habrán más de 300.

Las siguientes líneas son similares a la primera. No se sabe cuántas líneas vienen después de la primera, pero sí que son un número par.

El programa deberá leer la primera línea y almacenarla en un arreglo unidimensional. Luego leerá la segunda línea y lo almacenará en otro arreglo similar al anterior, finalmente lo hará con la tercera línea.

Cuando los tres arreglos estén cargados, el programa deberá buscar la secuencia de cifras del segundo arreglo en el primero y reemplazarlas por la tercera, en todos los casos en que se produzcan.

Una vez terminado el proceso deberá hacerse lo mismo con la cuarta y quinta línea, con la sexta y séptima, y así sucesivamente hasta terminar con el archivo. Finalmente deberá almacenar la secuencia de números que quedó en otro archivo de textos.

Para el ejemplo anterior se tiene:

Primer arreglo 2 4 1 6 2 4 2 4 6 9 3 5 9 6 2 4 6 5 2 9 2 4 8 5 5 7 …

Segundo arreglo 2 4 6 …

Tercer arreglo 1 2 3 4 5 …

Luego del primer proceso, el primer arreglo quedará de la siguiente manera: 2 4 1 6 2 4 1 2 3 4 5 9 3 5 9 6 1 2 3 4 5 5 2 9 2 4 8 5 5 7 …

Como se aprecia el primer arreglo podrá crecer o reducirse, dependiendo del los datos del archivo. Deberá tener en cuenta que si el arreglo está lleno o si la secuencia de reemplazo haría que se desborden los datos del arreglo no se deberá realizar el reemplazo. También deberá tener en cuenta que en el primer arreglo no deben quedar “huecos”.

Page 167: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

167

Solución: Este problema se presenta una dificultad, el tamaño de las secuencias a buscar y reemplazar. Estas pueden tener cualquier valor, esto quiere decir que ambas pueden ser del mismo tamaño o una puede ser más grande que la otra.

En este sentido, si la solución se inclina a trabajar en función a los tamaños, tendríamos que definir bloques de instrucciones para cada caso, que podrían repetir código y que extendería demasiado el programa.

Lo que hará la solución que se plantea aquí es que una vez encontrada la secuencia, ésta se eliminará del arreglo original desplazando los datos a la izquierda. Luego se desplazan los datos a la derecha de modo que quede un “hueco” del tamaño de la secuencia que va a reemplazar la que sale. Hacer esto elimina los casos, así ya no importará el tamaño relativo de una secuencia con respecto a la otra. La gráfica siguiente muestra ese proceso.

Primer paso: i ↓ 2 4 1 6 2 4 2 4 6 9 3 5 9 6 2 4 6 5 … Se corren los datos a la izquierda

i

Resultado : ↓ 2 4 1 6 2 4 9 3 5 9 6 2 4 6 5 … Se corren los datos a la derecha

i

↓ 2 4 1 6 2 4 9 3 5 9 6 2 4 6 5 … ↑ ↑ ↑ ↑ ↑ 1 2 3 4 5 Se coloca la nueva secuencia en el hueco

El programa principal definirá tres arreglos, en el primero se guardará la secuencia original que irá siendo modificada en cada ciclo, los otros guardará la secuencia a buscar y la secuencia a reemplazar. A continuación presentamos el programa:

program ReempSec; const MAX = 300; type TVectNum = array [1..MAX] of Byte;

var archIn, archOut: Text; linea1, linea2, linea3: TVectNum; numDat1, numDat2, numDat3: Integer;

begin preparaArchvos(archIn, archOut); leerSecuencia(archIn, linea1, numDat1); while not eof(archIn) do begin

leerSecuencia(archIn, linea2, numDat2); leerSecuencia(archIn, linea3, numDat3); reemplazar(linea1,NumDat1, linea2, numDat2, linea3, numDat3);

end; imprimir(archOut, linea1, numDat1); close(archIn); close(archOut);

end.

Page 168: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

168

procedure preparaArchvos(var archIn, archOut:Text); var nombArchIn, nombArchOut: String; begin

write('Ingrese el nombre del archivo de datos: '); readln(nombArchIn); write('Ingrese el nombre del archivo de salida: '); readln(nombArchOut); assign(archIn, nombArchIn); reset(archIn); assign(archOut, nombArchOut); rewrite(archOut);

end;

procedure imprimir( var archOut: Text; var linea: TVectNum; numDat: Integer);

var i:Integer; begin

for i:= 1 to numDat do write(archOut, linea[i]);

writeln(archOut); end;

La lectura de una secuencia tiene que adaptarse a cómo están los datos en el archivo. Si revisamos éste, vemos que las cifras están una a continuación de la otra, sin espacios intermedios, esto nos llevará a leer las cifras de una manera peculiar. Primero debemos estar consientes que no se puede leer la secuencia como un número, esto porque la secuencia puede tener hasta 300 cifras, y el máximo número entero que podemos leer es el que puede ser representado en 4 bytes, esto es 4,294’967,294, por eso esta opción queda descartada. Otra limitación que tenemos es que por el hecho que las cifras no están separadas por espacios, no se pueden leer las cifras independientes, una por una, como números. Esto precisamente porque para leer una lista de números, estos deben estar separados por delimitadores que pueden ser espacios en blanco, tabuladores o cambios de línea. Lo que nos queda es leer las cifras caracter por caracter, teniendo que transformar el caracter leído en un número luego de su lectura, al final lo colocaremos en un arreglo.

procedure leerSecuencia( var archIn:Text; var linea:TVectNum; var numDat:Integer);

var c: Char; begin

numDat:=0; while not eoln(archIn) do begin

Leemos un caracter a la vez de la secuencia y lo convertimos a número read(archIn, c); inc(numDat); linea[numDat] := ord(c)-ord('0');

Page 169: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

169

end; readln(archIn);

end;

La operación de reemplazo de las secuencias recorrerá una a una las cifras de la secuencia original verificando en cada una si empieza allí la secuencia buscada, si la encuentra la elimina e inserta allí la nueva, esta operación es simple. Algo que sí debemos hacer en este proceso es tratar de reducir las ineficiencias que se pueden presentar.

Primero podemos verificar que la posición de la cifra que estamos analizando permita encontrar la secuencia buscada. Nos referimos a que si la secuencia original tiene 20 cifras y estamos analizando el caracter 15, al margen de las cifras que estemos buscando, no podremos encontrar una secuencia que tenga más de seis caracteres. Entonces ya no tiene sentido continuar la búsqueda es ese caso.

Otra cosa que debemos tener en cuenta es que no podemos colocar en la secuencia original más de 300 cifras. En este sentido, si luego de sacar una secuencia encontrada, la cantidad de cifras que debemos insertar hará que la secuencia original tenga más de 300 cifras, el proceso debe terminar.

Según esto el procedimiento que se plantea es el siguiente:

procedure reemplazar( var linea1: TVectNum; var numDat1: Integer; var linea2: TVectNum; numDat2: Integer; var linea3: TVectNum; numDat3: Integer);

var i: Integer; i: es el índice del arreglo, del elemento a analizar begin

i:=1; Se verifica primero si hay suficientes cifras a partir de i para contener la

secuencia a buscar. Luego se verifica si, luego de quitar la secuencia buscada, la secuencia que la reemplaza pueda entrar en el arreglo

while (i <= numDat1 - numDat2 + 1) and (numDat1-numDat2+numDat3 <= MAX) do begin

Verificamos si en la posición i hay una secuencia if estaLaSecuencia(linea2, numDat2, linea1, i) then begin

borrarSecuencia(linea1, numDat1, i, numDat2); insertarNuevaSecuencia(linea1, numDat1, linea3, numDat3, i);

end; inc(i);

end; end;

A partir de aquí, los módulos siguientes se convierten en tareas muy simples de resolver. Los mostramos a continuación:

function estaLaSecuencia( var linea2:TVectNum; numDat2:Integer;

Page 170: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

170

var linea1:TVectNum; i:Integer):Boolean; var k:Integer;

sonIguales:Boolean; begin

k:=1; sonIguales := True; while (k <= numDat2) and sonIguales do begin

sonIguales := linea1[i] = linea2[k]; inc(i); inc(k);

end; estaLaSecuencia := sonIguales;

end;

procedure borrarSecuencia( var linea1:TVectNum; var numDat1:Integer; i, numDat2:Integer);

var k: Integer; begin

Corremos todos los datos a la izquierda eliminando la secuencia hallada for k:=i+numDat2 to numDat1 do begin

linea1[i] := linea1[k]; inc(i);

end; numDat1 := numDat1 - numdat2;

end;

procedure insertarNuevaSecuencia( var linea1:TVectNum; var numDat1:Integer; var linea3:TVectNum; numDat3, i:INteger);

var k, m: Integer; begin

Corremos todos los datos a la derecha para hacer espacio a la secuencia de reemplazo

m := numDat1 + numDat3; for k := numDat1 downto i do begin

linea1[m] := Linea1[k]; dec(m);

end; Se coloca la secuencia en el hueco formado for k:=1 to numDat3 do begin

linea1[i] := linea3[k]; inc(i);

end; numDat1 := numDat1 + numDat3;

end;

Page 171: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

171

Ordenación de datos Ordenar datos es una tarea muy importante y frecuente en programación, además está relacionada directamente con el manejo de arreglos.

Para ilustrar esto, veamos el siguiente ejemplo: queremos elaborar una aplicación que permita para un supermercado, dada una lista de compras de un cliente, determinar el total de la compra. La solución de este problema va por tomar uno a uno los artículos buscar en una tabla de artículos el productos, determinar el costo y agregar este monto al total. Pues bien, las búsquedas que hagamos en el proceso puede ser una tarea que tome la mayor parte del tiempo en la ejecución del programa, por lo que esta tarea debe realizarse con mucha eficiencia.

Para entender esto veamos la siguiente analogía: si alguien quiere buscar el teléfono de una persona en la guía, a nadie se le ocurriría buscarlo desde la primera página, leyendo de uno por uno los nombres que allí aparezcan. Lo que uno hace es abrir la guía más o menos por la mitad, y luego dependiendo del nombre que buscamos, por ejemplo si buscamos a Rodríguez sabremos que lo encontraremos en la mitad de la derecha, pero si buscamos a Jiménez estará a la derecha. Luego repetimos la operación con la mitad correspondiente hasta ubicarlo.

Esta manera tan rápida de encontrar un teléfono en la guía se puede hacer por una razón importante, los nombres de los usuarios están ordenados por el nombre de las personas. Imagínese que pasaría si en la guía que tenemos, los nombres de las personas estuvieran colocados conforme fueron adquiriendo su teléfono, ¿Cuánto tiempo nos tomaría encontrar el número de teléfono de una persona?

Volviendo a la aplicación del supermercado, si los datos no estuvieran ordenados, por más rápida que puede ser la computadora, entregar el total de una lista de compras demoraría tanto que seguramente el supermercado perdería mucha clientela.

Así como este ejemplo, la mayoría de aplicaciones a la que nos enfrentemos tendrán que realizar alguna búsqueda que, para que sea eficiente, deba trabajar con datos ordenados.

En este capítulo veremos una forma simple de ordenar datos, sin embargo debemos mencionar que la ordenación de datos es una tarea que se ha estudiado por muchos años y se sigue estudiando, buscando una eficiencia muy alta en el proceso, por lo tanto existen innumerables métodos de ordenación de datos. El estudio de estos métodos y el análisis de su eficiencia son propios de un texto de Algoritmos y estructuras de datos, por lo que no profundizaremos mucho en este sentido.

El método presentado a continuación se denomina “Método de intercambio”, el nombre se debe a la manera en que se ordenan los datos. Lo primero que haremos será explicar la forma cómo ordenaremos los datos para luego presentar el programa.

Método de intercambio:

Supongamos que tenemos la siguiente secuencia de datos que queremos ordenar: 47 53 35 19 97 59 77 12 93

Page 172: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

172

El proceso irá tomando uno a uno los elementos el conjunto comparándolos con el primero, si vemos que el que analizamos es menor que el primero, los intercambiamos. De este modo al finalizar el recorrido habremos colocado el menor valor del conjunto en el primer elemento del arreglo. Veamos este proceso:

47 53 35 19 97 59 77 12 93 Aquí, 53 es mayor que 47: No hay intercambio

47 53 35 19 97 59 77 12 93

35 < 47: Los intercambiamos

35 53 47 19 97 59 77 12 93

19 < 35: Los intercambiamos

19 53 47 35 97 59 77 12 93

97 no es mayor que 19: No hay intercambio

19 53 47 35 97 59 77 12 93

59 no es mayor que 19: No hay intercambio

19 53 47 35 97 59 77 12 93

77 no es mayor que 19: No hay intercambio

19 53 47 35 97 59 77 12 93

12 < 19: Los intercambiamos

12 53 47 35 97 59 77 19 93

97 no es mayor que 19: No hay intercambio

12 53 47 35 97 59 77 19 93 No se intercambian Se intercambian

Al final de este proceso vemos que el menor valor, el 12, terminó colocado en el primer lugar.

Luego tomaremos nuevamente los datos, sin incluir el primero, y procederemos a realizar la misma tarea a partir del segundo elemento. Así colocaremos el segundo valor más pequeño en la segunda casilla:

12 19 53 47 97 59 77 35 93

El proceso continuará hasta haber ordenado todos los datos. 12 19 35 47 53 59 77 93 97

Implementación:

El código del programa debe entonces definir un arreglo en donde se colocarán los datos que se quieren ordenar, luego el procedo de ordenación debe recorrer todos los elementos del arreglo y en cada elemento se deberá colocar el menor valor dentro de los que quedan a partir del elemento analizado. Tenemos que darnos cuenta que en este proceso, cuando lleguemos al penúltimo elemento, analizaremos ese elemento conjuntamente con el último, cuando esta tarea termine habremos colocado el menor en la penúltima casilla y el mayo en la última, por lo que el arreglo ya estará ordenado luego de terminar el proceso en la penúltima casilla, por lo tanto ya no se requiere otro ciclo para trabajar con el último elemento. El pseudocódigo para esta tarea sería como se muestra a continuación:

Siendo n el número de elementos colocados en el arreglo arr: Para i = 1 hasta el penúltimo elemento (n-1) hacer

Colocar en el elemento i del arreglo (arr[i]) el menor valor entre i y n

La tarea de colocar el menor valor en la casilla analizada (arr[i]) es otro proceso iterativo que va desde el elemento que sigue al que se está trabajando (arr[i+1]) hasta el último del arreglo. En cada ciclo se deberá tomar un elemento (arr[j]) y

Page 173: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

173

compararlo con el elemento que se encuentra en la casilla que estamos trabajando (arr[i]); si el elemento de i es mayor que el elemento de j, estos valores se deben intercambiar de casillas. Así se conseguirá ordenar el arreglo de menor a mayor valor.

El seudocódigo será el siguiente:

Para j = i+1 hasta el último elemento (n) hacer Sí el valor de arr[i] es mayor que el de arr[j] entonces intercambiarlos

Según esto, el programa que permita ordenar un conjunto de datos numéricos de menor a mayor será el siguiente:

program ordena const MAX = 100; type TVector = array [1..MAX] of Integer;

var arr: TVector; numDat: Integer;

begin leerDatos(arr, numDat); writeln('Datos en el orden original: '); imprimir(arr, numDat); ordenar(arr, numDat); writeln('Datos ordenados de menor a mayor: '); imprimir(arr, numDat); readln;

end.

procedure leerDatos(var arr: TVector; var numDat: Integer); var nombArch: String;

arch: Text; begin

write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch, nombArch); reset(arch); numDat := 0; while not eof(arch) do begin

inc(numdat); read(arch, arr[numDat]);

end; readln(arch); close(arch);

end;

procedure ordenar(var arr: TVector; numDat: Integer); var i, j, aux: Integer; begin

Page 174: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

174

for i := 1 to numDat -1 do for j := i+1 to numDat do

if arr[i] > arr[j] then begin intercambiamos los valores aux := arr[i]; arr[i] := arr[j]; arr[j] := aux;

end; end;

procedure imprimir(var arr: TVector; numDat: Integer); var i : Integer; begin

for i := 1 to numDat do write(arr[i]:4);

writeln; end;

Al ejecutar el programa con un archivo de texto como se muestra a continuación:

Se obtendrá el siguiente resultado:

Adaptación del método de ordenación a problemas más complejos

En este punto veremos cómo, empleando el mismo algoritmo de intercambio, podremos ordenar información que se presente de una manera más compleja.

Por ejemplo, tenemos en un archivo la lista de empleados de una compañía, en la que se aprecie el código, nombre y sueldo de cada empleado y queremos ordenarla por el código del empleado. El archivo puede ser similar al siguiente:

Aquí, el programa deberá definir tres arreglos y en cada uno debe colocar los datos de los empleados, vale decir definir un arreglo de enteros para colocar los códigos, un arreglo de cadenas de caracteres para los nombres y otro de reales para los sueldos.

El problema aquí es que si, como nos interesa ordenar los datos por código, ordenamos sólo el arreglo que contiene los códigos, obtendremos una repuesta equivocada, como veremos a continuación en el siguiente programa.

Page 175: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

175

program ordenaCodigos_MAL; const MAX = 100; type TVectorInt = array [1..MAX] of Integer;

TVectorStr = array [1..MAX] of String; TVectorReal = array [1..MAX] of Real;

var codigo: TVectorInt; nombre: TVectorStr; sueldo: TVectorReal; numDat: Integer;

begin leerDatos(codigo, nombre, sueldo, numDat); writeln('Datos en el orden original: '); imprimir(codigo, nombre, sueldo, numDat);

ordenar(codigo, numDat);

writeln('Datos ordenados de menor a mayor: '); imprimir(codigo, nombre, sueldo, numDat); readln;

end.

procedure ordenar(var codigo: TVectorInt; numDat: Integer); var i, j, aux: Integer; begin

for i := 1 to numDat -1 do for j := i+1 to numDat do

if codigo[i] > codigo[j] then begin aux := codigo[i]; codigo[i] := codigo[j]; codigo[j] := aux;

end; end;

procedure leerDatos ( var codigo: TVectorInt; var nombre: TVectorStr; var sueldo: TVectorReal; var numDat: Integer);

var nombArch: String; arch: Text;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch, nombArch); reset(arch); numDat := 0; while not eof(arch) do begin

inc(numdat); readln(arch, codigo[numDat]);

Page 176: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

176

readln(arch, nombre[numDat]); readln(arch, sueldo[numDat]);

end; close(arch);

end;

procedure imprimir( var codigo: TVectorInt; var nombre: TVectorStr; var sueldo: TVectorReal; numDat: Integer);

var i : Integer; begin

for i := 1 to numDat do writeln(codigo[i]:9, nombre[i]:25, sueldo[i]:10:2);

writeln; end;

La respuesta que obtendremos con este programa será la siguiente:

Observe que los códigos están ordenados, pero estos códigos no coinciden con el correspondiente trabajador. Podemos ver en los datos originales que el código: 1111111 corresponde al trabajador: “Julia Gonzalez Cardenas”, pero en los datos ordenados parece corresponder a “Juan Perez Ramirez”.

¿Cómo podemos hacer para que esto se corrija? La respuesta está en el proceso de ordenación, lo que pasa es que al intercambiar los datos, se cambian de lugar los códigos, lo que provoca que se pierda la correlación con los otros datos (nombre y sueldo). Por lo tanto para que se siga manteniendo esta relación a la hora de intercambiar los códigos, se debe también intercambiar los nombres y sueldos. Entonces el algoritmo de ordenación se debe adaptar a esta situación, modificando el código del procedimiento ordenar como muestra a continuación:

procedure ordenar( var codigo: TVectorInt; var nombre: TVectorStr; var sueldo: TVectorReal; numDat: Integer);

Page 177: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

177

var i, j, auxCod: Integer; auxNom: String; auxSue: Real;

begin for i := 1 to numDat -1 do

for j := i+1 to numDat do if codigo[i] > codigo[j] then begin

auxCod := codigo[i]; codigo[i] := codigo[j]; codigo[j] := auxCod;

auxNom := nombre[i]; nombre [i] := nombre [j]; nombre [j] := auxNom;

auxSue := sueldo[i]; sueldo [i] := sueldo [j]; sueldo [j] := auxSue;

end; end;

Con esto se obtiene la siguiente respuesta, observe que la correlación de los datos se mantiene:

Un segundo caso que permite ver una situación compleja en la ordenación se da cuando pueden existir datos repetidos en el campo que se emplee como criterio de ordenación. Por ejemplo si tuviéramos un archivo en el que se encuentren los usuarios de la compañía de teléfonos, se tiene allí el nombre completo (nombre, apellido paterno y apellido materno) del usuario y su teléfono, como se muestra a continuación:

Page 178: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

178

Si ordenáramos estos datos por el apellido paterno, intercambiando todos los campos como en el problema anterior, podemos observar, como se muestra a continuación, que la respuesta del programa no es del todo satisfactoria:

Lo primero que se puede observar es que los datos se han agrupado por apellido paterno, también se puede ver que están ordenados por el mismo campo. Sin embargo hay algo que no está bien, si nos fijamos en un grupo de personas con el mismo apellido paterno, podemos darnos cuenta que allí no hay orden. Por ejemplo el usuario “Andres Caceres Juares” debería estar antes que “Guillermo Caceres Mora”, que “David Caceres Diaz” y “Cesar Caceres Diaz” deberían estar juntos y “Cesar” antes que “David”. Esto para lograr un orden como el que se encuentra en las guías telefónicas.

Para lograr esto debemos analizar el algoritmo que empleamos para ordenar. Cuando el algoritmo hace la pregunta: if codigo[i] > codigo[j] then begin, lo que se está haciendo es verificar si codigo[i] el está ordenado en relación con codigo[j]. Por ejemplo, si estuviéramos ordenando de menor a mayor y en codigo[i] tuviéramos el valor 1111111 y en codigo[j] el valor 5555555, no habría que intercambiarlos ya que 1111111 esta ordenado con respecto a 555555. Pero si en codigo[i] tuviéramos el valor 777777 y en codigo[j] el valor 5555555, si se debería intercambiar porque no están ordenados relativamente.

Entonces cuando los ordenemos alfabéticamente, empleado este criterio, debemos determinar cuándo dos datos deben intercambiarse. Si un usuario i fuera “Rocio Soria Tapia” y el usuario j fuera “Julio Diaz Cumpitaz”, sólo con comparar el apellido paterno sería suficiente para saber que debemos intercambiarlos, ya que “Diaz” debe estar antes que “Soria” y aquí no interesa cual es el apellido materno.

Page 179: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

179

Sin embargo, si el usuario i fuera “Rocio Soria Tapia” y el usuario j fuera “Victor Soria Castro”, no podemos tomar una decisión comparando sólo el apellido paterno ya que al ser iguales no se haría el intercambio y “Rocio Soria Tapia” quedaría antes que “Victor Soria Castro” y eso estaría mal. Entonces debemos pensar en una condición más compleja que abarque estas dos situaciones, la pregunta debería llevar a verificar que si no hay orden relativo en el apellido paterno se debe hacer el intercambio pero si ambos apellidos son iguales se debe intercambiar si no hay orden en el apellido materno. A esto se debe agregar que en el caso que ambos apellidos sean iguales, se deberá intercambiar si no hay orden en el nombre. Estos tres casos se deben expresar en una sola condición.

El programa que resolverá este problema es el siguiente:

program ordenaNombres; const MAX = 150; type TVectorInt = array [1..MAX] of Integer;

TVectorStr = array [1..MAX] of String;

var telefono: TVectorInt; nombre, apellPat, apellMat: TVectorStr; numDat: Integer;

begin leerDatos(telefono, nombre, apellPat, apellMat, numDat); writeln('Datos en el orden original: '); imprimir(telefono, nombre, apellPat, apellMat, numDat);

ordenar(telefono, nombre, apellPat, apellMat, numDat);

writeln('Datos ordenados de menor a mayor: '); imprimir(telefono, nombre, apellPat, apellMat, numDat); readln;

end.

procedure ordenar( var telefono: TVectorInt; var nombre, apellPat, apellMat: TVectorStr; numDat: Integer);

var i, j, auxTel: Integer; auxStr: String;

begin for i := 1 to numDat -1 do

for j := i+1 to numDat do if (apellPat[i] > apellPat[j])

or ((apellPat[i] = apellPat[j]) and (apellMat[i] > apellMat[j])) or ((apellPat[i] = apellPat[j]) and (apellMat[i] = apellMat[j])

and (nombre[i] > nombre[j])) then begin

auxStr := nombre[i]; nombre[i] := nombre[j]; nombre[j] := auxStr;

Page 180: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

180

auxStr := apellPat[i]; apellPat[i] := apellPat[j]; apellPat[j] := auxStr;

auxStr := apellMat[i]; apellMat[i] := apellMat[j]; apellMat[j] := auxStr;

auxTel := telefono[i]; telefono[i] := telefono[j]; telefono[j] := auxTel;

end; end;

procedure leerDatos( var telefono: TVectorInt; var nombre, apellPat, apellMat: TVectorStr; var numDat: Integer);

var nombArch: String; arch: Text;

begin write('Ingrese el nombre del archivo de datos: '); readln(nombArch); assign(arch, nombArch); reset(arch); numDat := 0; while not eof(arch) do begin

inc(numdat); readln(arch, nombre[numDat]); readln(arch, apellPat[numDat]); readln(arch, apellMat[numDat]); readln(arch, telefono[numDat]);

end; close(arch);

end;

procedure imprimir( var telefono: TVectorInt; var nombre, apellPat, apellMat: TVectorStr; numDat: Integer);

var i : Integer; begin

for i := 1 to numDat do writeln(telefono[i]:9, nombre[i]:12, apellPat[i]:12,apellMat[i]:12);

writeln; end;

El resultado de este programa será el siguiente:

Page 181: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

181

Como se ve, es lo que esperábamos.

Cómo desordenar datos A pesar que esto parezca trivial, desordenar datos puede ser una tarea muy útil y compleja. Por ejemplo cuando se requiera una secuencia aleatoria de valores que no se repitan, esta situación se puede encontrar por ejemplo en la simulación de un juego de cartas, en donde la desordenación se refiere a barajar las cartas.

Si piensa que esta tarea es fácil, y que sólo tiene que ejecutar una función aleatoria varias veces, verá que esto no es así. El siguiente programa mostrará cómo haciendo esto no se consigue lo deseado.

program desordenar; Imprime una secuencia de valores aleatorios entre 1 y 20 var valor, i: Integer; begin

randomize; writeln; for i := 1 to 20 do begin

valor := random(20)+1; write(valor:3);

end; writeln; readln;

end.

La respuesta a esto será:

Como se puede ver, por la misma naturaleza de la función aleatoria, se generará un número aleatoria en el rango pedido sin fijarse en los valores que generó anteriormente. Por lo tanto la lista contendrá valores repetidos.

Entonces se podría pensar que para lograr una lista de datos desordenada en la que no se repitan los valores se tendría que generar el valor aleatorio y luego verificar si no

Page 182: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

182

ha salido, si ya salió se deberá generar un nuevo valor. Sin embargo, como veremos a continuación esta no es una buena solución debido a que cuando salga un nuevo valor no repetido, aumentará la probabilidad que es siguiente se repita. Para conseguir el último valor se deberán generar muchísimos intentos.

El siguiente programa muestra este algoritmo, y para ver la eficiencia hemos colocado un contador que nos dirá cuantas veces se ejecutará una instrucción crítica en el programa.

program desordena2; const MAX = 100; type TArr = array [1..MAX] of Integer; var nCiclos: LongInt; Variable global

var a: TArr; i: Integer;

begin randomize; nCiclos := 0;

for i := 1 to MAX do genera(a, i);

for i := 1 to MAX do write(a[i]:4); writeln; writeln('Numero de ciclos: ', nCiclos);

end.

procedure genera(var a: TArr; i: Integer); var valor: Integer; begin

repeat valor := random(MAX) + 1;

until noEsta(a, i, valor); a[i] := valor;

end;

function noEsta(var a:TArr; i, valor: Integer): Boolean; var k: Integer;

esta: Boolean; begin

esta := false; k := 0; repeat

inc(k); inc(nCiclos); instrucciones que más se esta := a[k] = valor; repetirán en el programa

until (k = i) or esta; noEsta := not esta;

end;

El resultado es el siguiente:

Page 183: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

183

Como se puede observar el algoritmo obtiene el resultado esperado sin embargo el costo ha sido muy alto, 62,240 veces se ejecutó la instrucción más crítica. Este valor puede ser más grande aún, de acuerdo a la secuencia de valores que vayan saliendo. Esto no se puede aceptar.

Ahora veremos un algoritmo muy ingenioso pero extremadamente eficiente que aplica el concepto de desordenar para generar una lista de valores sin repetir.

El algoritmo empieza llenando un arreglo con valores consecutivos, y a partir de allí los desordena. Luego de esto, genera un valor aleatorio entre 1 y el tamaño del arreglo. El valor aleatorio se toma como una posición en el arreglo. Seguidamente se intercambia el último valor del arreglo con el de la posición obtenida con el valor aleatorio. Finalmente se repite el mismo proceso pero ahora se toman todos los datos del arreglo sin considerar el último. Debido al intercambio de valores, no importará que el valor aleatorio se repita ya que en esa posición no se encontrará el mismo valor. Este proceso se repite hasta que sólo quede un elemento por analizar. De esta manera se recorre una sola vez el arreglo y se conseguirá que la secuencia esté desordenada aleatoriamente.

A continuación se presenta el programa: program desordenar; const MAX = 100; type TArr = array [1..MAX] of Integer;

var a: TArr; i, pos, aux, limite, nCiclos: Integer;

begin randomize; nCiclos := 0; Llenamos el arreglo con valores consecutivos for i:= 1 to MAX do a[i]:= i;

for limite:= max downto 2 do begin Determinamos un valor aleatorio entre uno y el límite pos := random(limite)+1; aux := a[limite]; Intercambiamos los datos de esas posiciones a[limite] := a[pos]; a[pos] := aux; inc(nCiclos); Instrucciones críticas

end; Imprimimos los valores for i:= 1 to MAX do

write(a[i]:4); writeln; writeln('Numero de ciclos: ', nCiclos);

end.

Page 184: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

184

El resultado es el siguiente:

Observe que la cantidad de ciclos equivale a la cantidad de datos que se quiere generar y no como el algoritmo anterior.

A continuación presentamos una pequeña aplicación en la que se aplica este concepto de desordenar. Se trata de un programa que permita repartir cartas a un grupo de jugadores. El programa leerá de un archivo los nombres de los jugadores y le repartirá a cada uno cinco cartas; previamente el programa barajará las cartas del mazo.

Para simular el mazo de cartas se definirá un arreglo con 52 elementos, que corresponde al número de cartas de un mazo convencional. Cada carta será identificada por un número consecutivo empezando de cero, luego para el proceso de impresión de una carta, el número se relacionará con la carta de la siguiente manera:

Las cartas que se encuentren entre 0 y 12 corresponderán a un palo de la baraja, las que se encuentren entre 13 y 25 a otro, entre 26 y 38 al tercero y de 39 a 51 al cuarto palo. De esta manera si recibimos un número cualquiera, bastará dividir el número entre 13 para obtener un valor 0, 1, 2 ó 3 que podemos relacionarlo con un palo. Casualmente en la tabla ASCII podemos encontrar las figuras correspondientes a las cartas en los caracteres de las posiciones 3 para los ♥, 4 para los ♦, 5 para los ♣ y 6 para las ♠, con lo que sólo necesitaríamos sumarle 3 a nuestra división para tener el caracter ASCII correspondiente al palo.

Finalmente el valor de la carta se obtiene del resto o módulo de la división. Así por ejemplo si recibimos el número 38, se tendrá que corresponde al 12 de ♣, ya que 38 ÷ 13 da como resultado 2 (2+3 = 5 ♣) con residuo 12.

El programa se muestra a continuación:

program desordenar; const NUMCAR = 52;

MAXJUG = 10; Type TArrCartas = array [1..NUMCAR] of Integer;

TArrStr = array [1..MAXJUG] of String;

var mazo: TArrCartas; jugador: TArrStr; numJug: Integer;

begin randomize; leeJugadores(jugador, numJug); barajar(mazo); repartir(mazo, jugador, numJug);

end.

Page 185: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

185

procedure barajar(var mazo: TArrCartas); var i, pos, aux, carta: Integer; begin

for i := 1 to NUMCAR do mazo[i]:=i-1; for i := NUMCAR downto 2 do begin

pos := random(i)+1; aux := mazo[i]; mazo[i] := mazo[pos]; mazo[pos] := aux;

end; end;

procedure repartir( var mazo: TArrCartas; var jugador: TArrStr; numJug: Integer);

var carta, palo, j, c: Integer; begin

for j:= 1 to NumJug do begin write(jugador[j]:15, ': '); for c := 1 to 5 do begin

carta := (mazo[(j-1)*5 + c] mod 13)+1; palo := mazo[(j-1)*5 + c] div 13; write(carta:5, chr(palo+3));

end; writeln;

end; end;

procedure leeJugadores( var jugador: TArrStr; var numJug: Integer);

var nombArch: String; arch: Text;

begin write('Ingrese el nombre del archivo con los jugadores: '); readln(nombArch); assign(arch, nombArch); reset(arch); numJug := 0; while not eof(arch) do begin

inc(numJug); readln(arch, jugador[numJug]);

end; close(arch);

end; El resultado que obtendremos es el siguiente:

Page 186: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

186

Page 187: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

187

CAPÍTULO 8: Arreglos de dos o más dimensiones

En el capítulo anterior manejamos arreglos de una dimensión, es decir con variables que manejan un índice. Un arreglo de más de una dimensión se define de manera similar a la de un arreglo de una dimensión con la diferencia que cada elemento se distingue de otro por el uso de dos o más índices, manteniéndose la característica que todos los elementos manejan el mismo tipo de datos. Por ejemplo, una aplicación clara del uso de un arreglo de dos dimensiones es aquella en donde se necesite una tabla de doble entrada, otra aplicación se presenta en aplicaciones donde se requiera un algebra de matrices (suma de matrices, multiplicación de matrices, inversa de matrices, etc.).

Implementación de arreglos de más de una dimensión La sintaxis de la declaración de un arreglo de más de una dimensión varía ligeramente con respecto a los de una dimensión, a continuación se presenta el diagrama sintáctico:

Al aplicar esta regla sintáctica un tipo de dato que permita definir un arreglo de dos dimensiones quedará de la siguiente manera:

type TipoMatriz: array [1..10, 1..5] of Integer; var mat: TipoMatriz;

La variable mat define los siguientes elementos: mat[1] [1] mat[1] [1] mat[1] [3] mat[1] [4] mat[1] [5] mat[1] [6] mat[1] [7] mat[1] [8] mat[1] [8] mat[1] [10] mat[2] [1] mat[2] [2] mat[2] [3] mat[2] [4] mat[2] [5] mat[2] [6] mat[2] [7] mat[2] [8] mat[2] [9] mat[2] [10] mat[3] [1] mat[3] [2] mat[3] [3] mat[3] [4] mat[3] [5] mat[3] [6] mat[3] [7] mat[3] [8] mat[3] [9] mat[3] [10] mat[4] [1] mat[4] [2] mat[4] [3] mat[4] [4] mat[4] [5] mat[4] [6] mat[4] [7] mat[4] [8] mat[4] [9] mat[4] [10] mat[5] [1] mat[5] [2] mat[5] [3] mat[5] [4] mat[5] [5] mat[5] [6] mat[5] [7] mat[5] [8] mat[5] [9] mat[5] [10]

Al igual que con los arreglos de una dimensión, los índices se pueden manejar mediante expresiones, en esta caso cada índice es independiente del otro. Así se puede escribir una expresión como se indica a continuación:

mat[i+3][k-1] := 87; La manera en que hemos presentado la definición de un arreglo de dos dimensiones es muy simple y fácil de realizar, sin embargo no es la manera más óptima de realizarla. Existen por lo menos dos razones para esto, la primera es que la forma como se ha definido el arreglo, a la hora que se le quiera pasar como parámetro a una función o procedimiento, sólo se le podrá pasar de dos maneras, o todo el arreglo o un solo elemento del arreglo. Esto es, en un programa se podrá escribir el siguiente código:

x := func(mat); se pasa el arreglo complete proced(mat[i][j]); se pasa un elemento del arreglo

Los encabezados de estos módulos serán los siguientes:

function func(var mat: TipoMatriz): Integer; se recibe todo el arreglo procedure proced(elemento: Integer); se recibe un solo elemento

type Nombre del tipo = array [ rango ] of tipo ;

,

Page 188: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

188

Si es cierto que estas formas son aceptables, no son suficientes. Para esto le pongo el siguiente ejemplo: supongamos que en un arreglo de dos dimensiones se encuentran almacenadas todas las notas de diferentes evaluaciones de los alumnos de un curso, si se quisiera calcular el promedio de un alumno, la función que permitirá este cálculo tendrá que recibir todo el arreglo, a pesar que sólo se quiere el promedio de un solo alumno. Esto lo hace ineficiente.

La segunda razón para considerar que la manera en que definimos un arreglo bidimensional no es la mejor, es por la proyección que podemos hacer con lenguajes de programación más modernos. Se ha visto que esta manera de definirlas, permite modelar con una tabla de doble entrada como la que se aprecia en la figura siguiente:

Co

lumna

s

1 2 3 4 5 6 7 Filas 1 2 3 4 5 6

Sin embargo en muchas aplicaciones se puede observar que el desperdicio de memoria en estas estructuras de datos es muy alto. Por esta razón los lenguajes de programación más modernos tienen una alternativa de definición de estos arreglos y es que primero se define un arreglo unidimensional que representan filas de un solo elemento y a cada uno de estos elementos se le asocia un arreglo, también unidimensional, representando las columnas. El esquema siguiente muestra esto:

1 2 3 4 5 6 7 1

2

3

4

5

6

Luego, al ser independientes cada fila, se puede controlar el tamaño de cada fila, independientemente una fila respecto a la otra, lo que reducirá el espacio desperdiciado, esto hace que el modelo en que podemos trabajar será el siguiente:

Co

lumna

s

1 2 3 4 5 6 7 Filas 1 2 3 4 5 6

Page 189: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

189

Cabe indicar que a pesar de la forma, el manejo de los elementos es igual al de un arreglo bidimensional común, esto es mat[i][k].

Por estas razones es que vamos a cambiar, a partir de ahora, la forma en que vamos a definir los arreglos de dos dimensiones, de modo que podamos solucionar el problema de la primera razón y nos preparemos par que a la hora de migrar a otro lenguaje de programación no nos cueste mucho trabajo entenderla.

Primero vamos a definir un tipo de dato que permita crear un arreglo unidimensional que modele una fila de nuestra estructura, este tipo se empleará para definir cada fila de nuestra estructura. Luego se definirá otro arreglo unidimensional con la cantidad de filas que requeriremos, finalmente le asignaremos a este arreglo el tipo de dato definido anteriormente. De este modo se estará definiendo un arreglo unidimensional en que cada elemento es un arreglo unidimensional. La forma de manejarlo luego en el programa será idéntica a la de la manera tradicional.

Por lo tanto, la manera en que definiremos el arreglo inicial será como a continuación se detalla:

Type TipoVector: array [1..5] of Integer; TipoMatriz: array [1..10] of TipoVector;

var mat: TipoMatriz;

Ahora, con esta definición además de poder llamar a una función o procedimiento pasando como parámetro todo el arreglo o un elemento del arreglo, podemos agregar el hecho de poder pasar una fila del arreglo, esto es:

x := func(mat); se pasa el arreglo complete proced(mat[i][j]); se pasa un elemento del arreglo y := func2(mat[i]); se pasa una fila del arreglo

Los encabezados de estos módulos serán los siguientes:

function func(var mat: TipoMatriz): Integer; se recibe todo el arreglo procedure proced(elemento: Integer); se recibe un solo elemento function func2(var fila: TipoVector): Integer; se recibe una fila del arreglo

Aplicaciones que emplean arreglos de dos o más dimensiones Algebra matricial El primer ejemplo que desarrollaremos es un programa que permita realizar un algebra vectorial en donde sumaremos y multiplicaremos matrices. El programa se planteará de la siguiente manera:

program algebraDeMatrices; const MAX = 10; Type TVector = array [1..MAX] of Real; TMatriz = array [1..MAX] of TVector; var a, b, suma, mult: TMatriz;

nFilA, nFilB, nFilS, nFilM, nColA, nColB, nColS, nColM: Integer; sumaOK, multOK: Boolean;

begin

Page 190: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

190

leeMat ('A', a, nFilA, nColA); leeMat ('B', b, nFilB, nColB); sumaOK := sumaMat(a, nFilA, nColA, b, nFilB, nColB, suma, nFilS, nColS); multOK := multMat(a, nFilA, nColA, b, nFilB, nColB, mult, nFilM, nColM); impMat ('A', a, nFilA, nColA); impMat ('B', b, nFilB, nColB); if sumaOK then impMat ('Suma', suma, nFilS, nColS) else writeln (‘No se pueden sumar’); if multOk then impMat ('Multiplicacion', mult, nFilM, nColM) else writeln (‘No se pueden multiplicar’); readln;

end. El procedimiento leeMat, que permitirá leer los datos de una matriz y devolver también el numero de filas y columnas que manejara (p.e.: nFilA, nColA), se desarrollará tomando algunas consideraciones. En primer lugar se le agrega un parámetro al inicio, una cadena de caracteres que identifica a la matriz, esto para que a la hora de leer los datos se pueda identificar la matriz a la que le introducimos los datos.

Luego, en cuanto a la lectura propiamente dicha, se debe tener en cuenta que cuando se trata de leer los datos de una matriz, determinar la cantidad de filas y columnas que tiene es una tarea muy sencilla para el usuario, a diferencia de cuando se tiene que leer una lista de datos que se introducirá en un arreglo lineal, la figura siguiente muestra este detalle.

528428617935

45

1571651721148523327192511725164318

33211314967152

xdeMatrizxdeMatrizxdeMatriz ←

Es por esta razón, que a diferencia de lo que se hace con la lectura de un arreglo lineal, aquí sí le pediremos al usuario que introduzca el numero de filas y columnas.

En cuanto al ingreso de cada dato, haremos que el usuario los introduzca de una manera cómoda, en este sentido no lo obligaremos a introducir uno por uno los datos (indicando la fila y columna del dato que debe introducir), sino que los introduzca en conjunto por filas. A continuación se presenta este módulo:

procedure leeMat (nomb: String; var m: TMatriz; var nFil, nCol: Integer); var f, c: Integer; begin Se muestra la identificación de la matriz al solicitar las filas y columnas

write ('Ingrese el numero de filas y columnas de la matriz ', nomb, ' : '); readln (nFil, nCol); for f := 1 to nFil do begin

for c:= 1 to nCol do begin read (m[f][c]); al usar read se permita leer varios datos por línea

end; readln; limpia el buffer de entrada para leer sólo los datos de una fila

end; end;

Page 191: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

191

El procedimiento que imprime una matriz se hará de modo que se muestre los elementos de la matriz tabulados de modo que se pueda apreciar fácilmente las filas y columnas de ella. El código es el siguiente:

procedure impMat (nomb: String; var m: TMatriz; nFil, nCol: Integer); var f, c: Integer; begin

writeln; writeln ('Matriz ',nomb, ': '); for f := 1 to nFil do begin

for c := 1 to nCol do begin write (m[f][c]:8:2);

end; writeln;

end; end;

Finalmente vienen los módulos que permitirán calcular la suma y multiplicación de las matrices, aquí se ha elegido emplear funciones porque de alguna manera debemos saber si se han podido operar, hay que recordar que para sumas dos matrices, ambas deben tener igual número de filas e igual número de columnas, y en el caso de la multiplicación, el número de columnas de la primera matriz debe ser igual que el número de filas de la segunda matriz. Si estas condiciones no se dan no se podrá sumar o multiplicar las matrices, por esta razón se decidió devolver un valor lógico que indique si se pudo o no operar las matrices. Las funciones devolverán también, a través de parámetros, las matrices de respuesta a las operaciones.

Centrándonos en el proceso de la suma, el código del programa tendrá que basarse en el algoritmo matemático que se presenta a continuación:

[ ] [ ]njmibac

bbb

bbbbbb

aaa

aaaaaa

ccc

cccccc

jijiji

nmmm

n

n

nmmm

n

n

nmmm

n

n

,1,,1,,,,

,2,1,

,21,21,2

,12,11,1

,2,1,

,21,21,2

,12,11,1

,2,1,

,21,21,2

,12,11,1

∈∈∀+=

+

=

+=

:comomatricesdesumaladefineSe

:BAC

Según este algoritmo, para colocar en una matriz la suma de dos matices, primero debemos verificas que las dos matrices que se suman tienen igual número de filas e igual número de columnas. Luego se debe recorrer uno a uno los elementos de la matriz donde se colocará la respuesta y en cada elemento se colocará la suma del elemento, de cada matriz que se opera, que coincida con la posición del elemento recorrido. El recorrido de la matriz se hace de manera similar a la que se hace cuando se imprime la matriz. El código se muestra a continuación:

Page 192: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

192

function sumaMat ( var a: TMatriz; nFilA, nColA: Integer; var b: TMatriz; nFilB, nColB: Integer; var s: TMatriz; var nFilS, nColS: Integer): Boolean;

var f, c: Integer; begin

if (nFilA = nFilB) and (nColA = nColB) then begin nFilS := nFilA; nColS := nColA; for f := 1 to nFilS do

for c := 1 to nColS do s[f][c]:= a[f][c] + b[f][c];

sumaMat := true; end else begin

sumaMat := false; end;

end;

Para el caso de la multiplicación, el algoritmo matemático en el que s tenga que basarse el código del programa se presenta a continuación:

[ ] [ ] [ ]pknjmibac

bbb

bbbbbb

aaa

aaaaaa

ccc

cccccc

p

kjkkiji

nppp

n

n

pmmm

p

p

nmmm

n

n

,1,,1,,1,1

,,,

,2,1,

,21,21,2

,12,11,1

,2,1,

,21,21,2

,12,11,1

,2,1,

,21,21,2

,12,11,1

∈∈∈∀×=

+

=

∑=

×=

:comomatricesdeciónmultiplicaladefineSe

:BAC

Según este algoritmo, para colocar en una matriz la multiplicación de dos matices, primero debemos verificas que el número de columnas del la primera matriz operando debe ser igual al número de filas de la segunda matriz operando. Luego se debe recorrer uno a uno los elementos de la matriz donde se colocará la respuesta y en cada elemento se colocará el producto escalar de dos vectores formados por, el primero, los elementos la fila de la primera matriz operando que coincide con la fila del elemento a calcular y el segundo, por el vector formado por los elementos de la columna de la segunda matriz que coinciden con coincidan con la columna del elemento a calcular. El recorrido de la matriz se hace de manera similar a la suma y el código se muestra a continuación:

function multMat ( var a: TMatriz; nFilA, nColA: Integer; var b: TMatriz; nFilB, nColB: Integer; var m: TMatriz; var nFilM, nColM: Integer): Boolean;

var f, c, k: Integer; begin

Page 193: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

193

if nColA = nFilB then begin nFilM := nFilA; nColM := nColB; for f := 1 to nFilM do

for c := 1 to nColM do begin m[f][c] := 0; for k := 1 to nColA do

m[f][c] := m[f][c] + a[f][k]*b[k][c]; end;

multMat := true; end else begin

multMat := false; end;

end;

Al ejecutar el programa se obtendrá lo siguiente.

Page 194: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

194

Manejo de las filas de una matriz El segundo ejemplo que desarrollaremos permitirá manejar las filas de una matriz de manera independiente, de modo que se pueda pasar como parámetro a un procedimiento o función sólo una fila de la matriz, en lugar de pasar toda la matriz.

El programa, que leerá de un archivo de textos el nombre y notas de los alumnos de un curso, imprimirá estos mismos datos pero ordenados por el promedio del alumno, promedio que será calculado en el programa para cada alumno.

El archivo de datos será similar al siguiente: Juan Pérez Ruiz Todos los alumnos tienen igual cantidad de notas. 10 14 7 12 19 11 12 14 9 12 17 12 4 prácticas, 6 laboratorios, un examen parcial y uno final Cecilia Rojas Ramos 11 12 4 15 12 13 10 7 12 17 8 10 Promedio = 0.2xPP + 0.2xPL + 0.3xEXP + 0.3xExF Paula Castillo Sánchez 18 20 12 13 14 14 11 10 11 7 17 2 Se elimina la menor nota de prácticas y laboratorios Alexandra Neyra Núñez …

Para resolver este problema se debe definir tres tipos de arreglos, uno que permita guardar los nombres de los alumnos, otro, de dos dimensiones, para guardar las notas, ya que también se imprimirán las notas parciales, y otro tipo para almacenar los promedios, los cuales serán valores reales a diferencia de las notas que serán valores enteros. El tipo de dato que manejará las notas se definirá en dos tiempos para así poder manejar las notas de un alumno, las que se encontrarán en una fila, de manera independiente.

El programa se planteará de la siguiente manera:

program promAlumn; const MAX_ALUM = 50; MAX_NOTAS = 12; type TVectNomb = array [1..MAX_ALUM] of String;

TVectNotas = array[1..MAX_NOTAS] of Integer; TMatNotas = array [1..MAX_ALUM] of TVectNotas; TVectProm = array [1..MAX_ALUM] of Real;

var alumno: TVectNomb; nota: TMatNotas; prom: TVectProm; numAl: Integer;

begin leeDator (alumno, nota, numAl); calcularProm (nota, numAl, prom); ordenar (alumno, nota, prom, numAl); imprimir (alumno, nota, prom, numAl);

end. Los procedimientos parar leer e imprimir los datos son simples por lo que los presentaremos de inmediato.

procedure leeDator ( var alumno: TVectNOmb; var nota: TMatNotas; var numAl: Integer);

var arch: Text; n: Integer;

Page 195: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

195

begin numAl := 0; assign (arch, 'notas.txt'); reset (arch); while not eof(arch) do begin

inc (numAl); readln (arch, alumno[numAl]); for n := 1 to MAX_NOTAS do

read (arch, nota[numAl][n]); readln (arch);

end; end;

procedure imprimir ( var alumno: TVectNOmb; var nota: TMatNotas; var prom: TVectProm; var numAl: Integer);

var a, n: Integer; begin

for a := 1 to numAl do begin write (alumno[a]:30); for n := 1 to MAXNOT do

write(nota[a][n]:3); writeln(prom[a]:6:1);

end; end;

El procedimiento que evaluará el promedio de cada alumno aprovechará la definición que hicimos del arreglo en dos partes. La función que calculará el promedio recibirá como parámetro una sola fila de la matriz. El código es el siguiente:

procedure calcularProm ( var notas: TMatNotas; numAl: Integer; var prom: TVectProm);

var n: Integer; begin

for n:=1 to numAl do prom[n] := promedio(notas[n]); pasamos solo una fila

end; La función promedio recibe la fila de la matriz, esto se puede hacer porque se ha definido el tipo de dato correspondiente a la fila (TVectNotas).

function promedio (var notas: TVectNotas): Real; var n, suma, menor: Integer;

prom, pP, pL: Real; begin

suma := 0; menor := 21; promedio de prácticas for n := 1 to 4 do begin

suma := suma + notas[n]; if notas[n] < menor then menor := notas[n];

end;

Page 196: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

196

pP := (suma-menor)/3; suma := 0; menor := 21; promedio de laboratorio for n := 5 to 10 do begin

suma := suma + notas[n]; if notas[n] < menor then menor := notas[n];

end; pL := (suma-menor)/5; prom := 0.2*pP + 0.2*pL + 0.3*notas[11] + 0.3*notas[12]; promedio := prom;

end; El procedimiento que ordenará los datos, también aprovechará la definición en dos etapas de la matriz. A la hora de intercambiar los datos, el código se simplificará como se ve a continuación:

procedure ordenar ( var alumno:TVectNOmb; var nota:TMatNotas; var prom: TVectProm; var numAl: INteger);

var i, j: Integer; auxNomb: String; auxProm: Real; auxVect: TVectNotas; definimos una fila auxiliar

begin for i := 1 to numAl-1 do

for j := i+1 to numAl do if prom[i]<prom[j] then begin

auxNomb := alumno[i]; alumno[i] := alumno[j]; alumno[j] := auxNomb;

auxProm := prom[i]; intercambiamos las filas en bloque prom[i] := prom[j]; prom[j] := auxProm;

auxVect := nota[i]; nota[i] := nota[j]; nota[j] := auxVect;

end; end;

Al ejecutar el programa se obtendrá lo siguiente.

Page 197: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

197

Pupiletras En el tercer ejemplo vamos a crear un programa que permita ubicar palabras en una matriz, esta matriz almacenará letras de modo que simule el conocido juego denominado “pupiletras” o “sopa de letras”.

Por ejemplo se quiere ubicar las palabras PASCAL, CATOLICA, ALUMNO, ENTERO, PLUMA, BYTE, WHILE en el siguiente tablero:

1 2 3 4 5 6 7 8 9 10 1 Q W E R T Y E U I P 2 P K A U V W G T K D 3 L A C S A P F A Y R 4 J H E U C U L L I B 5 O X U W I Q F U I O 6 B Y T A L S T M M U 7 K Y A M O H J N O A 8 B Q E N T E R O D P 9 H Y U I A R S T H R 10 X C A Z C A U C V Y

El programa deberá buscar las palabras y emitir un reporte en el que se indique la coordenada de la primera letra de la palabra en el tablero, y la dirección en que se encuentra la palabra. El reporte será similar al siguiente:

Palabra Fila Columna Dirección

PASCAL 3 6 Hacia la Izquierda CATOLICA 10 5 Hacia Arriba ALUMNO 3 8 Hacia Abajo ENTERO 8 3 Hacia la Derecha PLUMA 3 6 Hacia Abajo-Derecha PROGRAMA No se encontró BYTE 4 10 Hacia Arriba-Izquierda WHILE No se encontró

Los datos se tomarán de dos archivos de texto, uno que contendrá las letras, y el otro las palabras.

Lo primero que se debe hacer es definir cómo estarán estructurados los archivos, se debe buscar una forma sencilla de modo que la lectura de datos no sea una traba en el desarrollo del programa. En este sentido proponemos que el primer archivo sea similar al siguiente:

En el archivo se colocará en la primera línea el número de filas y columnas que tendrá el tablero, y a continuación, en las siguientes líneas se colocan las letras. Estas últimas se colocan en filas, tantas letras en una fila como columnas se hayan definido en el inicio del archivo. No se colocarán espacios entre las letras, ya que los espacios tendrían que ser leídos y no nos servirán luego; también porque en la lectura de caracteres no se requiere que éstos estén separados por espacios como si sucede con la lectura de números.

Page 198: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

198

El archivo que contendrá las palabras es muy sencillo ya que se colocarán cada palabra en una línea independiente, como se muestra a continuación:

Lo que sigue ahora es plantear los tipos de datos que usaremos para modelar nuestros datos. Para las letras usaremos un arreglo de dos dimensiones de tipo Char, mientras que para las palabras un arreglo unidimensional también de tipo Char, en este último caso no se usará el tipo String porque vamos a tener que trabajar con cada uno de las letras que conforman la palabra y no con la palabra en conjunto; como aun no se ha desarrollado el tema de manejo de cadenas de caracteres, manejaremos las cadenas como arreglos. En este caso, como la matriz de caracteres que usaremos para las letras la definiremos en dos tiempos, emplearemos el tipo que define una fila de la matriz como tipo de dato para manejar las palabras.

A continuación presentamos el programa:

program pupiletras; const Max = 50; type TVector = array [1..Max] of Char;

TVector se empleará tanto en la matriz como en las palabras TMatriz = array [1..Max] of TVector;

var letras: TMatriz; nFil, nCol: Integer;

begin leerLetras(letras, nFil, nCol); imprimeLetras(letras, nFil, nCol); buscarPalabras(letras, nFil, nCol);

end. La lectura de las letras es muy simple, por lo que la presentaremos en seguida:

procedure leerLetras(var letras: TMatriz; var nFil, nCol: Integer); var nombArch: String; arch: Text; fil, col: Integer; begin

write('Ingrese el nombre del archivo con el pupiletras: '); readln(nombArch); assign(arch, nombArch); reset(arch); readln(arch, nFil, nCol); for fil:=1 to nFil do begin

for col:= 1 to nCol do read(arch, letras[fil][col]);

readln(arch); end;

end;

Page 199: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

199

En el caso de la impresión de las letras leídas, es igual de sencilla, pero en este caso el código las mostrará centrada en la pantalla.

procedure imprimeLetras ( var letras: TMatriz; nFil, nCol: Integer);

var fil, col: Integer; begin

writeln; writeln('PUPILETRAS':45); 45 permite centrar el título en una ventana de 80 caracteres for fil:=1 to nFil do begin

write(' ':39-nCol); esto centra las letras for col:= 1 to nCol do

write(letras[fil][col]:2); writeln;

end; writeln;

end; El módulo que buscará las palabras se centrará en la lectura de las palabras del archivo de textos y su localización en el pupiletras.

procedure buscarPalabras ( var letras: TMatriz; var nFil, nCol: Integer);

var nombArch: String; arch: Text; palabra: TVector; Se usa el tipo definido para la fila de la matriz

begin write('Ingrese el nombre del archivo con las palabras: '); readln(nombArch); assign(arch,nombArch); reset(arch); writeln; while not eof(arch) do begin

leePalabra(arch, palabra); localizar(palabra, letras, nFil, nCol);

end end;

La lectura de la palabra se hará caracter por caracter del archivo, colocándose las letras en el arreglo. Aquí se presenta un problema, lo que pasa es que luego de colocar los caracteres en el arreglo, cuando queramos procesar la palabra, ¿cómo sabremos hasta dónde serán validos los caracteres en el arreglo?, podríamos hacer como en todos los casos en los que hasta ahora manejamos un arreglo de una dimensión, esto es definir una variable como numDat y allí colocar la cantidad de letras leídas, sin embargo emplearemos una forma diferente.

El método que emplearemos en este problema es el que se utiliza en el lenguaje de programación C para manipular las cadenas de caracteres, esto es colocar al final de las letras leídas un caracter, que no se use con frecuencia, para delimitar los caracteres

Page 200: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

200

válidos de los que no lo son. Este caracter será, al igual que en el lenguaje de programación C, el caracter cuyo código ASCII es cero (#0).

Esta forma de manejar las cadenas hará que los algoritmos que planteemos ya no se basen en llevar un contador que compararemos con la cantidad de caracteres que tenga el arreglo, si no que procesaremos cada caracter del arreglo hasta encontrar el caracter #0.

procedure leePalabra ( var arch: Text; var palabra: TVector);

var i: Integer; begin

i:=0; while not eoln(arch) do begin

inc(i); read(arch, palabra[i]);

end; palabra[i+1] := #0; indica que se terminaron las letras leídas readln(arch);

end; El proceso de localizar una palabra en el pupiletras se hará en dos etapas, lo primero que debemos hacer es recorrer la matriz comparando el elemento de la matriz con la primera letra de la palabra, en el momento que se ubique una igualdad, empezaremos la segunda etapa, que consistirá en comparar la palabra con letras de la matriz en cada una de las direcciones en se puede encontrar, hacia la izquierda, derecha, hacia abajo, hacia arriba, diagonal hacia abajo y hacia la derecha, etc.

El recorrido no se realizará mediante un doble for, esto porque debemos parar el proceso de búsqueda a penas encontremos una palabra en el pupiletras. Esto hará más eficiente el proceso ya que no tiene sentido seguir recorriendo la matriz luego de haber encontrado la palabra.

procedure localizar( var palabra: TVector; var letras: TMatriz; nFil, nCol: Integer);

var f, c: Integer; direccion: String;

begin f := 1; direccion := 'NSE'; indica: “No Se Encontró” repeat

c := 1; repeat if letras[f][c] = palabra[1] then

direccion := buscaDireccion ( palabra, letras, nFil, nCol, f, c);

if direccion = 'NSE' then inc(c); until (direccion <> 'NSE') or (c > nCol);

Page 201: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

201

if direccion = 'NSE' then inc(f); until (direccion <> 'NSE') or (f > nFil); imprime(palabra, f, c, direccion);

end; La función que busca la dirección de la palabra, caracter por caracter, deberá hacerlo en las ocho direcciones posibles, esto es: arriba, abajo, izquierda, derecha, diagonal hacia la derecha arriba y abajo, y diagonal derecha arriba y abajo.

Para buscar la palabra en una dirección dada, fijaremos primero una dirección, por ejemplo derecha, según esto deberemos tomar uno a uno los caracteres de la matriz que se encuentran a la derecha del elemento que coincide con la primera letra de la palabra buscada y compararlos con los de la palabra. Teniendo la fila y columna del elemento, para hacer esta tarea sólo tendremos que ir incrementado la columna. Si la dirección elegida fuera a la izquierda, el proceso sería similar al anterior pero deberíamos ir decrementando la columna. En el caso de elegir una dirección diagonal, por ejemplo derecha-arriba, el proceso deberá ir incrementando la columna y decrementando la fila.

El siguiente gráfico muestra todas estas alternativas o direcciones, el incremento o decremento se indica con un 1 ó -1 respectivamente en las columnas (DH-dirección horizontal) o filas (DV-dirección vertical).

DH=-1 DV=-1 DH= 0

DV= 1 DH= 1 DV=-1

DH=-1 DV= 0 DH= 1

DV= 0

DH=-1 DV= 1 DH= 0

DV= 1 DH= 1 DV= 1

Si nos fijamos un poco en los valores dados en la gráfica, veremos que DH toma los valores de -1, 0 y 1, y que DV también toma los valores -1, 0 y 1. De acuerdo a esto, el módulo que busca la dirección en que puede estar la palabra, sólo debería definir dos variables (dh y dv) y mediante un doble for generar los valores que permitan definir la dirección a buscar. Una vez fijados estos valores la nueva celda a analizar se determinará sumando estos valores a fila y columna de la celda inicial. Esto se aprecia en el código siguiente:

for dv := -1 to 1 do for dh := -1 to 1 do

if (dv <> 0) or (dh <> 0) then begin f := f + dv; c := c + dh; …

El if es para descartar la dirección (0,0) que harían que f y c no cambien.

Ahora, como lo que queremos es un proceso rápido, debemos parar las iteraciones a penas encontremos la palabra en una dirección, por eso en el código de la función no se usará for si no un ciclo con salida controlada.

El código es el siguiente:

Page 202: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

202

function buscaDireccion ( var palabra: TVector; var letras: TMatriz; nFil, Ncol, f, c: Integer): String;

var dv, dh: Integer; direccion: String; begin

direccion:= 'NSE'; dv := -1; repeat

dh := -1; repeat

if (dv <> 0) or (dh <> 0) then direccion := verificaDireccion (palabra, letras,

nFil, nCol, f, c, dv, dh); inc(dh);

until (dh > 1) or (direccion <> 'NSE'); inc(dv);

until (dv > 1) or (direccion <> 'NSE'); buscaDireccion := direccion;

end; Una vez que se fija la dirección en que se va a buscar la palabra (dh y dv), se debe recorrer la matriz en esa dirección comparando las letras de las celdas de la matriz con las letras de la palabra. Como ya hemos indicado, esta tarea consiste sólo en ir sumando los valores de dh y dv a la fila y columna inicial.

Este recorrido debe terminar de manera eficiente, esto quiere decir que no debemos recorrer necesariamente toda la palabra para saber si la palabra coincide o no con los caracteres de la matriz en la dirección dada. Si al avanzar en la matriz nos damos cuenta que nos salimos de los límites de la matriz, el proceso debe terminar en ese instante, sabiendo que no está la palabra en esa dirección. Si un carácter de la matriz no coincide con el de la palabra, también el proceso debe terminar. Por último, si llegamos al carácter #0 habremos corroborado que todos los caracteres de la palabra se encuentran en la matriz en la dirección dada. La función verificaDireccion realiza esta tarea.

function verificaDireccion ( var palabra: TVector; var letras: TMatriz; nFil, Ncol, fil, col, dv, dh: Integer): String;

var k: Integer; begin

k := 1; repeat

inc(k); fil := fil + dv; col := col + dh;

until not (fil in [1..nfil]) or not (col in [1..ncol] ) or (palabra[k] = #0) or (letras[fil][col] <> palabra[k]);

if palabra[k] = #0 then verificaDireccion := strDireccion (dv, dh)

Page 203: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

203

else verificaDireccion := 'NSE';

end; En este punto sabremos que hemos o no encontrado la palabra a partir de una celda y en una dirección dada. Sin embargo, la dirección la tenemos definida como un par de valores entre -1 y 1. Necesitamos traducir ese código numérico a una frase que el usuario entienda, por eso la función strDirección realizará esa tarea:

function strDireccion (dv, dh: Integer): String; var dir: String; begin

case dv of -1: begin

case dh of -1: dir := 'Hacia Arriba - Izquierda'; 0: dir := 'Hacia Arriba'; 1: dir := 'Hacia Arriba- Derecha';

end; end;

0: begin case dh of

-1: dir := 'Hacia la Izquierda'; 1: dir := 'Hacia la Derecha';

end; end;

1: begin case dh of

-1: dir := 'Hacia Abajo - Izquierda'; 0: dir := 'Hacia Abajo'; 1: dir := 'Hacia Abajo - Derecha';

end; end;

end; strDireccion:= dir;

end; Finalmente, teniendo ya toda la información, la podemos mostrar en la pantalla. Tsta tarea la realiza el módulo imprime:

procedure imprime ( var palabra: TVector; f, c: Integer; dir: String); var i, j: Integer; begin

write ('Palabra: '); i := 1; while palabra[i] <> #0 do begin

write (palabra[i]); inc(i);

end;

Page 204: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

204

for j := 1 to 15-i do write(' '); if dir = 'NSE' then

writeln(' No se encontro') else

writeln(' Posicion: ', f:2,' , ', c:2,' ', dir) ; end;

La ejecución de este programa dará la siguiente respuesta:

El Juego de la vida Una bonita aplicación que maneje arreglos de más de una dimensión es la implementación de lo que se denomina “el juego de la vida”, este juego, fue diseñado por los años 70 por un matemático británico llamado John Horton Conway, de lo que se trata es de simular la evolución de organismos o células en un medio. Esta simulación permite mostrar una serie de patrones muy interesantes que han sido estudiados por mucho tiempo.

El medio se simula mediante un tablero con una serie de casillas, cada casilla puede estar vacía o contener un "organismo". Un segmento del tablero puede ser como se muestra a continuación:

La siguiente generación de organismos se determina de acuerdo a reglas muy sencillas que a continuación se detallan:

a. Nacimiento: Un organismo nacerá en cada celda vacía del tablero que tenga exactamente tres vecinas.

b. Muerte: Un organismo que tenga cuatro o más organismos como vecinos morirá por apiñamiento. Un organismo con menos de dos vecinos morirá de soledad.

c. Supervivencia: Un organismo con dos o tres vecinos sobrevivirá hasta la siguiente generación.

Un organismo es influenciado sólo por los organismos de su generación, no puede ser influenciado por los organismos que aparecerán o desaparecerán en la próxima generación.

A continuación se muestran cómo quedarían los organismos en dos generaciones siguientes al ejemplo anterior:

Page 205: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

205

Generación 2 Generación 3

El programa que desarrollaremos leerá de un archivo de textos la distribución de una generación de organismos, luego se solicitará al usuario el número de generaciones que desea se formen a partir de la inicial. Finalmente el programa deberá mostrar en pantalla cada una de las generaciones.

En la solución al problema, primero definiremos la estructura de datos que manejará el medio. Como los organismos pueden estar en dos estados únicamente, esto es, pueden estar vivos o pueden estar muertos, hemos pensado en una matriz de valores lógicos (boolean) en dónde true significará que el organismo está vivo y false que está muerto. Sin embargo, habrá que tener en cuenta que el archivo de textos que se use para el ingreso de los datos no podrá tener esos valores, ya que en Pascal no se pueden leer datos de tipo boolean. Por eso es que decidimos que el archivo de textos sea de caracteres, la lectura será muy simple, y transformar el caracter leído en un valor lógico también es algo sencillo, y para hacerlo más sencillo aun definiremos en el archivo como un organismo vivo al caracter 0 (cero) y cualquier otro caracter significará un organismo muerto.

Otra cosa que debemos definir es la estrategia con que vamos a atacar el problema. La solución debe orientarse a recorrer el arreglo y en cada celda se deberá calcular la cantidad de vecinos vivos que haya, luego se determinará si en la celda nacerá un organismo, si sobrevivirá morirá el que está allí. Aquí se presenta un problema, si tomamos a la ligera la solución, el programa crecerá en forma desmedida, nos referimos a que en todas las celdas no se pueden contar los vecinos de la misma manera, si vemos la figura siguiente, veremos en un celda del interior de la matriz pueden haber hasta 8 vecinos, pero en el borde sólo pueden haber un máximo de cinco, además es muy diferente contar las celdas vecinas del borde superior que del borde lateral o inferior. Por último están las esquinas que sólo pueden tener hasta 3 vecinos.

Observe que se trata de nueve casos diferentes, por lo que tendríamos que escribir código para cada uno haciendo que éste se extienda mucho.

Sin embargo, como hemos dicho, tenemos que plantear una estrategia que elimine los casos y deje una sola alternativa. ¿Cómo se hará esto? Pues bien, haciendo que todas las

Page 206: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

206

celdas tengan la misma cantidad máxima de vecinos. Esto se conseguirá definiendo la matriz de una manera particular; dándole al tipo de dato una fila más al inicio (fila 0) y una fila más al final (fila n+1) del que se pretendía dar inicialmente (n). De la misma manera se le dará una columna más al inicio (columna 0) y una al final (columna n+1). Luego, cuando se lean los datos se forzará que éstos no sean colocados en estas filas y columnas adicionales, quedándose éstas con organismos muertos. Cuando se recorra la matriz no se tomarán estas caldas, sin embargo a la hora de contar los vecinos sí, pero como los organismos en estas celdas estarán muertos, no alterará la cuenta pero si eliminará los casos. Observe los casos en la siguiente figura:

fila 0

fila n+1

Se puede apreciar que cualquier celda que analicemos se encuentra bajo las mismas condiciones, a la hora de contar los vecinos, se analizará siempre 8 celdas, con la salvedad que en las celdas de los bordes no hay organismos.

El archivo de datos debe permitir colocar la información de los organismos de un medio de modo que sea lo más sencillo posible para el usuario, como ya hemos indicado estará compuesto por caracteres, de los cuales el carácter ‘0’ será considerado como un organismo vivo y cualquier otro uno muerto. En el archivo no se colocará la cantidad de filas y columnas que tendrá el medio que queremos simular, como hemos hecho hasta ahora en los anteriores ejemplos, esto porque queremos que el usuario se dedique sólo a “dibujar” el medio; imagínese lo tedioso que sería que luego de “dibujarlo” tenga que contar la cantidad de filas y columnas que colocó. Por eso el programa será el que cuente o calcule estos datos.

Un ejemplo del archivo de datos que se puede emplear será el siguiente:

Observe que hemos empleado puntos para indicar los organismos muertos, también vea lo complicado que sería contar las filas y columnas luego de haber colocado los caracteres

columna 0 columna n+1

Page 207: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

207

en el archivo, o habiendo definido el numero de filas y columnas que queremos usar, lo difícil que sería colocar los caracteres de modo de coincidir con esos valores.

Según lo planteado hasta ahora, el programa se diseñará de la siguiente manera:

program juegoDeLaVida; uses crt; const MAX = 50; type TVector = array [0..MAX+1] of Boolean;

TMatriz = array [0..MAX+1] of TVector; var organismo: TMatriz;

nFil, nCol, numGen, ng: Integer; begin

inicializaMatriz(organismo); leerGeneracion(organismo, nFil, nCol); write('Ingrese el numero de generaciones que quiere formar: '); readln(numGen); for ng := 0 to numGen do begin

mostrarGeneracion(organismo, nFil, nCol); nuevaGeneracion(organismo, nFil, nCol); writeln('Generacion ', ng); writeln('Presione el ENTER para ver la siguiente generacion'); readln; clrscr; //borramos la pantalla, se define en crt

end; end.

Observe el uso de la biblioteca de funciones crt, en Pascal estas bibliotecas se llaman unidades (unit). Esto se realiza porque no queremos que la pantalla se llene con un listado interminable con todas las generaciones formadas. El programa mostrará una generación y luego pedirá al usuario que presione la tecla ENTER para ver la siguiente generación, en ese instante el programa borrará la pantalla para que aparezca la nueva generación en el mismo lugar que la anterior. Para borrar la pantalla usamos el procedimiento clrscr (clear screen). Cabe mencionar que en la biblioteca crt se encuentran todas las funciones y procedimientos que permiten controlar la pantalla o ventana de salida de datos.

El programa primero inicializará la matriz para garantizar que todas las celdas, incluso la de los bordes tengan organismos muertos.

procedure inicializaMatriz(var mat: TMatriz); var f, c: Integer; begin

for f:= 0 to MAX+1 do for c := 0 to MAX+1 do

mat[f][c] := false; end;

El procedimiento leerGeneracion tendrá la tarea, como hemos indicado anteriormente, de leer los caracteres, convertirlos en un valor lógico y asignarlos a la matriz. Adicionalmente deberá contar el número de filas y columnas que manejará la matriz:

Page 208: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

208

procedure leerGeneracion ( var organismo: TMatriz; var nFil, nCol: Integer);

var nombArch: String; arch: Text; f, c: Integer; valor: Char;

begin write('Ingrese el nombre del archivo con la 1ra. generacion: '); readln(nombArch); assign(arch, nombArch); reset(arch); nFil := 0; nCol := 0; f := 0; while not eof(arch)do begin

inc(f); inc(nFil); //Aquí contamos las filas de la matriz c:=0; while not eoln(arch) do begin

if f = 1 then inc(nCol); //Aquí contamos las columnas inc(c); read(arch, valor); organismo[f][c] := valor = '0'; // si valor es igual a ‘0’ se asigna true, de lo contrario false

end; readln(arch);

end; close(arch);

end; El procedimiento que imprime las generaciones (mostrarGeneracion) es muy sencillo, sin embargo aquí debemos tener en cuenta que debemos transformar los valores lógicos de la matriz en caracteres. En este caso se colocará un espacio en blanco para los valores falso y el caracter (ASCII 1) para los valores verdadero. Lo mostramos a continuación:

procedure mostrarGeneracion ( var organismo: TMatriz; nFil, nCol: Integer);

var f, c: Integer; begin

for f := 1 to nFil do begin for c := 1 to nCol do

write(chr( ord(organismo[f][c]) + ord(' ')*ord(not organismo[f][c])));

// #1 (1) true, ‘ ‘ false writeln;

end; end;

Para crear la nueva generación debemos tener en cuenta que los organismos se crean, se mantienen vivos o mueren en función de los organismos que existe en la actual generación, y no se ven influenciados por la nueva generación. Por lo que la nueva generación se debe determinar en una matriz auxiliar y no en la misma en la que está la

Page 209: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

209

actual. Una vez concluida la generación se copiará íntegramente en la matriz original. Veamos esto:

procedure nuevaGeneracion ( var organismo: TMatriz; nFil, nCol: Integer);

var orgAux: TMatriz; numVecinos, f, c: Integer;

begin inicializaMatriz(orgAux); for f := 1 to nFil do begin

for c := 1 to nCol do begin numVecinos := cuentaVecinos(f, c, organismo); if not organismo[f][c] then

if numVecinos = 3 then orgAux[f][c] := true else orgAux[f][c] := false

else if numVecinos in [2, 3] then orgAux[f][c] := true

else orgAux[f][c] := false; end;

end; organismo := orgAux;

end; Finalmente la cuenta de vecinos se hará teniendo en cuenta el mismo criterio empleado en el “pupiletras” para tomar los caracteres en las diferentes direcciones. Aquí hay que aclarar que en el código no se tomará en cuenta la verificación que se debe hacer para evitar salirnos de los límites de la matriz ya que la matriz ha sido definida con filas adicionales en los bordes.

function cuentaVecinos ( f, c: Integer; var organismo: TMatriz): Integer;

var cont, i, j: Integer; begin

cont := 0; for i := -1 to 1 do

for j := -1 to 1 do if (i <> 0) or (j <> 0) then

cont := cont + ord(organismo[f+i][c+j]); cuentaVecinos := cont;

end; La ejecución del programa se verá como a continuación se presenta:

Page 210: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

210

Page 211: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

211

CAPÍTULO 9: Manejo de cadenas de caracteres

Definición Una cadena de caracteres es un conjunto de caracteres que se almacenan en un arreglo unidimensional, los diferentes lenguajes de programación proveen una serie de funciones y procedimientos que permiten manejar textos en un programa de manera sencilla a través de las cadenas de caracteres.

A pesar que las cadenas de caracteres son simplemente arreglos, se requieren otros elementos para poderlos manejar, esto sencillamente porque si por ejemplo definimos un arreglo de 20 elementos y allí colocamos los caracteres que formen la palabra ‘Juan Pérez’, podemos darnos cuenta que colocar los caracteres es una tarea sencilla, pero a la hora de querer recuperarlos, por ejemplo imprimirla, ¿cómo lo hacemos? La frase ‘Juan Pérez’ no tiene 20 caracteres, sólo tiene 10, entonces, ¿cómo hacemos para imprimir sólo 10 caracteres y no los 20 con los que fue definido el arreglo?. Uno podría decir que ya que estamos trabajando con arreglos, por qué no definimos una variable auxiliar para manejar su tamaño como se hace con los arreglos comunes, no obstante esto se hace poco práctico, sobre todo cuando estemos hablando de varias cadenas de caracteres, como por ejemplo manejar un conjunto de nombres para ordenarlos, en este caso se tendría que definir un arreglo auxiliar de enteros para manejar las longitudes de cada nombre.

Por esta razón muchos lenguajes de programación definen reglas y modos para declarar un variable como cadena de caracteres para que su manejo sea más práctico. Analicemos a continuación algunos de estos casos.

El lenguaje Pascal, como se ha visto en los capítulos anteriores, una cadena de caracteres se define mediante la palabra String, esto define un arreglo de 256 elementos, cuyos índices van del 0 al 255, cuando se hace una asignación a la variable, internamente el sistema coloca los caracteres a partir del segundo elemento del arreglo (con índice 1), y es en la primera posición del arreglo en donde coloca la cantidad de caracteres que asignó. Por ejemplo, si se quiere almacenar la siguiente cadena de caracteres:

‘Ana Cecilia Roncal Neyra’

El sistema colocará los caracteres en el arreglo de la siguiente manera: ↑ A n a C e c i l i a R o n c a a l N e y r a … 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 1 2

Nótese que en la posición 0 del arreglo se coloca el caracter ‘↑’ que corresponde al código 24 en la tabla ASCII, precisamente este valor coincide con la cantidad de caracteres colocados en el arreglo.

Cualquier operación que se realice sobre la cadena afectará en forma automática la posición cero del arreglo de modo que en cada momento se tenga la cantidad de caracteres válidos almacenados.

Esta forma práctica de manejar las cadenas de caracteres elimina el uso de la variable auxiliar, sin embargo tiene una limitante muy fuerte, es el hecho que bajo este esquema

Page 212: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

212

sólo se pueden manejar cadenas que tengan más de 255 caracteres, esto simplemente porque en la posición cero del arreglo hay un elemento de un byte de tamaño, por lo tanto allí no puede caber un valor mayor a 255.

Versiones más modernas de Pascal, por ejemplo en el Object Pascal esta estructura ha sido modificada para manejar cadenas de mayor tamaño, sin embargo no hay documentación al respecto sobre su estructura interna, esto se ha hecho con la finalidad que el programador se preocupe más por entender cómo se manejan las cadenas de caracteres sin preocuparse por su representación interna.

En el caso del lenguaje C, se ha encontrado una forma muy práctica y sencilla de definir y manejar las cadenas de caracteres, en primer lugar una cadena de caracteres se define de igual manera cómo se define un arreglo unidimensional, no hay diferencias. En segundo lugar la manera de controlar la cantidad de caracteres válidos colocados en el arreglo se hace mediante un ‘caracter de terminación’, estos es, se ha elegido un caracter dentro de la tabla de caracteres que no se utilice dentro de cualquier texto y se coloca en la posición del arreglo inmediata al lado del último carácter. Este caracter es el caracter cuyo código ASCII es cero, conocido como ‘carácter nulo’. Por ejemplo, si se quiere almacenar la siguiente cadena de caracteres:

‘Ana Cecilia Roncal Neyra’

El sistema colocará los caracteres en el arreglo de la siguiente manera: A n a C e c i l i a R o n c a a l N e y r a ‘\0’ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 1 2 ‘\0’ representa al caracter cuyo código ASCII es cero

Esta forma de definir las cadenas de caracteres permite que prácticamente no haya límites en cuanto a la cantidad de caracteres que se pueda manejar en una variable.

La forma cómo el lenguaje de programación define sus cadenas de caracteres se debe tomar en cuenta a la hora de plantear el algoritmo de solución del un problema que maneje una cadena caracter por caracter, esto debido a que mientras en Pascal esto se hace en función de la longitud de la cadena, en C se debe hacer hasta que se encuentre el caracter de terminación. El no tomar en cuentea este detalle, hará que el proceso se vuelva ineficiente.

Declaración de una cadena de caracteres Existen dos formas de definir una cadena de caracteres en Pascal, las mostramos a continuación:

var cad1: string; cad2: string[20]; el valor entre los [ ] indica el número máximo de caracteres

que podrá almacenar la variable, en este caso 20

En el caso de una definición como la de la segunda línea, se recomienda que se declare un tipo de dato con esas características y luego definir las variables en función de ese tipo. La razón es que esa forma de declaración se considera como una descripción de tipo, al igual que cuando se define un arreglo, y como ya hemos explicado anteriormente,

Page 213: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

213

el Pascal no permite emplear esa forma en la declaración de parámetros en los procedimientos y funciones.

Por lo tanto se debe realizar esta tarea de la siguiente manera:

type St20 = String[20];

Cuando se quiere declara la variable se debe hacer de la siguiente manera:

var cad: St20;

En el caso de declarar un procedimiento o función:

procedure proced(cad: String[20]); Esto produce un ERROR de compilación procedure proced(cad: St20); Esto es CORRECTO

Funciones y procedimientos que manejan cadenas: A continuación mostraremos una lista de funciones y procedimientos dados por el Pascal para el procesamiento de textos en un programa.

FUNCIÓN LENGTH: esta función recibe como parámetro una cadena de caracteres y devuelve la cantidad de caracteres válidos que tiene la cadena. El siguiente ejemplo muestra cómo se maneja esta función.

Ejemplo: var cad: String[20]; lon: Integer; begin

writeln(' 1 2 3'); writeln(' 123456789012345678901234567890'); Lo anterior dará una idea del tamaño de los textos cad := 'Hola amigos'; lon := length(cad); Cuenta la cantidad de caracteres que hay en cad writeln('Cadena 1: ', cad); writeln('Longitud: ', lon); writeln; cad := 'Ana Cecilia Roncal Neyra'; lon := length(cad); writeln('Cadena 2: ', cad); writeln('Longitud: ', lon); writeln;

end. La ejecución de este programa tendrá una salida como la que se muestra a continuación:

Page 214: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

214

FUNCIÓN CONCAT: esta función recibe como parámetro dos o más cadenas de caracteres, la función une o concatena las cadenas en el orden en que ingresan formando una única cadena y la devuelve.

Se tiene que tener cuidado cuando la respuesta es asignada a una cadena acotada (variable definida con menos de 255 caracteres), si la concatenación es más grande que la variable que recibe la respuesta, la cadena se trunca, asignándose sólo los caracteres que puede contener la variable, esto no produce errores en la ejecución del programa.

Otra cosa que s debe tener en cuenta es que la función no agrega ni quita caracteres a la concatenación. En el siguiente ejemplo se muestra cómo trabaja esta función.

var cad1, cad2, cad3, cad4, cad5: String[50]; cad6: String[20];

begin cad1 := 'Juan'; cad2 := 'Perez'; cad3 := 'Cardenas'; cad4 := concat(cad1,cad2); writeln('Cadena 1: ', cad4); writeln; cad5 := concat('Resultado = ', cad1, ' / ', cad2, ' - ', cad3); writeln('Cadena 2: ', cad5); writeln; cad6 := concat('Resultado = ', cad1, ' / ', cad2, ' - ', cad3); writeln('Cadena 3: ', cad6);

end. La salida a la ejecución del programa será como se muestra a continuación:

Esta función trabaja de manera similar a la forma como trabaja el operados + de concatenación.

FUNCIÓN COPY: Esta función sirve para poder tomar una copia de una parte de una cadena de caracteres. La función recibe tres parámetros, el primero es la cadena de la que se quiere extraer la copia, el segundo argumento indica la posición de inicio de la copia, esta posición está dada por el índice en el arreglo dónde está el primer caracteres de la copia que se quiere extraer, y el tercer argumento indica la cantidad de caracteres a extraer a partir de la posición dada en el segundo argumento.

El ejemplo siguiente muestra estas propiedades.

program FuncionCopy; var cad1, cad2, cad3, cad4: String[30]; begin

Page 215: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

215

writeln(' 1 2 3'); writeln(' 123456789012345678901234567890'); cad1 := 'Hola amigos, como estan'; cad2 := copy(cad1, 6, 5); Copiar desde la posición 6, 5 caracteres cad3 := copy(cad1, 8, 11); Copiar desde la posición 8, 11 caracteres cad4 := copy(cad1, 1, 6); Copiar desde la posición 1, 6 caracteres writeln('Cadena 1 : ', cad1); writeln; writeln('Cadena 2 ( 6, 5): ', cad2); writeln; writeln('Cadena 3 ( 8, 11): ', cad3); writeln; writeln('Cadena 4 ( 1, 6): ', cad4);

end. La salida a la ejecución del programa será como se muestra a continuación:

En ésta y en la mayoría de funciones y procedimientos que manejan cadenas de caracteres en Pascal, no se hacen verificaciones sobre el rango en que se colocan los argumentos numéricos, esto quiere decir que si estos argumentos no son coherentes con la cadena dato, la función no enviará ninguna alerta ni cortará la ejecución del programa, siempre entregará un resultado que esté dentro de la lógica para los datos dados. Observe el ejemplo siguiente:

program FuncionCopy2; var cad1, cad2, cad3, cad4: String[40]; begin

writeln(' 1 2 3 4'); writeln(' 1234567890123456789012345678901234567890'); cad1 := 'Hola amigos, como estan'; cad2 := copy(cad1, 19, 50); Copiar desde la posición 19, 50 caracteres La posición es correcta pero el número de caracteres sobrepasa el tamaño

cad3 := copy(cad1, 35, 5); Copiar desde la posición 35, 5 caracteres La posición es sobrepasa el número de caracteres que tiene la cadena

cad4 := copy(cad1, -3, 6); Copiar desde la posición -3, 6 caracteres Ilógica la posición dada

writeln('Cadena 1 : ', cad1); writeln; writeln('Cadena 2 (19, 50): ', cad2); writeln; writeln('Cadena 3 (35, 5): ', cad3); writeln;

Page 216: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

216

writeln('Cadena 4 (-3, 6): ', cad4); end.

Veamos la salida a este programa:

FUNCIÓN POS: Esta función busca la posición en que una cadena se encuentra dentro de otra más grande, devuelve la posición del primer caracter en la cadena mayor. Si la cadena buscada se encuentra varias veces en la cadena mayor, la función siempre devolverá la primera ocurrencia.

La función pos tiene dos argumentos, el primero es la cadena que se quiere localizar, la segunda es la cadena en donde se va a buscar la primera. La función devuelve un número entero con la posición encontrada, si no la encuentra devuelve cero.

El programa siguiente muestra su funcionamiento

program FuncionPos; var cad: String[50];

p1, p2, p3: Integer; begin

writeln(' 1 2 3 4'); writeln(' 1234567890123456789012345678901234567890'); cad := 'Valentina busca a Naomi en el parque'; p1 := pos('Naomi', cad); Busca la posición de 'Naomi en la cadena p2 := pos('Juan' , cad); Busca la posición de 'Juan' en la cadena p3 := pos('NAOMI', cad); La ocurrencia debe darse de manera exacta writeln('Cadena: ', cad); writeln; writeln('Posicion de Naomi: ', p1:2); writeln; writeln('Posicion de Juan: ', p2:2); writeln; writeln('Posicion de NAOMI: ', p3:2);

end. Veamos lo que muestra el programa

PROCEDIMIENTO INSERT: Este procedimiento modifica una cadena dada como argumento, la modificación consiste en insertar otra cadena en ella a partir de una posición dada. El procedimiento tiene tres parámetros, el primero es la cadena que se

Page 217: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

217

quiere insertar, el segundo, parámetro por referencia, es la cadena que se quiere modificar, y el tercero es la posición donde se quiere hacer la inserción.

En el programa siguiente se aprecian estas características.

program procedimientoInsert; var cad: String[80]; begin

writeln(' 1 2 3 4 5 6'); writeln(' 123456789012345678901234567890123456789012345678901234567890'); cad := 'Naomi estudia con Valentina'; writeln('Cadena Inicial: ',cad); writeln; insert('Ciencias de la Computacion ', cad, 15); writeln('Cadena Final : ',cad);

end.

La salida de este programa se muestra a continuación:

Al igual que en casos anteriores se debe tener en cuenta que si la cadena que se desea modificar es de menor tamaño que la longitud de la cadena que se obtendría como respuesta, el resultado se truncará. Por ejemplo:

program procedimientoInsert; var cad: String[30]; --- begin

writeln(' 1 2 3 4 5 6'); writeln(' 123456789012345678901234567890123456789012345678901234567890'); cad := 'Naomi estudia con Valentina'; writeln('Cadena Inicial: ',cad); writeln; insert('Ciencias de la Computacion ', cad, 15); writeln('Cadena Final : ',cad);

end. El resultado es el siguiente

Finalmente con este procedimiento tampoco se mostrarán mensajes o se truncará el programa si se dan datos incoherentes, veamos esto.

program ProcedimientoInsert2; var cad, cad2: String[80]; begin

Page 218: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

218

writeln(' 1 2 3 4 5 6'); writeln(' 123456789012345678901234567890123456789012345678901234567890'); cad := 'Naomi estudia con Valentina'; writeln('Cadena Inicial: ',cad); writeln; insert('Ciencias de la Computacion ', cad, 50); Aquí la cadena se inserta al final writeln('Cadena Final : ',cad); writeln; writeln; cad2 := 'Naomi estudia con Valentina'; writeln('Cadena Inicial 2: ',cad2); writeln; insert('Ciencias de la Computacion ', cad2, -5); Aquí la cadena se inserta al inicio writeln('Cadena Final 2 : ',cad2);

end.

PROCEDIMIENTO DELETE: Este procedimiento también modifica una cadena ingresada como parámetro, la modificación consiste en eliminar un grupo de caracteres de la cadena. El procedimiento tiene 3 parámetros, el primero es la cadena que se quiere modificar, el segundo es un valor entero que indica la posición en el arreglo desde donde se eliminarán los caracteres, el último, también de valor entero, indica la cantidad de caracteres que se van a eliminar desde la posición indicada.

El programa siguiente muestra cómo se maneja este procedimiento.

program ProcedimientoDelete; var cad : String[50]; begin

writeln(' 1 2 3'); writeln(' 123456789012345678901234567890'); cad := 'Hola a todos mis amigos'; writeln('Cadena Inicial: ',cad); writeln; delete(cad, 6, 12); writeln('Cadena Final : ',cad);

end. La salida es la siguiente.

Page 219: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

219

Como en los casos anteriores datos incoherentes harán que la respuesta se adapte a la cadena ingresada como parámetro sin emitir mensajes de error o truncar el programa.

program ProcedimientoDelete2; var cad, cad2, cad3: String[50]; begin

writeln(' 1 2 3'); writeln(' 123456789012345678901234567890'); cad := 'Hola a todos mis amigos'; writeln('Cadena Inicial 1: ',cad); delete(cad, 10, 120); Borra todos los caracteres desde la posición 10 writeln('Cadena Final 1 : ',cad); writeln; writeln; cad2 := 'Hola a todos mis amigos'; writeln('Cadena Inicial 2: ',cad2); delete(cad2, 60, 7); No borra los caracteres writeln('Cadena Final 2 : ',cad2); writeln; writeln; cad3 := 'Hola a todos mis amigos'; writeln('Cadena Inicial 3: ',cad3); delete(cad3, -6, 7); No borra los caracteres writeln('Cadena Final 3 : ',cad3);

end. Veamos la salida.

PROCEDIMIENTO VAL: Este procedimiento sirve para transformar una cadena de caracteres que represente un valor numérico en un número propiamente dicho. Expresado en otras palabras, un número que está representado como una cadena de caracteres podrá ser asignado a una variable numérica, entera o real.

El procedimiento tiene tres argumentos, el primero es la cadena de caracteres que se quiere convertir, el segundo argumento es la variable numérica que recibirá el valor convertido, esta variable puede ser de tipo entero o real, la respuesta que de este procedimiento cambiará en función del tipo de variable que se emplee aquí. El tercer argumento es opcional, esto quiere decir que se puede o no utilizar en el procedimiento, este argumento debe ser de tipo entero y en él se almacenará un valor que nos indique si la cadena a transformar representa o no un valor numérico; si el valor asignado es cero quiere decir que la transformación ha sido exitosa, si el valor es diferente de cero

Page 220: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

220

indica que la cadena no representa un dato numérico, el valor asignado indicará la posición del caracter en la cadena que no pudo convertir, en estos no se asignan valores al segundo argumento.

En el programa siguiente veremos varios casos en el funcionamiento de este procedimiento.

program ProcedimientoVal; var cadI, cadR, cad2, cad3, cad4, cad5: String[20];

valorI, codErr, valor2: Integer; valorR, valor3, valor4, valor5: Real;

begin cadI := '437'; cadR := '629.34'; writeln('1ra Parte:'); writeln('Valor entero asignado a variable entera'); val(cadI, valorI, codErr); writeln('CadenaI: ', cadI:15, ' ValorI: ', valorI:8, ' Error: ', codErr); writeln; writeln('Valor real asignado a variable real'); val(cadR, valorR, codErr); writeln('CadenaR:', cadR:15, ' ValorR:', valorR:8:3, ' Error: ', codErr); writeln; writeln; writeln('2da Parte:'); writeln('Valor entero asignado a variable real'); val(cadI, valorR, codErr); writeln('CadenaI:', cadI:15, ' ValorR:', valorR:8:3, ' Error: ', codErr); writeln; writeln('Valor real asignado a variable entera'); val(cadR, valorI, codErr); writeln('CadenaR: ', cadR:15, ' ValorI: ', valorI:8, ' Error: ', codErr); writeln('***El codigo de error coincide con la posicion del punto***'); writeln; writeln; writeln('3ra Parte:'); writeln('La cadena no representa un valor numerico'); cad2 := '45Ax23'; val(cad2, valor2, codErr); writeln('Cadena : ', cad2:15, ' Valor : ', valor2:8, ' Error: ', codErr); writeln; cad3 := '45.23.56'; val(cad3, valor3, codErr); writeln('Cadena : ', cad3:15, ' Valor :', valor3:8:3, ' Error: ', codErr); writeln; writeln('La cadena contiene espacios al inicio'); cad4 := ' 453.56'; val(cad4, valor4, codErr);

Page 221: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

221

writeln('Cadena : ', cad4:15, ' Valor :', valor4:8:3, ' Error: ', codErr); writeln('***No se debe producir un error***'); writeln; writeln('La cadena contiene espacios al final'); cad5 := '453.56 '; val(cad5, valor5, codErr); writeln('Cadena : ', cad5:15, ' Valor :', valor5:8:3, ' Error: ', codErr); writeln('***Se debe producir un error***'); writeln; writeln;

end. Veamos lo que se obtiene

PROCEDIMIENTO STR: Este procedimiento busca realizar una tarea inversa al del procedimiento val, esto es convertir un número en una cadena de caracteres. Este procedimiento recibe dos argumentos, el primero es una variable que puede ser entero o real que contiene el número que se desea transformar, el segundo es la cadena que recibe el valor convertido. La particularidad de este procedimiento es que la cadena final tendrá el mismo formato que tendría el número si se lo enviáramos como parámetro al procedimiento write, esto es, si se trata de un valor real la cadena resultante saldría en notación científica, por lo que el valor entregado como dato podrá tener especificadores de formato. El programa siguiente muestra estos detalles.

program ProcedimientoStr; var cadI, cadR: String[30];

valorI: Integer; valorR: Real;

begin valorI := 374; valorR := 381.79; writeln('Sin formato:'); str(valorI, cadI); str(valorR, cadR);

Page 222: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

222

writeln('ValorI: ', valorI:10, ' CadenaI: ', cadI); writeln('ValorR: ', valorR:10:3, ' CadenaR: ', cadR); writeln; writeln; writeln(' 1 2'); writeln('Con formato: 12345678901234567890'); str(valorI:10, cadI); str(valorR:10:4, cadR); writeln('ValorI: ', valorI:10, ' CadenaI: ', cadI); writeln('ValorR: ', valorR:10:3, ' CadenaR: ', cadR); writeln; writeln;

end. Veamos la salida.

PROCEDIMIENTO SETLENGHT: Este procedimiento no está implementado en el Pascal estándar, recién aparece en versiones de Object Pascal debido a la manera en que son implementadas las cadenas de caracteres en estas versiones. Con este procedimiento se establece un tamaño inicial para una cadena de caracteres no acotada. Si se quiere asignar caracteres a una cadena como si fuera un arreglo, pero la cadena no ha sido inicializada previamente con un texto, el sistema obligará a emplear este procedimiento antes de utilizar la cadena como arreglo, de no hacerlo se emitirá un error de compilación. Se debe tener en cuenta que el error se produce al asignar un caracter a una posición determinada, no al emplear un elemento del arreglo.

El procedimiento tiene dos argumentos, el primero es la cadena a la que se le quiere definir su tamaño, el segundo es un valor entero que contiene el tamaño deseado.

Veamos su uso.

program ProcedimientoSetLength; var cad, cad2: String;

c: Integer; begin

cad := 'Hola amigos';

setLength(cad2, length(cad)); // El proceso siguiente no debe compilar si eliminamos // la línea que contiene el procedimiento SetLength for c := 1 to length(cad) do

cad2[c] := cad[c];

writeln('Cadena inicial: ', cad); writeln('Cadena asignada: ', cad2);

end.

Page 223: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

223

Aplicaciones que emplean cadenas de caracteres: Veamos ahora algunas aplicaciones que permitan procesar textos.

Invertir un texto Aquí veremos dos formas de invertir el contenido de una cadena de caracteres, la primera emplearemos la función concat, la segunda se manejarán las cadenas como arreglos.

program InvierteCadena; var cad, cadInv1, cadInv2: String;

c, c2: Integer; begin

cad := 'Hola amigos, como estan';

// Primera versión: Usando CONCAT cadInv1 := '';

// Aquí se emplearán los elementos del arreglo // por lo que no se producen errores for c := length(cad) downto 1 do

cadInv1 := concat(cadInv1, cad[c]); // Esto es equivalente a hacer: cadInv1 := cadInv1 + cad[i];

writeln('Cadena original: ',cad, ' Cadena 1: ',cadInv1);

// Segunda versión: Usando los elementos del arreglo cadInv2 := '';

setLength(cadInv2,length(cad)); // De no emplearse la línea anterior producirá un error // en la asignación de los elementos de la cadena c2 := length(cad); for c := 1 to length(cad) do begin

cadInv2[c2] := cad[c]; dec(c2);

end; writeln('Cadena original: ',cad, ' Cadena 2: ',cadInv2);

end.

Buscar y reemplazar una cadena por otra La idea de este ejemplo es la de buscar un texto dentro de una cadena de caracteres y reemplazarlo por otro. No habrá restricciones entre la longitud del texto que sale y del que entra, esto quiere decir que el texto que sale podrá ser de mayor, menor o igual que el que sale. Aquí también mostraremos la solución de dos maneras, una empleando la

Page 224: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

224

función copy y la otra empleando los procedimientos delete e insert. En ambos caso se ubicará el texto con la función pos.

En el gráfico siguiente mostramos las tareas que se desean realizar en el programa.

1er paso: buscamos el texto con la función pos:

Versión 1: Usando Copy

Versión 2: Usando Insert y Delete

Delete Texto buscado

Insert Texto de reemplazo

Veamos el programa:

program BuscarYReemplazarTextos; var cad1, cad2, cadOut, cadIn: String;

p: Integer; begin

writeln('Version 1 (copy):'); cad1 := 'Curso de programacion de EE GG CC de la PUCP'; cadOut := 'EE GG CC'; cadIn := 'la Faciltad de Ciencias e Ingenieria';

writeln('Texto incial:'); writeln(cad1:60);

p:= pos(cadOut, cad1); if p>0 then

cad1 := copy(cad1, 1, p-1) // Parte izquierda + cadIn // Texto de reemplazo

Texto buscado

Posición P

P a r t e i z q u i e r d a P a r t e d e r e c h a Texto buscado

Texto buscado

C a d e n a d e c a r a c t e r e s

P a r t e i z q u i e r d a P a r t e d e r e c h a Texto de reemplazo + +

P a r t e i z q u i e r d a P a r t e d e r e c h a Texto buscado

Posición P

P a r t e i z q u i e r d a P a r t e d e r e c h a

Posición P

P a r t e i z q u i e r d a P a r t e d e r e c h a Texto de reemplazo

Posición P

Page 225: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

225

+ copy(cad1, p+length(cadOut), 255); // Parte derecha // No se requiere calcular la cantidad de caracteres de la parte // derecha, sólo se debe colocar un valor suficientemente grande

writeln('Texto reemplazado:'); writeln(cad1:75); writeln;

writeln('Version 2 (insert y delete):'); cad2 := 'Curso de programacion de EE GG CC de la PUCP'; cadOut := 'EE GG CC'; cadIn := 'la Faciltad de Ciencias e Ingenieria'; writeln('Texto incial:'); writeln(cad2:60); p := pos(cadOut, cad2); if p > 0 then begin

delete(cad2, p, length(cadOut)); // Borramos el texto buscado insert(cadIn, cad2, p); // Insertamos el texto de reemplazo

end; writeln('Texto reemplazado:'); writeln(cad2:75);

end.

Verificación en el ingreso de datos En ejemplos anteriores hemos presentado situaciones en las que se requiere de la verificación de los datos a la hora de ingresarlos al programa, por ejemplo si se desea ingresar la nota de un alumno y ésta requiere que esté entre 0 y 20, podríamos escribir el siguiente código:

var nota: Integer; begin

… writeln(‘Ingrese un valor entre 0 y 20:’); repeat

readln(nota); if not (nota in [0..20]) then begin

writeln(‘ERROR: Debe ingresar un valor entre 0 y 20’); writeln(‘Ingrese un nuevo valor’);

end; until nota in [0..20]; …

Este programa, como ya hemos visto, funciona adecuadamente para cualquier valor numérico entero que se ingrese, si ingresamos un valor como 18, el programa continúa sin novedad, si ingresamos un valor como 25, aparece en la ventana de salida del programa

Page 226: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

226

un mensaje indicándonos que no se ha ingresado un valor correcto y nos permite ingresar un nuevo valor, si se sigue ingresando valores incorrectos, el mensaje seguirá apareciendo, esto hasta que se ingrese un valor en el rango esperado, luego de esto el programa continua.

A pesar de esto, ¿qué cree usted que pasaría si ingresamos un valor de tipo real como por ejemplo 12.5?, ¿qué pasaría si ingresamos un dato como 2x4?

La respuesta a esto es simple, el programa se interrumpiría, aparecería en la ventana de salida un mensaje de error diferente al que hemos puesto en el código y no podría continuar la ejecución del programa. En la ventana de salida el mensaje sería similar al siguiente:

Esta situación es grave ya que el programa termina en ese punto, perdiéndose todo lo avanzado o realizado por el programa hasta ese momento.

El empleo de cadenas de caracteres en este caso puede ser muy útil para manejar estas situaciones. La idea es que el dato sea recibido por una variable de tipo cadena de caracteres en vez de una variable numérica. Al hacerse así, por la naturaleza de la variable, cualquier cosa que se ingrese podrá ser almacenada en la variable, de esta forma el programa no se detendrá por un error de entrada. Luego de recibir la información, el código del programa será capaz de decidir si el dato es adecuado y continuar con el mismo, o enviar una alerta de error y solicitar un nuevo dato, pero sin interrumpir el programa.

El código siguiente muestra como se puede hacer esto:

program VerificarDatoConStr; type Str15 = String[15]; var notaStr: Str15;

nota, error: Integer; begin

writeln('Ingrese un valor entre 0 y 20:'); repeat

readln(notaStr); //Intentamos convertir el texto a un numero entero val(notaStr, nota, error); if error <> 0 then begin

writeln('ERROR: Debe ingresar un valor numerico entero'); writeln('Ingrese un nuevo valor');

end else if not (nota in [0..20]) then begin

writeln('ERROR: Debe ingresar un valor entre 0 y 20'); writeln('Ingrese un nuevo valor');

end; until (error = 0) and (nota in [0..20]);

Page 227: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

227

writeln('Dato correcto = ', nota); end.

El código siguiente muestra como se puede hacer esto:

Extrae palabras de una cadena de caracteres Otra tarea que se presenta frecuentemente en los problemas en los que interviene el uso de cadenas de caracteres es la de la descomposición de la cadena en palabras.

El método que emplearemos se basará en la implementación de una función a la que se le ingresará la cadena de caracteres como parámetro y nos devolverá una palabra, siguiente a la última que salió.

El programa siguiente muestra el código del programa principal que ilustra lo que queremos hacer.

var cad, palabra: String; begin

// Partimos de una cadena que tenga varios espacios en blanco entre palabras cad := ' Esta es una prueba de como se extraen palabras de una cadena ';

writeln('Antes:'); writeln(' Cadena: ', cad); writeln; writeln('Palabras de la cadena: '); repeat

palabra := sacaPalabra(cad); if palabra <> '' then writeln(palabra);

until palabra = ''; // Mostramos como queda la cadena original después del proceso writeln; writeln('Despues:'); writeln(' Cadena: ', cad);

end.

Al igual que con alguno de los ejemplos anteriores, plantearemos dos maneras de resolverlo. En la primera, veremos cómo las limitaciones presentes en el leguaje de programación nos llevan, como veremos, a una solución poco elegante, en la segunda empelaremos el concepto de ‘variable estática’, definida anteriormente.

En ambos casos simplificaremos el problema restringiéndolo al hecho que las palabras están separadas solamente por espacios en blanco; en base a este algoritmo se podrá extender la complejidad del problema a otros separadores como tabuladores, signos de puntuación, etc.

Page 228: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

228

Versión 1:

Como indicamos, en esta versión tendremos que adaptarnos a las limitaciones del lenguaje. En este sentido, tenemos que ver que la manera de poder extraer una palabra de la cadena estará determinando por la búsqueda de espacios en blanco, delimitadores de las palabras. Aquí es donde se enfoca la limitación; resulta que la función de búsqueda que nos proporciona el lenguaje es la función pos, y como hemos visto, esta función nos entrega la ubicación de la primera ocurrencia. Por lo tanto el algoritmo deberá eliminar los espacios en blanco que tenga la cadena antes de la primera palabra y, usando la función pos, buscar el siguiente espacio en blanco, lo que nos dará la extensión de la primera palabra para poder extraerla con la función copy, que en realidad nos entrega una copia de la palabra, luego para poder sacar la siguiente palabra, deberemos borrar la palabra ‘extraída’ de la cadena para repetir el proceso.

Como se puede apreciar para realizar la tarea de extraer las palabras de una cadena debemos destruir poco a poca la cadena original. Aquí es donde radica el hecho que consideremos poco elegante el método, ya que finalmente la cadena original quedará destruida. Esto se tratará de evitar en la segunda versión.

A continuación mostramos la función que implementa la primera versión.

function sacaPalabra(var cad: String): String; // El parámetro debe ser por referencia porque se modificará la cadena // argumento, luego de entregar el valor. var p: integer; begin

// Borramos los espacios que se encuentren antes de la primera palabra. // El proceso iterativo primero verifica si la cadena tiene caracteres y // luego si el primer caracteres es espacio en blanco, si lo es lo borra. while (cad <> '') and (cad[1] = ' ') do delete(cad, 1, 1); // En este punto la cadena no tiene espacios en blanco iniciales, por lo // que procedemos a buscar el espacio en blanco que delimitará la palabra. p := pos(' ', cad); // Si la función pos devuelve cero quiere decir que la cadena sólo tiene // una palabra o que está vacía. if p = 0 then begin

// Sacamos la última palabra y limpiamos la cadena para que el // proceso termine sacaPalabra := cad; cad := '';

end else begin

// Sacamos la primera palabra de la cadena y la // borramos sacaPalabra := copy(cad, 1, p-1); delete(cad, 1, p);

end end;

La salida de este programa será como se muestra a continuación.

Page 229: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

229

Observe que la cadena original fue destruida en proceso.

Versión 2:

En esta versión trataremos de implementar una solución que no termine con la destrucción de la cadena original. Para hacer esto entonces no podemos basarnos en el empleo de la función pos, por lo menos no de manera principal. Lo que tenemos que hacer es guardar de alguna manera la posición del espacio en blanco que delimito el fin de la palabra anterior, si esto se consigue se podrá buscar entonces el inicio de la siguiente palabra y por lo tanto también el final de la palabra.

El problema aquí es cómo guardamos esa posición. Un programador novato plantearía la solución definiendo una variable en el programa principal en la que se guarde la posición final de la última palabra, luego tendría que pasar esta variable como parámetro por referencia a la función que extrae la siguiente palabra para que ella, a partir de allí la saque.

Esta solución no es buena, si lo vemos desde el punto de vista de la programación modular. La razón a esto es que debemos entender que la tarea de la función sacaPalabra es la de entregarnos la siguiente palabra de la cadena, si esto lo vemos desde la perspectiva del que hace el programa principal, podemos decir que a él sólo le interesa que le entregue la palabra para poderla procesar en la tarea que está solucionando, ¿cómo hace la función para entregarle la siguiente palabra?, pues no es su problema, esto es el principio del encapsulamiento. Si el programa principal va a definir una variable para controlar la posición de las palabras, pues simplemente estamos condicionando la forma cómo va a plantearse el algoritmo de solución del sacaPalabra, y por otro lado estamos aliviando el trabajo que debe hacer el equipos que se encargue de implementar la función.

Esto no debe pasar, por lo tanto el planteamiento de solución de la función sacaPalabra, debe enfocarse en la idea de entregar la siguiente palabra de una cadena, único parámetro de la función. Por esta razón es que en esta versión emplearemos ‘variables estáticas’, las cuales como hemos visto son variable locales a la función pero no se destruyen luego de ejecutar la función y por lo tanto el valor final que tuvo luego de la ejecución anterior se mantiene en la actual ejecución.

Entonces en la implementación de esta versión de la función definiéremos una variable estática que permita almacenar la posición del espacio que marca el final de la última palabra extraída.

Page 230: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

230

Luego debemos analizar una situación que bajo estas condiciones no se presenta en la primera versión. En la versión 1, la función devuelve, literalmente, siempre la primera palabra de la cadena, por lo tanto en este caso si el programa tuviera la tarea de extraer unas cuantas palabras de la cadena, no todas, y luego se quisiera extraer las palabras de otra cadena, lo único que habría que hacer es cambiar la cadena que ingresa como parámetro. Sin embargo el cambiar la cadena que ingresa como parámetro en esta nueva versión ocasionaría un error, esto debido a que la palabra que se extraería en este caso se hará a partir del valor que tiene la variable estática, valor que no permitirá sacarlas desde la primera palabra.

Por esto se debe buscar un mecanismo que permita detectar, dentro de la misma función, que se está ingresando o no una cadena de caracteres diferente a la que se usó en la anterior llamada. La solución que planteamos aquí se basará en el uso de otra variable estática, definida como una cadena de caracteres, esta variable en la primera llamada a la función se inicializará con una cadena vacía.

Lo que se hará a partir de aquí será comparar esta variable estática con la cadena entrada como parámetro, si son diferentes, como es el caso de la primera llamada a la función, entonces la variable estática definida para determinar los límites de las palabras se inicializará para que empiece a sacar las palabras desde el inicio, luego se le asignará a la cadena de caracteres, definida como variable estática, el valor de la cadena pasada como parámetro para que mantenga ese valor en la siguiente llamada.

A partir de la segunda llamada a la función la cadena de caracteres estática se compara nuevamente con la cadena parámetro, si son iguales, el proceso extraerá la siguiente palabra, partiendo del valor que tiene la variable estática que delimita las palabras, si son diferentes se procede cono si se tratara de la primara llamada a la función.

Veamos el código: function sacaPalabra(cadena: String): String; // Variables estáticas: const control: String = ''; // Controla si se cambió la cadena parámetro

delimitador: Integer = 0; // Servirá para delimitar las palabras

var cadAux: String; posIni, cantCar: Integer;

begin // Verificamos si el contenido de la variable de control estática // coincide con el contenido del argumento if cadena <> control then begin

// Si son diferentes, se establece el delimitador de palabras // en el primer caracter de la cadena y ... delimitador := 1; // ... se guarda la nueva cadena para ser comparada en el // siguiente llamado a la función control := cadena;

end; // si las cadenas son iguales se mantiene el valor del delimitador // que se estableció en el llamado anterior

Page 231: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

231

// Movemos la delimitador de modo de saltar los espacios en //blanco iniciales while (delimitador <= length(cadena)) and

(cadena[delimitador] = ' ') do inc(delimitador);

// Marcamos el inicio de la palabra posIni := delimitador; // Buscamos el fin de la palabra cantCar:= 0; while (delimitador<length(cadena)) and

(cadena[delimitador] <> ' ') do begin inc(delimitador); inc(cantCar);

end;

sacaPalabra := copy(cadena, posIni, cantCar); // Si la extracción de la palabra da una cadena vacía, indicará // que se sacaron ya todas las palabras de la cadena.

end; La salida a este programa será la siguiente:

Observe que la cadena original queda intacta en proceso.

Manipulación de números mixtos Se desea elaborar un programa que permita realizar operaciones con números mixtos. La idea es poder extraer los valores desde cadenas de caracteres, por lo que la fuente de datos será un archivo de textos con un formato peculiar.

Se tiene un archivo de textos como se muestra a continuación: Expresión que contiene varios quebrados (D): 3 4/5 + 371 - 4 101/710 + 12 2/9012 + 3/5… Sumatoria de valores compuestos: 11/7 - 2 2/5 - 5 + 11 12/23 + 3 5/6… Lista de datos (Q): 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5… …

El archivo contiene en cada línea una lista de números mixtos colocados a modo de una expresión. Los operadores que se emplean en estas expresiones son únicamente la suma (+) y la resta (-). Los números mixtos estarán dados por un número entero y/o una fracción, esto es que puede aparecer sólo un entero (p. e.: 5), sólo un quebrado o

Page 232: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

232

fracción (p. e.: 3/5) o un número mixto (p. e.: 3 1/4). No hay límite en la cantidad de cifras que puede tener un número.

Cada línea empieza con un texto que termina con el carácter “:”. Antes de los dos puntos puede aparecer entre paréntesis las letras “D”, “Q” o “M”.

Se requiere un programa que permita obtener el resultado de las expresiones en cada línea. El resultado deberá presentarse con un formato que dependa de la letra que aparece al final del texto, esto es si aparece la D el resultado se presentará en un formato decimal (p. e.: 2.875), si aparece la Q se expresará como quebrado simple (p. e.: 23/8) y si aparece la letra M se deberá mostrar como un número mixto (p. e.: 2 7/8), en los dos últimos casos los quebrados deben aparecer simplificados esto es, no deben aparecer valores como 2/4 ó 6/9, en vez de eso debe aparecer 1/2 ó 2/3. En el caso que no aparezca la letra, el formato que se aplicará será el mismo que apareció en la línea anterior. En la primera línea siempre aparecerá una letra.

Solución:

La solución la plantearemos de la siguiente manera: leeremos una línea completa del archivo y la almacenaremos en una cadena de caracteres. A partir de allí separaremos el texto inicial, el carácter con el formato de impresión y del resto de la cadena determinaremos el resultado. El valor que contiene la respuesta estará dado en dos variables, una contendrá el numerador y la otra el denominador de la fracción resultante, esto porque a partir de allí se pude determinar el valor decimal o el valor mixto. Si el valor resultante fuera sólo un número real, a partir de él no se podría determinar la fracción que lo genera, por la pérdida de precisión que se presenta en la representación de valores reales. Con esa información imprimiremos el resultado de la línea. El proceso se repite hasta completar todo el archivo.

El código siguiente muestra esta solución:

program ManipulacionDeNumerosMixtos; var linea, texto: String;

formato: Char; numerador, denominador: Integer; arch: Text;

begin assign(arch, 'datos.txt'); reset(arch); while not eof(arch) do begin readln(arch, linea); obtenerResultado(linea, texto, formato, numerador, denominador); imprime(texto, formato, numerador, denominador);

end; close(arch);

end. El procedimiento que se encargue de obtener los resultados deberá realizar lo siguiente: primero separará el texto inicial de la secuencia de valores que forman una expresión numérica de valores mixtos, esto se hará en dos cadenas de caracteres adicionales, para eso sólo habrá que buscar en la cadena el carácter ‘:’ y a partir de allí dividir la cadena

Page 233: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

233

en dos. Luego del texto inicial se extraerá el caracter de formato, si no lo tiene se determinará el valor que tenía la línea anterior.

El siguiente paso será el de descomponer la cadena que contiene la expresión numérica en cada uno de sus términos. Esto se hará considerando cada término como una palabra, en la que el signo de operación (+ ó -) será el separador entre cada una. Para hacer el proceso más sencillo colocaremos inicialmente cada término en los elementos de un arreglo de cadenas de caracteres. Por otro lado los signos de operación lo guardaremos en una cadena de caracteres a modo de arreglo de caracteres. Luego de la separación de los datos, se irán tomando uno a uno los términos de las expresiones, se transformará las cadenas en números y se acumulará según los signos de operación. La acumulación se hará a modo de quebrado, esto es manteniendo un valor para el numerador y otro para el denominador, luego de obtener la respuesta de la expresión se procederá a simplificar el quebrado.

El código que plasma estas operaciones, se muestra a continuación:

Debemos definir primero el tipo de dato (arreglo de cadenas de caracteres) que manejará las palabras.

const MAXQUEB = 100; type Str30 = String[30];

TMixto = array [1..MAXQUEB] of Str30; … procedure obtenerResultado( linea:String; var texto: String;

var formato: Char; var numerador, denominador: Integer);

var operandos: TMixto; // Guardará las palabras operaciones: String; // Guardará los signos de operación

begin separaDatos(linea, texto, formato, operandos, operaciones); Observe que el procedimiento separaDatos no se devuelve el número de

operandos, este valor se manejará a través de la longitud de la cadena operaciones.

resultado(operandos, operaciones, numerador, denominador); end;

El procedimiento separaDatos se muestra a continuación:

procedure separaDatos( linea: String; var texto: String; var formato: Char; var operandos: TMixto; var operaciones: String);

var p, numOper: Integer; oper: String;

begin Primero eliminamos los espacios sobrantes, de modo que cada ‘palabra’ de la

cadena se separe sólo por un espacio en blanco. Se considera en este ejemplo como ‘palabra’ a las palabras propiamente dichas, a los valores enteros, a los quebrados y a los signos de operación.

Page 234: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

234

borarEspaciosSobrantes(linea); // Inicializamos los datos que manejarán los operandos y operadores. operaciones := ''; numOper := 0;

// Buscamos la separación entre el texto y las operaciones, y las separamos p := pos(':', linea); texto := copy(linea, 1, p-1);

// Extraemos el formato que determina cómo saldrá el resultado. obtenFormato(texto, formato); // Borramos el texto inicial. delete(linea, 1, p);

// Extraemos los operandos y operaciones del resto de la cadena p := 1; repeat

// Buscamos el siguiente operador en la cadena while (p <= length(linea)) and not (linea[p] in ['+', '-']) do

inc(p);

if linea[p] in ['+', '-'] then begin // Encontramos un operador y lo guardamos operaciones := operaciones + linea[p]; // Extraemos el operando oper := copy(linea,1,p-2); Ahora procederemos a estandarizar el operando, esto para que

luego cuando tengamos que convertirlo a un número y lo operemos, esta tarea sea más sencilla

estandariza(oper); // Colocamos el operando en el arreglo inc(numOper); operandos[numOper] := oper;

end else

// No encontramos un operador, luego sólo está el operando final operandos[numOper+1] := linea;

// Borramos la parte de la cadena analizada delete(linea, 1, p+1); p := 1;

until linea = ''; end;

El procedimiento borarEspaciosSobrantes se muestra a continuación:

procedure borarEspaciosSobrantes(var linea: String); var p: Integer; begin

// Buscamos una secuencia de dos espacios en blanco y eliminamos uno.

Page 235: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

235

// De ese modo dejamos sólo un espacio entre palabras. repeat

p := pos(' ', linea); // busca 2 espacios en blanco … if p <> 0 then delete(linea, p, 1); // … y borramos uno

until p = 0; // Verificamos si hay un blanco al inicio o al final y los eliminamos if linea[1] = ' ' then delete(linea, 1, 1); if linea[length(linea)] = ' ' then delete(linea, length(linea), 1);

end;

El procedimiento obtenFormato se muestra a continuación:

procedure obtenFormato(var texto: String; var formato: Char); var p: Integer; begin

// Buscamos el paréntesis ‘(‘ p := pos('(', texto); if p <> 0 then begin

// Si lo encontramos, lo extraemos formato := texto[p+1];

// Borramos los caracteres desde el paréntesis delete(texto, p, 255);

end; Si no lo encontramos, la variable formato no es modificada, quedándose con

el valor anterior. end;

El procedimiento que estandariza el operando guardará el número mixto en un formato con la forma: “E N/D”; donde E será la parte entera del número (si el número no tiene parte entera se colocará un carácter cero), N es el numerador de la fracción del número (si no tiene parte fraccionaria se colocará un carácter cero) y D es el denominador de la parte fraccionaria del número (si no tiene parte fraccionaria colocará un carácter 1). De este modo un operandos como “35”, “7/15” ó “2 11/23” se estandarizará como “35 0/1”, “0 7/15” y “2 11/23” respectivamente.

El procedimiento estandariza se muestra a continuación:

procedure estandariza(var oper: String); var posBl, posSl: Integer; begin

Buscamos la posición del espacio en blanco y del slash ‘/’, si los ubicamos guardamos su posición en la cadena.

posBl := pos(' ',oper); posSl := pos('/',oper); Si encontramos el espacio en blanco entonces tendremos un número mixto

como “2 11/23”y no habrá que procesarlo, si no lo encuentra será porque se trata de un entero como “35” o un quebrado como “7/15”.

if posBl = 0 then begin si encontramos el slash quiere decir que estamos ante un quebrado, si no

será sólo un entero.

Page 236: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

236

if posSl = 0 then oper := oper + ' 0/1'

else oper := '0 ' + oper;

end; end;

Ahora nos concentraremos en el procedimiento resultado, como se indicó anteriormente, aquí se irán tomando del arreglo uno a uno los términos de las expresiones, se transformará las cadenas en números y se acumulará según los signos de operación, manteniendo un valor para el numerador y otro para el denominador, luego de obtener el resultado se procederá a simplificar el quebrado.

procedure resultado( var operandos: TMixto; operaciones: String; var numerador, denominador: Integer);

var num, den, i: Integer; begin

Convertimos la cadena con el primer operando en un número quebrado, sobre estas variables se acumularán los otros operandos.

aNumero(operandos[1], numerador, denominador); Agregamos un espacio al inicio de la cadena de operadores para así hacer

más fácil el manejo de los índices de los arreglos. operaciones := ' ' + operaciones;

Acumulamos el resto de los operandos según el operador. for i := 2 to length(operaciones) do begin

aNumero(operandos[i], num, den); if operaciones[i] = '+' then

numerador := numerador*den + num*denominador else

numerador := numerador*den - num*denominador; denominador := denominador * den;

end;

Reducimos o simplificamos el quebrado resultante. reduce(numerador, denominador);

end;

El procedimiento aNumero se muestra a continuación:

procedure aNumero (operando:String; var numerador, denominador: Integer);

var pBl, pSl, entero, codErr: Integer; ent, num, den: String;

begin pBl:= pos(' ',operando); pSl:= pos('/',operando); ent:= copy(operando, 1, pBl-1); num:= copy(operando, pBl+1, pSl-pBl-1);

Page 237: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

237

den:= copy(operando, pSl+1,255); val(num, numerador, codErr); val(den, denominador, codErr); val(ent, entero, codErr); numerador := entero*denominador + numerador;

end;

El procedimiento reduce se muestra a continuación:

procedure reduce(var numerador, denominador: Integer); var factor: Integer; begin

factor := 2; while (factor <= numerador div 2) or (factor <= denominador div 2) do

begin if (numerador mod factor = 0) and (denominador mod factor = 0) then

begin numerador := numerador div factor; denominador := denominador div factor;

end else inc(factor);

end; end;

Finalmente procedemos a imprimir el resultado mediante el procedimiento imprime:

procedure imprime ( texto: String; formato: Char; numerador, denominador: Integer);

var i: Integer; begin

write('Formato (',formato,'): '); write(texto); for i := 1 to 40-length(texto) do write(' '); if formato = 'D' then

writeln(numerador/denominador:10:5) else if formato = 'Q' then

writeln(numerador:4,'/',denominador) else

writeln ( numerador div denominador:4,' ', numerador mod denominador,'/',denominador);

end; Si ejecutamos el programa con un archivo como el que se muestra a continuación:

Expresion que contiene varios quebrados (D): 3 4/5 + 371 - 4 101/710 + 12 2/9012 + 3/5 Sumatoria de valores compuestos: 11/7 - 2 2/5 - 5 + 11 12/23 + 3 5/6 Lista de datos (Q): 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5 Expresion que contiene varios quebrados (M): 3 4/5 + 371 - 4 101/710 + 12 2/9012 + 3/5 Sumatoria de valores compuestos: 11/7 - 2 2/5 - 5 + 11 12/23 + 3 5/6 Lista de datos (D): 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5 + 1 1/5

Page 238: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

238

Obtendremos el siguiente resultado:

Page 239: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

239

CAPÍTULO 10: Registros

Definición Cuando se declara una variable en un programa, se está definiendo un espacio de memoria en la que se podrá almacenar un solo valor. Por ejemplo cuando colocamos en un programa la sentencia var a: Integer; estamos definiendo la variable entera a, la cual sólo podrá almacenar un valor en un instante dado. A este tipo de variable se le denomina escalar.

Por otro lado cuando definimos un arreglo estamos definiendo un conjunto de variables, todas identificadas por un mismo nombre, diferenciándose cada una por uno o más índices, así por ejemplo cuando en un programa escribimos la sentencia var a: array [1..5] of Integer; estamos definiendo las variables enteras a[1], a[2], a[3], a[4] y a[5]. Hemos visto en los capítulos anteriores la cantidad de aplicaciones que podemos desarrollar con este tipo de dato, sin embargo, a pesar de ello los arreglos tienen un inconveniente, que todos elementos de un arreglo tienen el mismo tipo de dato Esto quiere decir que en el ejemplo, en el arreglo a sus elementos sólo pueden albergar valores enteros. Si quisiéramos almacenar por medio de un programa una lista de personas en donde por cada una tenemos un código, un nombre y un sueldo, para poder hacerlo tendríamos que definir un arreglo por cada uno de los datos, esto es un arreglo de enteros para el código, un arreglo de cadenas de caracteres para el nombre y uno de valor real para el sueldo. El inconveniente sería que los datos de una persona no se pueden manejar, así, como una unidad

Los “registros” como se conocen en Pascal o "estructuras" como se denominan en el lenguaje C, permiten definir variables que pertenecen también a la categoría de variables estructuradas, en este sentido al declarar una estructura estaremos definiendo, como en el caso de los arreglos, un conjunto de datos, todos identificados por el mismo nombre, pero con la diferencia que cada elemento del conjunto puede ser de diferente tipo. La forma en que se diferenciará un elemento de otro del conjunto ya no se hará por medio de índices sino que se le asignará a cada elemento un nombre. Cada elemento de un registro se denominará campo. Las estructuras son un tipo primitivo de datos, su evolución ha dado lugar a las "Clases" definidas en la programación orientada a objetos (POO) e implementadas en Object Pascal y C++.

Implementación de un registro De la misma manera como se implementó los arreglos, los datos de tipo registro deberán ser definidos primero como un tipo de dato para luego emplear este tipo de datos para declarar variables. La razón es la misma, esto es que la definición de un registro implica una descripción detallada y compleja, por lo que el compilador no permite una programación modular a menos que el registro se haya definido como un tipo de dato.

A continuación se presenta la sintaxis en Pascal de la definición de un tipo de dato registro:

Page 240: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

240

De acuerdo a esta sintaxis, un tipo de dato para un arreglo se puede definir de la siguiente manera:

type TipoRegistro = record codigo : Integer; nombre : String; sueldo : Real;

end; Esta expresión define el tipo de dato denominado TipoRegistro el cual permitirá definir variables de tipo registro. Este tipo de dato estará compuesto por tres campos, el primero (codigo) podrá almacenar un valor entero, el segundo (nombre) guardará cadenas de caracteres y el tercero (sueldo) valores reales. Una variable de este tipo se declarará de la siguiente manera:

var persona, empleado: TipoRegistro; La variable persona, de tipo registro, podrá manipular tres elementos, éstos se manejarán de la siguiente manera:

persona.codigo := 7722099; persona.nombre := ‘Ana Roncal’; persona.sueldo := 3789.50;

Como se puede apreciar todas las variables inmersas en el tipo de dato se identifican por el mismo nombre (persona) sin embargo cada una se diferencia de la otra por el nombre del campo. El nombre de la variable y el del campo se deben separar por un punto. Un elemento (campo) del registro como por ejemplo persona.codigo se puede manipular como cualquier otra variable sin restricción alguna, así se le podrá asignar valores directamente como en el ejemplo anterior y también desde la consola o de algún archivo, se le podrá emplear en expresiones, en fin como cualquier variable común y corriente. La desventaja con respecto a los arreglos es que no se puede generalizar el uso de los campos, esto es, que en el caso de los arreglos se puede emplear variables para manipular los índices como por ejemplo a[i] := 51;, en donde si i vale 3 nos estaremos refiriendo a a[3], y si i vale 5 a a[5]; en el caso de los registros NO se podrá hacer algo como persona.i, pensando que si i vale ‘sueldo’ nos estaremos refriendo al campo sueldo, el hacer esto dará como resultado un error de compilación.

Otra ventaja del uso de los registros es que al igual que en el caso de los arreglos, se puede asignar en una sola operación todos los campos de un registro a otro como en el ejemplo siguiente:

program EjemploDeAsignacionDeRegistros; type St60 = String[60];

St10 = String[10]; TRegistro = record

codigo : Integer;

type Nombre del tipo = record Nombre del campo : tipo ; end ;

Page 241: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

241

nombre : St60; fIngreso : St10; sueldo : Real;

end; var empleado, trabajador: TRegistro; begin

empleado.codigo := 775566; empleado.nombre := 'Ana Roncal'; empleado.fIngreso := '01/02/2003'; empleado.sueldo := 5680.90; // Asignación directa: trabajador := empleado; // Manejamos el nuevo registro writeln('Codigo : ', trabajador.codigo); writeln('Nombre : ', trabajador.nombre); writeln('Ingreso: ', trabajador.fIngreso); writeln('Sueldo : ', trabajador.sueldo:8:2); readln;

end.

Al ejecutar el programa obtendremos el siguiente resultado:

La asignación de registros puede hacerse también a nivel de funciones, esto quiere decir que una función puede también devolver un registro completo. El siguiente ejemplo se puede ver cómo se puede asignar todos los valores de los campos de un registro en una sola operación por medio de una función:

program EjemploDeAsignacionDeRegistrosConFunciones; type St60 = String [60];

St10 = String [10]; TRegistro = record

codigo : Integer; nombre : St60; fIngreso : St10; sueldo : Real;

end; function asigna:TRegistro; var empleado: TRegistro; begin

empleado.codigo := 775566; empleado.nombre := 'Ana Roncal'; empleado.fIngreso := '01/02/2003'; empleado.sueldo := 5680.90; asigna := empleado; // Devuelve el registro completo

end; var trabajador: TRegistro;

Page 242: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

242

begin trabajador := asigna; // Asigna el registro completo writeln('Codigo : ', trabajador.codigo); writeln('Nombre : ', trabajador.nombre); writeln('Ingreso: ', trabajador.fIngreso); writeln('Sueldo : ', trabajador.sueldo:8:2); readln;

end.

Situaciones complejas en la implementación de un registro Una de las características más importante de la implementación de un registro es la que un campo puede ser definido empleando cualquier tipo de dato. Esto quiere decir que un campo puede ser un tipo de dato escalar (Integer, Real, Char, Boolean, etc.) como estructurado (String, Array, Record, etc.) y por lo tanto no hay límites. Por esta razón se deben analizar los diferentes casos que se puedan presentar a fin que se aprecie cómo se puedan resolver.

Registros anidados:

Esta situación se presenta cuando existe un tipo de dato de tipo registro y este tipo se emplea para definir el campo de otro tipo de dato registro. El siguiente ejemplo muestra esta situación:

program registrosAnidados type St60 = String [60];

TFecha = record dd : Integer; // día mm : Integer; // mes aa : Integer; // año

end; TRegistro = record

codigo : Integer; nombre : St60; fIngreso : TFecha; sueldo : Real;

end; var trabajador: TRegistro;

Aquí podemos apreciar que el campo fIngreso (fecha de ingreso) está definido como un tipo de dato registro (TFecha) esto ocasionará que cuando se desee asignar valores a este campo, se le tendrá que manejar precisamente como un registro, esto es:

empleado.codigo := 775566; empleado.nombre := 'Ana Roncal'; empleado.fIngreso.dd := 1; empleado.fIngreso.mm := 2; empleado.fIngreso.aa := 2003; empleado.sueldo := 5680.90;

Page 243: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

243

De esa misma forma, se pueden incrementar los niveles de anidación de modo que un campo sea definido con otro tipo registro que a su vez tiene campos definidos como registro. En esos casos sólo habrá que incrementar el nombre de la variable, recordando que hay que emplear el punto (.) para separar cada nivel.

Campos de tipo arreglo y arreglos de tipo registro:

Aunque esto parezca un trabalenguas estas situaciones se pueden presentar por lo que la nomenclatura que se emplee será muy importante analizar para no cometer errores.

El primer caso que analizaremos es el del campo como arreglo en el siguiente ejemplo:

program EjemploDeCamposComoArreglos; type St60 = String [60];

TVector = array [1..5] of Integer; TAlumno = record

codigo : Integer; nombre : St60; curso : St60; nota : TVector; sueldo : Real;

end; var alumno: TAlumno;

En este caso hemos defiendo un campo denominado nota, que guardará las notas parciales de un alumno en un curso determinado. Entonces para poder asignar por ejemplo la tercera nota del curso a la variable deberemos tomar en cuenta que el arreglo es el campo nota y no la variable alumno por lo que el índice debe acompañar al campo y no al registro, esto es:

empleado.nota[3] := 17; // El índice se coloca a la derecha del campo.

Por otro lado, qué pasa si queremos definir un arreglo en el que cada elemento sea un registro, por ejemplo un arreglo de tipo TAlumno, veamos ese caso:

program EjemploDeArregloDeRegistros; type St60 = String [60];

TVector = array [1..5] of Integer; TAlumno = record

codigo : Integer; nombre : St60; curso : St60; nota : TVector; sueldo : Real;

end; TVectosAl = array [1..50] of TAlumno; //Tipo arreglo de registros

var alumno: TVectorAl;

Aquí se tiene que tener mucho cuidado al manejar los elementos, en primer lugar hay que entender que alumno es un vector y que los campos codigo o nombre no lo son, de modo

Page 244: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

244

que si se quisiera asignar los datos al séptimo elemento del arreglo, la forma de hacerlo será de la siguiente manera:

alumno[7].codigo := 775566; // El índice se coloca a la derecha del registro. alumno[7].nombre := 'Ana Roncal';

Sin embargo si quisiéramos asignar la nota 3 al séptimo alumno deberemos hacer:

alumno[7].nota[3]:= 17; // El índice se coloca a la derecha del registro y también // a la derecha del campo porque cada uno es un arreglo

Aplicaciones que emplean registros Veamos ahora alguna aplicación que permita manejar registros.

Ordenar un arreglo de registros En este ejemplo veremos cómo organizar la información en un arreglo de registros en lugar de un grupo de arreglos como se hizo en capítulos anteriores, aquí se podrá apreciar las ventajas que esto trae como también algunos detalles no muy prácticos, como por ejemplo se verá que es una ventaja a la hora de ordenar los datos que se puede manejar toda la información de una persona como una unidad, esto hará que se intercambien los datos de manera más sencilla; sin embargo veremos una desventaja a la hora de leer e imprimir los datos porque los identificadores son más grandes.

Los datos serán leídos desde un archivo de textos como el que se mostrará a continuación. Se contempla que la información del archivo corresponde a la de alumnos de un curso, esto es que se tiene su código, nombre y notas (6). El programa leerá estos datos y los almacenará en registros, luego calculará los promedios correspondientes, que también serán almacenados en el registro y finalmente ordenará estos datos en función al promedio obtenido y los mostrará en la pantalla:

Datos: 19992345 Juan Lopez 12 14 10 8 12 10 20001010 Maria Ruiz 10 12 11 9 9 5 20101234 Ana Roncal 15 16 12 14 18 15 20002324 Pedro Sanchez 14 15 18 12 13 16 20020107 Paula Gomez 17 19 20 12 15 16 19971313 Carlos Castro 2 5 9 11 10 6 20041003 Alexandra Neyra 18 15 17 16 18 13 …

Programa:

program ordenaRegistros; const MAXAL = 100; type St10 = String[10];

St60 = String[60];

Page 245: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

245

TVectorN = array [1..6]of Integer; TReg = record

codigo : St10; nombre : St60; nota : TVectorN; prom : Real;

end; TVectorA = array [1..MAXAL] of TReg;

var alumno: TVectorA;

numAlum: Integer; begin

leeDatos(Alumno,numAlum); calculaPromedios(Alumno,numAlum); ordenaPorNota(Alumno,numAlum); imprime(Alumno,numAlum); readln

end.

procedure leeDatos(var alumno:TVectorA; var numAlum: Integer); var arch:Text;

i: Integer; begin

assign(arch,'ord-reg.dat'); reset(arch); numAlum := 0; while not eof(arch) do begin

inc(numAlum); readln(arch, alumno[numAlum].codigo); readln(arch, alumno[numAlum].nombre); for i:=1 to 6 do read(arch ,alumno[numAlum].nota[i]); readln(arch);

end; close(arch);

end;

procedure calculaPromedios(var alumno:TVectorA; numAlum: Integer); var i: Integer; begin

for i := 1 to numAlum do alumno[i].prom := promedio(alumno[i].nota); // Se envía el arreglo

end;

function promedio(var nota:TVectorN):real; var suma: real; // Se recibe el arreglo

i: Integer; begin

Page 246: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

246

suma := 0; for i := 1 to 6 do suma := suma + nota[i]; promedio := suma / 6;

end;

procedure ordenaPorNota(var alumno: TVectorA; numAlum: Integer); var i, j: Integer;

aux: TReg; begin

for i := 1 to numAlum-1 do for j := i+1 to numAlum do

if alumno[i].prom < alumno[j].prom then begin aux := alumno[i]; // Se maneja el registro alumno[i] := alumno[j]; // como una unidad alumno[j] := aux;

end; end;

procedure imprime(var alumno: TVectorA; numAlum: Integer); var i, j: Integer; begin

for i := 1 to numAlum do begin write(alumno[i].codigo:10, alumno[i].nombre:20); for j := 1 to 6 do write(alumno[i].nota[j]:3); writeln(alumno[i].prom:8:2);

end end;

El resultado del programa será como sigue:

Campeonato de futbol Se desea tener un programa que dada la lista de equipos que participan en un campeonato de fútbol y los resultados obtenidos en los partidos en la primera etapa, permita obtener un reporte que muestre la tabla de posiciones de cada grupo, así como los dos equipos de cada grupo que se clasifican a la siguiente etapa. Para esto se tiene un archivo de textos en el cual aparecen primero los nombres de los equipos que participan separados por grupos, y luego aparecen los resultados en el orden en que se jugaron, como se muestra en el ejemplo siguiente:

Page 247: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

247

Archivo: futbol.txt Copa Mundial 2010 Grupo A 01 Italia 02 Austria 03 Marruecos 04 Estados Unidos Grupo B … Grupo J 01 Inglaterra 02 República Checa 03 Perú 04 Haití ***Fin grupos*** Partido Resultado Equipos 01 0 1 Argentina – Camboya 02 2 2 Polonia – Uruguay 03 3 0 Italia – Marruecos … 51 4 1 Perú – Inglaterra 52 0 0 Colombia – Finlandia ...

El reporte deberá ser como a continuación se detalla: Tabla de posiciones Copa Mundial 2010 Grupo A Equipo PJ PG PE PP GF GC DG Puntos Italia 3 2 1 0 6 2 4 7 Clasifica Austria 3 1 1 1 1 3 -2 4 Clasifica Marruecos 3 0 0 3 2 5 -3 0 Estados Unidos 3 2 0 1 4 1 3 6 Grupo B …

Donde: PJ = Partidos jugados PG = Partidos ganados PE = Partidos empatados PP = Partidos perdidos GF = Goles a favor GC = Goles en contra DG= Diferencia de goles

Para el puntaje que se otorga a cada partido considera 3 puntos por partido ganado, 1 por partido empatado y 0 por partido perdido. Se clasifican los equipos con mayor puntaje, si igualan en puntaje el que tiene mayor diferencia de goles y si ambos tienen igual diferencia de goles, el que metió más goles.

Solución: El problema al que nos enfrentamos es complejo por la cantidad de datos que hay que procesar y mantener almacenados, por eso es importante que manejemos una adecuada estructura de datos, de modo que evitemos duplicar la información innecesariamente y que las estructuras no sean demasiado grandes que dificulten su manejo.

Emplear una sola estructura en la que se almacene toda información puede volverse inmanejable ya que se trata de mucha información y porque de ser así alguno de los procesos se puede volver muy complejo, por esta razón emplearemos dos estructuras de datos en la solución de este problema, una de ellas guardará los nombres de los países, y la otra las tablas de posiciones. Sin embargo debido a que son dos estructuras, se debe considerar elementos que enlacen una estructura con la otra, por eso a la primera estructura se le adicionarán dos campos, uno que guarde el grupo al que se le ha ubicado el país y el otro su posición dentro del grupo. A la estructura que almacene las tablas de posiciones se le añadirá un campo que guarde la posición del equipo en la estructura de países, de este modo cuando estemos trabajando con una estructura, podremos emplear los datos de la otra sin tener que hacer búsquedas innecesarias.

A continuación presentamos las estructuras estas estructuras de datos:

Page 248: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

248

const // Los grupos irán de la 'A' a 'Z' como máximo MAXGRUPOS = 'Z'; // Cada grupo tendrá máximo 4 equipos MAXEQXGR = 4; // Cantidad de equipos que pueden participar MAXEQUIPOS = (ord(MAXGRUPOS) - ord('A') + 1) * MAXEQXGR;

type Str30 = String[30]; TRegEquipo = record

pais : Str30; grupo : Char; orden : Integer; //Posisción en el grupo

end; TArrEquipos = array [1 .. MAXEQUIPOS] of TRegEquipo;

TRegTabla = record posEq : Integer; pJ : Integer; pG : Integer; pE : Integer; pP : Integer; gF : Integer; gC : Integer; dG : INteger; pts: Integer;

end; // Estructura que contiene un grupo TArrGrupo = array [1..MAXEQXGR] of TRegTabla; // Estructura que contiene todos los grupos TArrCampeonato = array ['A'.. MAXGRUPOS] of TArrGrupo; // Arreglo que servirá para el ordenamiento

TArrIndices = array [1..MAXEQXGR] of Integer;

El programa principal para este problema será muy simple, esto porque conforme se lean los datos habrá que ir actualizando las tablas, luego será el procedimiento de lectura el que lleve toda la carga del proceso.

var paises: TArrPaises; grupo: TArrCampeonato; numPaises: Integer; ultipoGrupo: Char; nombMundial: String;

begin leerDatosCrearTabla(paises, numPaises, grupo, ultimoGrupo, nombMundial); imprimirTablas(paises, numPaises, grupo, ultimoGrupo, nombMundial);

end. La lectura de datos, conforme está estructurado el archivo, se tendrá que dividir en dos partes. La primera se encargará de leer los nombres de los países, aquí se deberá

Page 249: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

249

registrar también el grupo y la ubicación del país en la estructura que se ha denominado “paises” en el programa, y la posición del país leído en esa estructura deberá ser registrada también en la estructura “grupo”, de modo que se pueda ir de uno a otro directamente sin hacer necesariamente búsquedas.

La segunda parte sólo leerá los datos y actualizará las tablas de puntajes. El proceso es el siguiente:

procedure leerDatosCrearTabla ( var paises: TArrPaises; var numPaises: Integer; var grupo: TArrCampeonato; var ultimoGrupo: Char; var nombMundial: String);

var arch: Text; nombArch, gr, pais, pais1, pais2: String; fin: Boolean; p, num, partido, gol1, gol2: Integer;

begin numPaises := 0; write('Ingrese el nombre del archivo: '); readln(nombArch); assign(arch, nombArch); reset(arch); // Leemos el nombre del mundial, se requiere en el reporte readln(arch, nombMundial);

// 1ra parte: Leemos los paises fin := false; while not eof(arch) and not fin do begin

// Leemos la identificación del grupo readln(arch, gr); if gr = '***Fin grupos***' then fin := true else begin

ultimoGrupo := obtenerGrupo(gr); // Leemos los paises del grupo for p := 1 to MAXEQXGR do begin

inc(numPaises); readln(arch, num, pais); limpiarCadena(pais); paises[numPaises].pais := pais; paises[numPaises].grupo := ultimoGrupo; paises[numPaises].orden := num; // Colocamos El enlace en “grupo” e inicializamos los demás campos inicializarRegistroG(grupo, ultimoGrupo, num, numPaises);

end; end;

end;

Page 250: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

250

// 2da. Parte: Leemos los resultados readln(arch); // Saltamos la línea con títulos while not eof(arch)do begin

readln(arch, partido, gol1, gol2, pais); separarPaises (pais, pais1, pais2); actualizarTabla ( pais1, gol1, gol2,

paises, numPaises, grupo, ultimoGrupo); actualizarTabla ( pais2, gol2, gol1,

paises, numPaises, grupo, ultimoGrupo); // Observar que no es necesario definir dos procedimientos para

actualizar las tablas end;

end;

La primera parte de este procedimiento se apoyará en tres procesos: “obtenerGrupo” que se encargará de extraer la letra del grupo de la cadena leída (‘Grupo A’), “limpiarCadena” que eliminará los espacios en blanco en exceso que pueda tener la cadena al inicio, al final o los intermedios, en este último caso se trata de dejar sólo un espacio entre las palabras para hacer más fácil los procesos con las cadenas, y finalmente el procedimiento “inicializarRegistroG” que pondrá en cero todos los campos (pJ, pG, pE, etc.) y además registrará la posición del país en el otro arreglo.

function obtenerGrupo(grupo: String):Char; begin

limpiarCadena(grupo); delete(grupo, 1, 6); // Borramos la cadena 'Grupo ' obtenerGrupo := grupo[1];

end;

procedure inicializarRegistroG( var grupo: TArrCampeonato; gr: Char; n, numPais: Integer);

begin grupo[gr][n].posEq := numPais; grupo[gr][n].pJ := 0; grupo[gr][n].pG := 0; grupo[gr][n].pE := 0; grupo[gr][n].pP := 0; grupo[gr][n].gF := 0; grupo[gr][n].gC := 0; grupo[gr][n].dG := 0; grupo[gr][n].pts := 0;

end;

procedure limpiarCadena (var cadena: String); var p: Integer; begin

// Borramos blancos iniciales while cadena[1] = ' ' do delete(cadena, 1, 1); // Borramos blancos finales while cadena[length(cadena)] = ' ' do

delete(cadena, length(cadena), 1);

Page 251: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

251

// Borramos blancos sobrantes repeat

p := pos(' ', cadena); // Buscamos dos blancos y borramos uno if p <> 0 then delete(cadena, p, 1);

until p = 0; end;

La segunda parte del proceso de lectura se apoyará en dos procedimientos: “separarPaises” ya que a la hora de leer, los dos países se leen juntos, y “actualizarTabla”, este procedimiento se ha diseñado de modo tal que no se tenga que repetir el código ya que se deben procesar dos paises. Lo que se ha hecho en este módulo es que el procedimiento recibirá sólo tres datos, el nombre del país, los goles que anotó y los goles que le anotaron, de esta forma el procedimiento se llama dos veces.

procedure separarPaises(equipos: String; var pais1, pais2: String); var p: Integer; begin

limpiarCadena(equipos); p := pos('-', equipos); pais1 := copy(equipos, 1, p - 2); pais2 := copy(equipos, p + 2, 255);

end;

procedure actualizarTabla( pais:String; golF, golC: Integer; var paises: TArrPaises; numPaises: Integer; var grupo: TArrCampeonato; ultimoGrupo: Char);

var p, num: Integer; gr: Char;

begin p := buscar(pais, paises, numPaises); gr := paises[p].grupo; num := paises[p].orden; inc(grupo[gr][num].pJ); if golF = golC then inc(grupo[gr][num].pE) else if golF > golC then inc(grupo[gr][num].pG)

else inc(grupo[gr][num].pP); inc(grupo[gr][num].gF, golF); inc(grupo[gr][num].gC, golC); grupo[gr][num].dG := grupo[gr][num].gF - grupo[gr][num].gC; grupo[gr][num].pts := 3*grupo[gr][num].pG + grupo[gr][num].pE;

end;

La segunda parte de este programa corresponde a la impresión de las tablas de posiciones, aquí se presenta una dificultad, que es que las tablas de posiciones deben apareces ordenadas por el puntaje y mostrar los clasificados. En este caso, a modo de presentar otras formas de ordenar, se ha decidido mostrar una forma diferente de clasificar la información, ésta se denomina “ordenación por índices”. De lo que trata este método es de mostrar los datos ordenados pero sin alterar la ubicación original de los

Page 252: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

252

datos. La técnica es muy simple, se crea un arreglo de enteros, paralelo a los datos, y en él se colocan las posiciones (índices) que tienen los datos pero en el orden que se espera que aparezcan, el ejemplo siguiente muestra cómo se consigue esto.

Dado u arreglo como el que se muestra a continuación: Datos 1 Maria 2 Pedro 3 Jose 4 Daniel 5 Ana 6 Valentina 7 Naomi 8 Carlos

Si se quiere imprimir los datos ordenados alfabéticamente, se debe creaa un arreglo (de índices) de la siguiente manera:

1 2 3 4 5 6 7 8 Índices 5 8 4 3 1 7 2 6

Aquí se aprecia que en el arreglo de índices, en la primera posición se ha colocado el valor de 5 y esto es porque en el arreglo de datos, en la quinta posición se encuentra ‘Ana’ que es el primer elemento del arreglo que debería aparecer al imprimir los datos ordenados, en la segunda posición se encuentre el 8, que corresponde a ‘Carlos’ el segundo que debería salir, y así sucesivamente.

Observe que si en un programa escribiéramos: for i := 1 to n do writeln(datos[i]); obtendríamos un reporte como el de la tabla A, pero si escribimos: for i := 1 to n do writeln(datos[indice[i]]); el reportes sería como en la tabla B:

Tabla A Tabla B Maria Ana Pedro Carlos Jose Daniel Daniel Jose Ana Maria Valentina Naomi Naomi Pedro Carlos Valentina

En la tabla B se puede ver que los nombres salen ordenados sin embargo no se ha modificado la posición original de los datos.

La pregunta que nos hacemos ahora es ¿cómo construimos esa tabla de índices? La forma de conseguirlo no es muy compleja, lo que se hace es, como se indicó, definir un arreglo de enteros denominado de índices, en el que inicialmente se colocan valores consecutivos, empezando del 1 hasta el número de elementos del arreglo original. Luego se procede a ordenar el arreglo, esto se hace empleando cualquier método de ordenación, en esta caso se empleará el método de intercambio que se ha utilizado en todo este texto, pero empleando tanto el arreglo de datos como el arreglo de índices. Lo que se cambia en este proceso es que cuando se hace la pregunta para ver si un elemento está desordenado con respecto a otro se emplea el arreglo de datos pero a la hora de intercambiarlos, los que cambiarán de posición serán los índices y no los datos.

El código para realizar la impresión de las tablas se muestra a continuación:

Page 253: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

253

procedure imprimirTablas ( paises: TArrPaises; numPaises: Integer; grupo: TArrCampeonato; ultimoGrupo: Char; nombMundial: String);

var p, i: Integer; g: Char; ind: TArrIndices;

begin writeln('Tabla de posiciones: ', nombMundial); for g:= 'A' to ultimoGrupo do begin

writeln('Grupo ', g, ':'); writeln('Equipo PJ PG PE PP GF GC DG Puntos'); determinarIndices(grupo[g],ind); for p:= 1 to MAXEQXGR do begin

write(paises[grupo[g][ind[p]].posEq].pais); for i := 1 to 20-length(paises[grupo[g][ind[p]].posEq].pais) do

write(' '); // Esto para la alineación correcta de los datos write ( grupo[g][ind[p]].pJ:4, grupo[g][ind[p]].pG:4,

grupo[g][ind[p]].pE:4, grupo[g][ind[p]].pP:4, grupo[g][ind[p]].gF:4, grupo[g][ind[p]].gC:4, grupo[g][ind[p]].dG:4, grupo[g][ind[p]].pts:4);

if p <= 2 then writeln(' Clasifica') else writeln;

end; writeln;

end; end;

A continuación la creación de la tabla de índices:

procedure determinarIndices(pais: TArrGrupo; var ind: TArrIndices); var p, i, j, auxInd: Integer; begin

// Llenamos la tabla de índices con valores consecutivos for p := 1 to MAXEQXGR do ind[p]:= p; // Empleamos el método de intercambio para ordenar los datos for i := 1 to MAXEQXGR - 1 do

for j := i + 1 to MAXEQXGR do // Comparamos empleando los datos pero a través de los índices if (pais[ind[i]].pts < pais[ind[j]].pts)

or ((pais[ind[i]].pts = pais[ind[j]].pts) and (pais[ind[i]].dG < pais[ind[j]].dG)) or ((pais[ind[i]].pts = pais[ind[j]].pts) and (pais[ind[i]].dG = pais[ind[j]].dG) and (pais[ind[i]].gF < pais[ind[j]].gF))

then begin // intercambiamos sólo los índices

Page 254: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

254

auxInd := ind[i]; ind[i] := ind[j]; ind[j] := auxInd;

end; end;

La ejecución del programa dará una respuesta similar a la siguiente:

Page 255: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

255

CAPÍTULO 11: Archivos binarios

Definición Un archivo es una colección de datos que se encuentran almacenados de manera permanente en algún dispositivo externo del computador (memoria secundaria), como el disco duro, dispositivo o memoria USB, CDs, etc. En realidad todo lo que se almacena en el computador se considera un archivo, así por ejemplo si escribimos una carta en algún procesador de palabras y luego la guardamos, esta carta se constituye en un archivo, si escribimos un programa en un editor de textos y lo guardamos, éste también es un archivo, de igual manera el programa ejecutable (con extensión .exe) también constituye un archivo, y así una fotografía digital, un video en un CD, una hoja de cálculo también es un archivo.

En otros capítulos se ha trabajado con archivos de texto, se ha visto la manera tan práctica que representa manejar un archivo de textos para el ingreso y la salida de datos y también se han apreciado las limitaciones que tienen estos archivos, sin embargo los archivos de textos son sólo un tipo de archivo. Lo que estudiaremos en este capítulo es otro tipo de archivo, los archivos binarios, que sin alejarse de la definición de lo que es un archivo, por la forma en que se almacenan los datos difiere sustancialmente de lo que es un archivo de textos y por lo tanto la manera de procesarlos también será diferente.

Formas en las que se puede almacenar información en un archivo Cuando se habla de un archivo desde el punto de vista de la programación, no es del todo correcto hablar de "tipos de archivos". Esto porque en realidad un archivo es sólo una colección de bytes consecutivos almacenados, y en ese sentido todos los archivos son lo mismo para el computador. Lo que hace la diferencia es el formato cómo se almacena la información; es así que podemos distinguir dos modelos diferentes, los conocidos como "archivos de texto" y los denominados "archivos binarios".

En los archivos de texto, la información sufre una transformación. Cuando se guarda un dato en un archivo de texto, el valor es convertido a una cadena de caracteres antes de almacenarse en el archivo. Recuerde que los datos en un programa se almacenan en variables y éstas son posiciones de memoria, pues bien, como sabemos en la memoria del computador la información (por ejemplo un número) se guarda en una representación binaria; sin embargo cuando abrimos un archivo de texto no vemos esta representación binaria sino el número como lo entendemos, en otras palabras una secuencia de dígitos. Entienda entonces que cuando en un programa se hace un asignación de la forma a := 93751; donde “a” es definida de tipo Integer, lo que se hace es colocar en la posición de memoria relacionada a la variable a, la representación binaria del número, esto es el valor de 0011 0111 0110 1110 0000 0001 0000 0000 (representación de 4 bytes), luego cuando se envía el contenido de esta variable al archivo de textos, se toma esta representación binaria, se transforma en la cadena ‘93751’ y cada caracteres que la conforman es colocado en el archivo. Observe que en la variable se emplean 4 bytes para almacenar el número sin embargo al archivo se envían 5 caracteres o bytes; si el

Page 256: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

256

número hubiera sido 751, al archivo sólo se enviarían 3 bytes y si fuera 193751 se enviarían 6 bytes.

Cuando se hace el proceso inverso, es decir cuando se lee información del archivo, el sistema toma uno a uno los caracteres del archivo los convierte a una representación binaria y finalmente lo almacena en la variable. En el ejemplo anterior, se toman los 5 caracteres y se transforman en 4 bytes.

En los archivos binarios, la información no se transforma cuando es colocada en el archivo. La representación binaria del dato, tal como se encuentra en la memoria asignada a la variable, es llevada y almacenada en el archivo. Esta es una diferencia muy fuerte con respecto a los archivos de texto que influirá de manera significativa en la forma cómo se accederá a la información del archivo. Una de las cosas que podemos apreciar en esta forma de almacenar los datos en el archivo es que cuando llevamos un valor, por ejemplo de una variable de tipo Integer, serán los 4 bytes que conforman la variable lo que se enviarán al archivo, sin importar el número de cifras que tenga el valor. Esto es, si el número fuera 93751, la información almacenada en la variable que será llevada al archivo será 0011 0111 0110 1110 0000 0001 0000 0000, si fuera 193751 lo que se almacenará será 1101 0111 1111 0100 0000 0010 0000 0000, y si el número fuera 751 lo que se almacenará en el archivo será 1110 1111 0000 0010 0000 0000 0000 0000, 4 bytes en todos los casos.

Diferencias funcionales entre un archivo de textos y uno binario La forma cómo se almacena la información en los archivos afectará en gran medida la forma cómo se accederán a ellos, a continuación presentaremos estos aspectos:

Separación de los datos:

Dado que los datos en un archivo de texto son secuencias de caracteres y por lo tanto la cantidad de bytes para almacenar un valor dependerá de la cantidad de cifras que tiene el valor, se deben que definir separadores (caracteres especiales) que permitan al sistema saber dónde empieza y dónde termina el dato. Estos separadores son el espacio en blanco (' '), el tabulador (#9) y el cambio de línea (#13, #10).

Por otro lado, en los archivos binarios lo que se guarda es la misma secuencia de bytes que tiene la representación numérica del dato en la memoria, y que la cantidad de bytes que se guarda en el archivo depende del tipo de dato y no de la cantidad de cifras que tiene el número. Por esta razón, los archivos binarios no requieren de separadores; si se guarda en el archivo un valor entero (Integer), para recuperarlo sólo se requiere extraer del archivo 4 bytes para tener lo número completo, luego los siguientes 4 bytes que se encuentren en el archivo pueden perfectamente corresponder al siguiente dato sin la necesidad de separadores. En un archivo de textos la única forma de detectar que se culminó la lectura de un valor entero será cuando se detecte el separador.

A continuación se muestran estas diferencias:

var a, b, c: Integer; begin a := 735; b := 29; c := 173923; …

En memoria:

11011111 00000010 00000000 00000000

00011101 00000000 00000000 00000000

01100011 10100111 10000000 00000000

a

b

c

Page 257: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

257

Acceso a los datos:

En un archivo de texto, la única forma de acceder a los datos es de manera secuencial, la razón es muy simple, si los dos primeros datos de un archivo de textos no son relevantes pero el tercero sí, para poder leer el tercer dato se requiere leer antes los otros dos, esto porque no hay manera de saber la ubicación del tercer dato para poder colocarse allí y leerlo, se debe leer el primer dato y una vez que se detecte el separador se sabrá que se ha leído el primer valor, de igual manera pera el segundo, y recién allí se sabrá que se está listo para leer el tercer dato. Por lo tanto, en un archivo de textos no se puede leer un dato sin antes haber leído los anteriores, a esto se le denomina “Acceso Secuencial”. La forma cómo se almacenó los datos limita, pues, esta acción.

En los archivos binarios, como el espacio almacenado por los datos depende del tipo de dato y no del número de cifras, es muy fácil poder calcular la posición en que se ubica el dato que nos interesa obtener, por ejemplo si tenemos almacenados en un archivo una serie de números enteros y queremos extraer el tercer valor, sólo debemos calcular cuántos bytes hay desde el inicio del archivo hasta el inicio del dato. En este caso hay dos valores antes del dato, que multiplicado por 4 bytes, que es lo que ocupa un entero en memoria, nos dan 8 bytes. Entonces no tendremos que leer los dos datos que se encuentran antes del que nos interesa, sólo debemos desplazarnos 8 bytes desde el inicio del archivo para ubicarnos en el dato que nos interesa y leerlo sin tocar los anteriores. Esta forma de acceder a los datos de un archivo se denomina "Acceso Directo".

En resumen se puede afirmar que en los archivos de textos los datos se acceden de manera secuencial los datos, sin embargo en los archivos binarios los datos se pueden acceder de manera secuencial, pero también de manera directa.

A continuación se presenta gráficamente la manera cómo se extrae la información de los archivos:

Al enviar los datos a un archivo de textos, la información quedará de la siguiente manera:

bytes o caracteres

‘7’ ‘3’ ‘5’ ‘ ’ ‘2’ ‘9’ ‘ ’ ‘1’ ‘7’ ‘3’ ‘9’ ‘2’ ‘3’ ‘…’

separadores

Al enviar los datos a un archivo binario, la información quedará así:

735

1101 1111 0000 0010 0000 0000 0000 0000 0001 1101 0000 0000 0000 0000 0000 0000 0110 0011 1010 0111 1000 0000 0000 0000

29 173923

bytes

Page 258: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

258

Actualización de datos:

La actualización de datos en un archivo se refiere a modificar la información que se tiene guardada en el archivo. Para poder realizar esto se requiere de tres pasos, primero leer el dato que se quiere modificar del archivo, luego se tiene que actualizar o modificar el dato en memoria y finalmente volverlo a guardar en el archivo.

Una vez leído un dato de un archivo, el indicador del archivo está listo para leer el siguiente valor, luego está colocado en el byte siguiente al dato leído. Para poder modificar el dato leído y finalmente guardar la modificación en el archivo, se tiene que retroceder el indicador del archivo y colocarlo al inicio del dato leído, luego se debe proceder a guardar el dato en el archivo a partir de esa posición. Aquí es donde se presenta un problema en los archivos de textos, imagínese que el dato leído tiene dos cifras y que luego de su modificación el número quedara con 5 dígitos. Resultaría que los dos primeros dígitos del número ocuparían las dos posiciones originales del número, el tercero se colocaría en la posición del separador y las dos últimas se colocarían en las posiciones de las dos primeras cifras del siguiente dato, el resultado sería un daño irreparable al archivo.

Este problema no se presenta con los archivos binarios por lo que ya se ha comentado, la información en el archivo no depende del número de cifras, luego no importa si el número

En los archivos binarios, se puede calcular la posición del dato en el archivo, desplazar el indicador del archivo y luego proceder con su lectura sin haber leído los datos que lo preceden:

Al abrirlo, el indicador del archivo se coloca al inicio.

Se calcula la posición del dato en el archivo (2 x 4 bytes = 8 bytes) y se desplaza el indicador del archivo

735

1101 1111 0000 0010 0000 0000 0000 0000 0001 1101 0000 0000 0000 0000 0000 0000 0110 0011 1010 0111 1000 0000 0000 0000

29 173923

Ya se está listo para leer el dato sin haber leído los anteriores.

En los archivos de textos, la única forma de acceder la información es de manera secuencial:

Al detectar el separador, se da por terminada la lectura del primer dato

‘7’ ‘3’ ‘5’ ‘ ’ ‘2’ ‘9’ ‘ ’ ‘1’ ‘7’ ‘3’ ‘9’ ‘2’ ‘3’ ‘…’

11011111 00000010 00000000 00000000

a

‘735’

‘7’

‘3’

‘5’

‘2’

‘9’

2° 3°

5° 6°

Al detectar el separador, se da por terminada la lectura del segundo dato y se está listo recién para leer el tercero

Page 259: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

259

crece o se reduce, siempre ocupará el mismo espacio. Por lo tanto, en este tipo de archivo la actualización de datos se puede realizar si complicación alguna.

Son por estas razones que algunos lenguajes de programación como el Pascal no permiten realizar actualizaciones en los archivos de texto, sin embargo en el caso de lenguajes como el C y el C++ esto sí se permite pero dejan al programador la responsabilidad de controlar los errores que se puedan producir.

A continuación se ilustra este proceso:

En los archivos de textos, las acciones que se deberían seguir para actualizar un dato serían las siguientes:

3° A continuación se debe, de alguna forma, regresar el indicador del archivo para colocarse nuevamente en la posición del dato leído

‘7’ ‘3’ ‘5’ ‘ ’ ‘2’ ‘9’ ‘ ’ ‘1’ ‘7’ ‘3’ ‘9’ ‘2’ ‘3’ ‘…’

Luego detectar el dato, se debe proceder a leerlo

‘7’ ‘3’ ‘5’ ‘ ’ ‘2’ ‘9’ ‘ ’ ‘1’ ‘7’ ‘3’ ‘9’ ‘2’ ‘3’ ‘…’ 00011101 00000000 00000000 00000000

b

‘29’ ‘2’

‘9’

2° Luego se debe procede a modificar el dato b := b * 627; // b 18183

00000111 01000111 00000000 00000000

b

‘1

‘8

‘3

4 Finalmente se debe proceder a grabar el dato modificado en el archivo

00000111 01000111 00000000 00000000

b

‘18183 ‘1

‘8

‘7’ ‘3’ ‘5’ ‘ ’ ‘1 ‘8 ‘1 ‘8 ‘3 ‘3’ ‘9’ ‘2’ ‘3’ ‘…’

Como se aprecia el dato que ingresa destruye el siguiente dato del archivo.

Page 260: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

260

Funciones y procedimientos elementales que manejan archivos binarios: Las funciones y procedimientos para manejar los archivos binarios en Pascal no difieren mucho de los empleados para los archivos de texto, sin embargo en muchos de los casos se tendrá que tomar muy en cuenta esas diferencias funcionales tratadas en el punto anterior para poder manejar este tipo de archivo.

Variable de archivo:

Para poder explotar las características funcionales de los archivos binarios, esto es poder calcular la posición de un registro y poder leerlo de manera directa o modificar uno o más datos de un archivo sin tener que volverlo a escribir completamente (actualizar datos) es necesario indicar de manera precisa qué tipo de datos vamos a guardar en el archivo. Esto no quiere decir que en un archivo binario un sólo se deba guardar un tipo de dato, sino que si se quiere guardar en un archivo información de diferente tipo, se

En los archivos binarios las acciones son similares, pero aquí no hay peligro de estropear el archivo:

Luego colocar el indicador del archivo en el dato, se debe proceder a leerlo

735

1101 1111 0000 0010 0000 0000 0000 0000 0001 1101 0000 0000 0000 0000 0000 0000 0110 0011 1010 0111 1000 0000 0000 0000

29 173923

00011101 00000000 00000000 00000000

b

2° Luego se debe procede a modificar el dato, igual como se hizo con el archivo de texto

b := b * 627; // b 18183 00000111 01000111 00000000 00000000

b

3° Finalmente se coloca nuevamente el indicador del archivo en la posición del segundo dato y se graba el dato en el archivo

735

1101 1111 0000 0010 0000 0000 0000 0000 0000 0111 0100 0111 0000 0000 0000 0000 0110 0011 1010 0111 1000 0000 0000 0000

18183 173923

00000111 01000111 00000000 00000000

b

El dato que ingresa no altera los otros datos del archivo

Page 261: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

261

deberá estructurar esta información de modo que no se pierdan las características inherentes a los archivos binarios (acceso directo, actualización de datos, etc.). Por ejemplo si se desea guardar en un archivo binario el código (Integer), nombre (String) y sueldo (Real) de un grupo de personas, si se agrupan esto datos por entidad, es decir colocar primero el código, nombre y sueldo de una misma persona de manera consecutiva, luego se coloca el código, nombre y sueldo de una segunda persona y así sucesivamente, el acceso a los datos de este archivo podrá hacerse de manera directa sin problema alguno, también se les podrá actualizar. Lo que no se podrá es agregar al archivo encabezados, títulos o pies de páginas que difieran del orden de los datos que rompan la homogeneidad del archivo, como sí se puede hacer en los archivos de texto.

Por esta razón la variable de archivo debe indicar claramente la información que va a manejar el archivo, y como esto implicará una descripción de un tipo, primero se deberá definir un nuevo tipo de dato con esta descripción y luego se declarará la variable de archivo, como se hace con los arreglos.

La sintaxis para declarar una variable de archivo para un archivo binario es la siguiente:

Por ejemplo:

program variablesDeArchivosBinarios; type // Si el archivo sólo tendrá valores enteros

TArchBinInt = File of Integer;

// Si el archivo sólo tendrá valores reales TArchBinReal = File of Real;

var archBinNotas: TArchBinInt; archBinFactores: TArchBinReal; …

NOTA IMPORTANTE: En el caso en que se quiera guardar cadenas de caracteres en un archivo binario, sólo se podrá emplear cadenas acotadas, así:

type Srt60 = String[60]; TArchBinStr = File of Str60; // Si usted coloca File of String no se guardará en el archivo las cadenas esperadas.

En el caso que quiera guardar en el archivo diferentes tipos de datos, estos se deberán hacer a través de un registro, esto es:

= ; type Nombre del tipo File of Tipo de dato

:

,

; var Variable de archivo

Nombre del tipo

Page 262: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

262

type Srt60 = String[60]; TReg = Record

codigo : Integer; nombre : Str60; // Recuerde que no puede usar String solamente sueldo : Real;

end; TArchBinReg = File of TReg;

Asignación de un archivo:

La asignación de un archivo no difiere de la forma en que se asignan los archivos de textos, esto es:

var archBinPersonas: TArchBinInt;

begin assign (archBinPersonas, ‘empleados.bin’); // Puede emplear cualquier extensión para el nombre del archivo como la que empleamos aquí (.bin), pero debe recordar que hay extensiones estándar que se usan para identificar la procedencia del archivo, como .txt para archivos de texto, .doc ó .docx par archivos de Microsoft Word, .xls ó .xlsx para Microsoft Excel, etc.

Apertura de archivos:

Aquí si se presentan algunas diferencia. En primer lugar en archivos binarios no se pueden abrir los archivos empleando el procedimiento append, esta orden es exclusiva de los archivos de texto.

En segundo lugar están la forma como se comportan los procedimientos reset y rewrite. Como hemos dicho, en los archivos binarios se puede actualizar los datos almacenados en él, por esta razón un archivo binario no se abre exclusivamente para leer o escribir sino que se abren simplemente y luego de abiertos se pueden leer o escribir en ellos indistintamente. Entonces, la funcionalidad de estos procedimientos será la siguiente:

1° Procedimiento reset

Este procedimiento permite abrir un archivo para leer o escribir en él. El indicador del archivo se coloca al inicio del archivo. El archivo debe existir de lo contrario se producirá un error y se interrumpirá la ejecución del programa.

2° Procedimiento rewrite

Rewrite permite abrir un archivo para leer o escribir en él. Si el archivo no existe lo crea, de lo contrario borra el contenido del archivo. Por lo tanto si se usa rewrite, la primera operación que se haga en él no podrá ser de lectura, porque el archivo estará vacío, sin embargo una vez grabado un dato se le podrá leer.

Cierre de un archivo

Aquí no hay diferencia, la orden es close y se emplea de manera similar a la de los archivos de texto, esto es: close (archBin);

Page 263: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

263

Lo que si se debe tomar en cuenta es lo crítico que resulta el cierre de un archivo binario, como al abrir un archivo binario se puede leer y escribir indistintamente, el olvidarse cerrar el archivo puede ocasionar que el archivo quede inservible.

Entrada y Salida de datos

Se presentan aquí algunas diferencias con respecto a los archivos de texto. A continuación se enumeran esto casos:

1º Como no existen separadores entre los datos, éstos no están estructurados en función de líneas sino en función de “registros”, entiéndase por registro el tipo de dato en que fue definido el archivo, si es File of Integer; un registro será un valor entero, si es File of TReg; un registro será el conjunto de campos de TReg.

En este sentido para leer o escribir datos en un archivo binario ya no se podrá emplear los procedimientos readln ni writeln, sólo se empleará read y write.

2º Sólo se puede leer o escribir un “registro” por operación. Esto es las ordenes tendrán la siguiente forma:

read (archBin, dato); ó write (archBin, dato);

No se podrán leer varios “registros” a la vez, esto es, no se puede hacer:

read (archBin, dato1, dato2, dato3); ó write(archBin, dato1 , dato2, dato3);

3º Si el archivo es definido como File of TReg; no se podrá leer o escribir los datos campo por campo, sino el registro en bloque, esto es, se debe hacer:

read (archBin, reg); ó write (archBin, reg); y NO:

read (archBin, reg.codigo, reg.nombre, reg.sueldo); ó

write (archBin, reg.codigo, reg.nombre, reg.sueldo);

4º La función eof se puede emplear para detectar el fin del archivo pero no se podrá emplear eoln porque no existe el separador o cambio de línea en los archivos.

Acceso secuencial a un archivo binario Como hemos dicho, un archivo binario puede manejarse tanto de manera secuencial como de manera directa, en este punto analizaremos el primer caso ya que es la forma natural de acceso a todo tipo de archivo y porque es la más sencilla.

El siguiente programa muestra la manera cómo llenar un archivo con valores enteros de manera secuencial. Hay que tener en cuenta que como lo que se guarda en un archivo binario es una copia exacta del dato en memoria (representación binaria), no se puede crear un archivo binario mediante un editor de palabras como se puede hacer con los archivos de texto. Sólo se podrá crear un archivo binario mediante un programa.

program CreaArchivoBinarioDeEnteros; type TArcBinInt = File of Integer; var archBin : TArcBinInt;

valor: Integer;

Page 264: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

264

begin assign(archBin, 'datos.bin'); rewrite(archBin); writeln('Ingrese los datos que guardara en el archive, para terminar ingrese cero (0)'); repeat

read(valor); // Leemos el dato desde la consola if valor <> 0 then write(archBin, valor); // Lo guardamos en el archive binario

until valor = 0; close(archBin);

end. El programa permitirá la lectura, desde el teclado, de los datos que se desean guardar en el archivo binario y los colocará de manera secuencial en el archivo. Al ejecutar el programa se verá algo similar a lo mostrado en la figura siguiente:

Si luego se desea ver los datos que se han guardado en el archivo no se debe emplear un editor de palabras, como se puede hacer con un archivo e textos. La razón es simple, lo que hacen los procesadores de palabras es recuperar byte a byte la información del archivo y luego muestran estos bytes como caracteres ASCII, esto es si se ha guardado el número 37, en el archivo de textos se encuentran los bytes con valores: 0011 0011 y 0011 0111, ó los valores 51 y 55, que corresponden precisamente a los caracteres ‘3’ y ‘7’ respectivamente.

Si por el contrario ese mismo número se almacena en un archivo binario, se guardará la secuencia de 4 bytes: 0010 0101, 0000 0000, 0000 0000 y 0000 0000, si lo tratamos de mostrar con un editor de palabras lo que veremos es %, que corresponden a los caracteres ASCII de esos byte. Por lo tanto no vamos a poder entender el contenido del archivo.

El archivo que creamos en la aplicación se puede ver en un procesador de texto de una manera similar a la siguiente:

Como se puede apreciar el contenido no se entiende, sin embargo lo que se guardó en el archivo es la información requerida y suficiente para poder recuperar la información y poderla procesar. La única forma de poder ver, entender y procesar el contenido del archivo es mediante un programa. Por eso, a continuación presentaremos un programa que lea la información del archivo y lo muestre en pantalla.

program LeeArchivoBinarioDeEnteros; type TArcBinInt = File of Integer; var archBin : TArcBinInt;

valor: Integer;

Page 265: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

265

begin assign(archBin, 'datos.bin'); reset(archBin); writeln('Contenido del archivo:'); while not eof(archBin) do begin

read(archBin, valor); // Leemos el dato desde el archive de texto writeln(valor); // Mostramos el dato en la pantalla

end; close(archBin);

end. Al ejecutar este programa podremos apreciar lo siguiente:

Una segunda aplicación que presentamos consistirá en la creación de un archivo binario en la que coloquemos mayor cantidad de información. En este caso los datos los vamos a sacar de un archivo de textos en donde se encuentre la información de algunos productos, por ejemplo un listado de medicinas. En este archivo de textos tendremos por cada producto su código (valor entero), la descripción (cadena de caracteres) y el precio unitario (valor real), el archivo es similar al que se muestra a continuación:

Medicinas.txt 60509 AMPICILINA 125MG SUSP 90 ML 58.65 73972 VITAMINA E 400 MG C90 CAPS 54.4 96031 CETIRIZINA 10 MG TABS C/10 45.9 43633 ERITROMICINA T 250 MG C/20 39.1 79189 CLORFENAMINA 4MG T20 73.1 81695 LECHE DE MAGNESIA 180ML 15.3 …

Como se indicó, el programa leerá la información de este archivo de textos y lo guardará de manera secuencial en otro archivo pero bajo el formato binario.

Para poder implementar este programa debemos tomar en cuenta las características de los archivos binarios, de modo que luego se le pueda explotar al máximo. En este sentido, lo primero que debemos considerara es que cada dato que se guarde en el archivo binario debe tener el mismo tamaño en bytes, por eso si nos enfocamos en los

Page 266: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

266

datos de manera individual veremos que tenemos tres diferentes tipos (entero, cadenas de caracteres y real), por lo que si consideramos los datos individualmente no conseguiremos una uniformidad en los tamaños. Sin embargo si vemos toda la información de un producto como una unidad (entidad), esto es como un registro (Record) compuesto por tres campos (código, descripción y precio), la variable que maneje el registro si podrá tener un tamaño fijo.

Una vez que se haya decidido trabajar con una variable de tipo registro, se debe solucionar el problema de las cadenas de caracteres. Resulta que las versiones modernas de Pascal manejan las cadenas de caracteres de manera dinámica. Esto se refiere a que cuando se asigna una cadena a una variable de tipos String el sistema no reserva un espacio de memoria de 255 bytes a la variable y luego asigna la cadena, si no que le reserva a la variable el espacio de memoria justo para contener la cadena que se quiere asignar, también existe otros problemas que se refieren al manejo de punteros que no se tratará en este capítulo. Pues bien, si esto sucede no se podrá controlar el tamaño de la información que se guarde en el archivo. Sin embargo, afortunadamente tenemos una salida a este problema y es que las cadenas acotadas (p. e.: String[30]) si reservan la memoria de manera anticipada y fija a las variables, de modo que sin importar la cantidad de caracteres que tenga la cadena que se desea guardar, siempre se emplearán la misma cantidad de byte para almacenarlo (para el ejemplo siempre se almacenarán 30 bytes a pesa que se quiera guardar una cadena como ‘Ana Li’), por lo tanto como regla que debemos adoptar en el manejo de archivos binarios es que las cadenas de caracteres que se empleen deberá ser acotadas de manera obligatoria.

Los siguientes dos programas contemplan estas cosas, observe la forma cómo se define la estructura y sobretodo la manera cómo se hace con las cadenas de caracteres. Fíjese también que la lectura y escritura de los datos del archivo de texto debe hacerse campo por campo; no se puede leer o escribir el registro todo como una unidad. En el caso de los archivos binarios, la figura se invierte, aquí la información se trabaja como una unidad por lo que no se puede leer o escribir por separado campo por campo.

program CreaArchivoBinarioDeRegistros; type // Primero se definen los tipos de datos que manejarán las cadenas de caracteres

// que deber ser necesariamente cadenas acotadas Str50 = String[50];

// Luego se define el tipo que manejará el registro TRegMedicina = Record

codigo: Integer; descripcion: Str50; // No olvidar que las cadenas deben ser acotadas precioUnit: Real;

end;

// Finalmente definimos la variable de archivo TArchMedicina = File of TRegMedicina;

var archDatos: Text; // Los datos se tomarán de un archivo de textos archMedic: TArchMedicina; regMed: TRegMedicina;

Page 267: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

267

begin assign(archDatos, 'Medicinas.txt'); // El archivo de datos es de textos reset(archDatos);

assign(archMedic, 'Medicinas.bin'); // El archivo final será binario rewrite(archMedic);

while not eof(archDatos) do begin // Se deben leer los datos archivo de textos obligatoriamente campo por campo, // NO SE PUEDE LEER O ESCRIBIR EL REGISTRO COMO UNA UNIDAD // En los archivos de texto se puede usar read, readln, write o writeln readln(archDatos, regMed.codigo); readln(archDatos, regMed.descripcion); readln(archDatos, regMed.precioUnit);

// Se guardan los datos en el archivo binario como una unidad // NO SE PUEDEN LEER O ESCRIBIR LOS DATOS CAMPO POR CAMPO // En los archivos binarios sólo se puede usar read, write write(archMedic, regMed);

end;

close(archDatos); close(archMedic);

end.

El programa que lea o procese el archivo creado debe definir una estructura de datos idéntica a la que se empleó en su creación. Esta igualdad no se refiere a los nombres de los campos, si no a la cantidad y tipos de dato de los campos. En otras palabras la estructura empleada debe definir la misma cantidad de campos, con los mismos tipos de datos y en el mismo orden. De no seguir estas recomendaciones el programa podrá leer la información del archivo pero no la interpretará correctamente por lo que no se obtendrán los resultados esperados.

program LeeArchivoBinarioDeRegistros; type // Se define las cadenas de caracteres como cadenas acotadas

Str50 = String[50]; // Se define el tipo que manejará el registro // Observe que los nombres de los campos no coinciden con el tipo de datos que el // archivo, el resto sí TRegMedicina = Record

cod: Integer; desc: Str50; // No olvidar que las cadenas deben ser acotadas precUnit: Real;

end; // Finalmente definimos la variable de archivo TArchMedicina = File of TRegMedicina;

var archMedic: TArchMedicina; regMed: TRegMedicina;

Page 268: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

268

begin assign(archMedic, 'Medicinas.bin'); reset(archMedic); while not eof(archMedic) do begin

// Se lee el registro como una unidad read(ArchMedic, regMed); // Se muestra el registro en pantalla. Recuerde que la pantalla se comporta // como un archivo de textos, por lo que se debe imprimir campo por campo writeln(regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2);

end; close(archMedic);

end.

Al ejecutar el programa podremos observar un resultado similar al siguiente:

Acceso directo a un archivo binario Hasta ahora el manejo que hemos hecho de los archivo binarios difiere muy poco de lo que hicimos con los archivos de textos. Por un lado hemos leído uno a uno los datos y los hemos guardado uno a continuación del otro en el archivo binario y por otro, lo que hemos hecho es leer los datos del archivo binario secuencialmente y los hemos mostrado en la pantalla. Esto se hace porque la manera natural de manejar cualquier tipo de archivo es de manera secuencial y por lo tanto no tenemos que hacer algo extraordinario para poder acceder de esa forma a los datos de un archivo cualquiera sea su formato.

En este punto, lo que vamos a mostrar es otra forma de acceder a los datos de un archivo, esta forma se denomina “acceso directo”. Esta forma de acceso no es exclusiva de los archivos binarios, sin embargo el que no lo sea implicará realizar operaciones muy complejas que se escapan a la finalidad de este texto, por eso el acceso directo a archivos lo circunscribiremos sólo a archivos binarios.

Una de las cosas en la que debemos enfocarnos para realizar este tipo de acceso es en las funciones y procedimientos que permitan realizar esta labor, por eso a continuación las describiremos:

Page 269: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

269

Procedimiento seek

Cuando se abre un archivo, el indicador del archivo se coloca al inicio del mismo, este indicador marca la posición a partir de donde se extraerá o grabará la información del o en el archivo. Conforme se extrae o graba en el archivo este indicador se va desplazando hacia delante de modo que cuando se termina la operación, el indicador del archivo queda listo para realizar otra operación a partir del siguiente registro. Esto sucede tanto en archivos de texto como en archivos binarios.

El procedimiento seek permite desplazar el indicador del archivo a voluntad en el archivo. Este procedimiento, en el caso de Pascal, sólo se puede emplear con archivos binarios, sin embargo en otros lenguajes de programación, procedimientos similares se pueden aplicar a cualquier tipo de archivo.

El procedimiento seek tiene la siguiente sintaxis:

seek(varArch, n);

Donde varArch es el nombre de la variable de archivo que queremos procesar y n es un valor entero que indica, indirectamente, que registro queremos procesar. Si esto lo haríamos con el archivo de medicinas de la aplicación anterior, podríamos ejecutarla de la siguiente manera:

seek(archMedic, 3);

Cuando se ejecuta esta orden, lo que va a hacer el sistema es lo siguiente, primero va a observar cómo fue declarada la variable de archivo, en el ejemplo esta orden vería que fue declarada como:

var archMedic: TArchMedicina; y luego:

TArchMedicina = File of TRegMedicina; Para finalmente:

TRegMedicina = Record cod: Integer; desc: Str50; precUnit: Real;

end;

De este análisis el sistema podrá determinar cuál es el tamaño en bytes que ocupará todo registro en el archivo (si desea saber el tamaño en bytes que ocupa una variable o tipo de dato en un programa se puede usar la función sizeof, por ejemplo: tam := sizeof(TRegMedicina); - tam := sizeof(Integer);).

Luego de obtener el tamaño del registro, el procedimiento multiplicará este valor por el valor de n (segundo argumento del procedimiento) y con esto se tendrá la cantidad de que se desplazará el indicador del archivo a partir de inicio, finalmente el procedimiento realiza este desplazamiento. Quedando de este modo el indicador del archivo listo para leer o escribir del o en el archivo un registro que no necesariamente se encuentre consecutivo al registro anterior que se operó.

Por lo expuesto, podemos concluir que por medio del procedimiento seek podemos acceder a cualquier registro del archivo sin tener que hacerlo secuencialmente.

Page 270: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

270

La figura siguiente muestra el cómo se puede realizar este proceso.

El siguiente programa muestra este proceso, el programa trabajará con el archivo de medicinas, pero en este caso para hacerlo más didáctico se reducirá el número de registros en el archivo.

program AccesoDirectoAUnArchivoBinarioDeregistros; type Str50 = String[50];

TRegMedicina = Record cod: Integer; desc: Str50; precUnit: Real;

end; TArchMedicina = File of TRegMedicina;

var archMedic: TArchMedicina; regMed: TRegMedicina; n: Integer;

begin assign(archMedic, 'Medicinas.bin'); reset(archMedic);

archivo

Registro 1 Registro 2 Registro 3 Registro 4 Registro 5 Registro 6 …

Al hacer: reset(Archivo); el indicador del archivo se coloca al inicio: archivo

Registro 1 Registro 2 Registro 3 Registro 4 Registro 5 Registro 6 …

Indicador del archivo

Luego si hacemos por ejemplo seek(Archivo, 4), se calcula el valor 4 * sizeof(Registro) y se desplaza el indicador del archivo a esa posición

archivo

Registro 1 Registro 2 Registro 3 Registro 4 Registro 5 Registro 6 …

El indicador del archivo se coloca al inicio del Registro 5

sizeof(registro)

1 2 3 4

Si luego hacemos por ejemplo seek(Archivo, 2), primero se coloca el indicador del archivo al inicio, luego calcula el valor 2 * sizeof(Registro) y finalmente se desplaza el indicador del archivo a esa posición

archivo

Registro 1 Registro 2 Registro 3 Registro 4 Registro 5 Registro 6 …

El indicador del archivo se coloca al inicio del Registro 3

Luego se desplaza el indicador al valor calculado

1 2

1ro se regresa el indicador del archivo a inicia

Page 271: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

271

n:=0; // Primero mostramos los datos secuencialmente en el orden en que se aparecen en el archivo writeln('Datos del archivo (acceso secuencial):'); writeln; writeln(' No Codigo Descripcion P.U.'); writeln; while not eof(archMedic) do begin

inc(n); read(ArchMedic, regMed); writeln(n:3,regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2);

end; // Luego mostraremos algunos de los registros en orden aleatorio writeln('Datos del archivo (acceso directo):'); writeln; writeln(' No Codigo Descripcion P.U.'); writeln;

n:=5; // Desplazamos el indicador de archivo 5*sizeof(regMed) bytes desde el inicio seek(archMedic, n); read(ArchMedic, regMed); // Leemos el registro writeln(n+1:3,regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2); // Observe que se imprime "n+1" en lugar de simplemente "n", porque n no coincide con // el número de registro, así si n = 0 se leerá el 1er registro, si n = 1 se leer el segundo y // así sucesivamente

n:=2; // Desplazamos el indicador de archivo 2*sizeof(regMed) bytes desde el inicio seek(archMedic, n); read(ArchMedic, regMed); // Leemos el registro writeln(n+1:3,regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2);

n:=7; // Desplazamos el indicador de archivo 7*sizeof(regMed) bytes desde el inicio seek(archMedic, n); read(ArchMedic, regMed); // Leemos el registro writeln(n+1:3,regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2);

close(archMedic); end.

El resultado de este programa lo mostramos a continuación:

Page 272: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

272

Funciones filepos y filesize

La función filepos nos indica la posición del indicador del archivo en un momento dado, en otras palabras nos dice cuántos registros desde el inicio está desplazado el indicador del archivo luego de haber realizado alguna operación de lectura o escritura en el archivo. La forma cómo se emplea es la siguiente:

var archMedic: TArchMedicina; posicion: Integer;

begin … reset(archMedic); … // operaciones de lectura o escritura posición := filepos(archMedic); // posición recibe la ubicación del indicador del archivo

Si la función se ejecuta inmediatamente después de la apertura (reset o rewrite) la función filepos nos devuelve cero (0), si se realiza luego de la primera operación de entrada o salida devolverá uno(1).

La función filesize nos devuelve la cantidad de registros guardados en el archivo. La forma de utilizarla es la siguiente:

var archMedic: TArchMedicina; tam: Integer;

begin

… reset(archMedic); … tam := filesizes(archMedic);

El siguiente programa ilustra el uso de estas funciones:

program UsoDeFileposYFileSize; type Str50 = String[50];

TRegMedicina = Record cod: Integer; desc: Str50; precUnit: Real;

end; TArchMedicina = File of TRegMedicina;

var archMedic: TArchMedicina; regMed: TRegMedicina; n, numReg, posIndArch, r: Integer;

begin assign(archMedic, 'Medicinas.bin'); reset(archMedic); n:=0;

Page 273: Texto-Técnicas de Programación

Estudios Generales Ciencias Curso: Técnicas de Programación Autor: Miguel Guanira Erazo

273

// Primero mostramos los datos secuencialmente en el orden en que se encuentran en el // archivo writeln('Datos del archivo (acceso secuencial):'); writeln; writeln(' No Codigo Descripcion P.U.'); writeln; while not eof(archMedic) do begin

inc(n); read(ArchMedic, regMed); writeln(n:3,regMed.cod:10, regMed.desc:35, regMed.precUnit:10:2);

end; writeln;

// Ahora determinaremos el tamaño (número de registros) del archivo numReg := filesize(archMedic); writeln('El archivo tiene: ', numReg, ' registros.');

// Ahora veamos cómo trabaja filepos reset(archMedic); posIndArch := filepos(archMedic); writeln; writeln('Luego de hacer reset el indicador del archivo nos da: ', posIndArch);

for r:= 1 to 3 do // Leemos tres registros del archivo read(ArchMedic, regMed);

posIndArch := filepos(archMedic); writeln; writeln( 'Luego de leer tres registros el indicador del archivo nos da: ', posIndArch); writeln( 'por lo tanto se estará listo para leer el registro No. ',posIndArch+1);

close(archMedic); end.

Al ejecutar de este programa veremos un resultado similar al que se muestra a continuación: