From c3956427d256958b54b97e5acd75dd740f970217 Mon Sep 17 00:00:00 2001 From: Manuel Vergara Date: Sat, 4 May 2024 20:28:28 +0200 Subject: [PATCH] Update PR2 --- fundamentos-programacion/PR2/README.md | 765 ++++++++++++++++++++++++- 1 file changed, 761 insertions(+), 4 deletions(-) diff --git a/fundamentos-programacion/PR2/README.md b/fundamentos-programacion/PR2/README.md index ee21b4a..82b5b11 100644 --- a/fundamentos-programacion/PR2/README.md +++ b/fundamentos-programacion/PR2/README.md @@ -21,80 +21,837 @@ - [12.6. Funciones contra acciones](#126-funciones-contra-acciones) - [12.7. Acciones y funciones predefinidas](#127-acciones-y-funciones-predefinidas) - [12.8. Modularización](#128-modularización) - - [12.9 Ejercicios de autoevaluación](#129-ejercicios-de-autoevaluación) - - [12.10 Soluciones](#1210-soluciones) + - [12.9 Ejercicios](#129-ejercicios) + - [12.10 Soluciones en lenguaje algorítmico](#1210-soluciones-en-lenguaje-algorítmico) ## 12. Modularidad +La modularidad es una técnica de programación que consiste en dividir un programa en partes más pequeñas y manejables. Cada parte se denomina módulo y puede ser un subprograma, una función, una acción, un procedimiento, una rutina, un método, etc. La modularidad permite dividir un problema complejo en problemas más simples y fáciles de resolver. Además, facilita la reutilización de código, la depuración y el mantenimiento de los programas. + ### 12.1 Aproximación intuitiva +Un ejemplo con una comida: + +``` +algorithm almuerzo + var + primerPlato, salsa, segundoPlato, postres: tPlato; + end var + + primerPlato:= encargarPaella(marisco, 5); + segundoPlato:= encargarPescado(bacalao, ajoaceite, 5); + postres:= encargarPostres(pastel, chocolate, velas, 5); +end algorithm + +``` + +Podemos ver cómo nuestro algoritmo, a base de solicitar a expertos que hagan el trabajo, se convierte en un algoritmo simple y fácil de entender. Lo único que hemos tenido que hacer son peticiones en las que hemos añadido valores, a los que llamamos **parámetros**, que indican exactamente cómo queremos cada plato. + +Los lenguajes de programación permiten trabajar de una forma similar a como lo hemos hecho en el menú. Es decir, se pueden utilizar subalgortimos, o se pueden definir otros nuevos, que quedan aparte del algoritmo principal y que permiten dividir un algoritmo complicado en subalgoritmos más simples. Además, como veremos en los siguientes apartados, otra de las ventajas que proporcionan es el hecho de que estos subalgoritmos se pueden reutilizar evitando la reescritura de código. + +Estos subalgoritmos reciben el nombre de **acciones** y **funciones** y es como si empaquetásemos un conjunto de instrucciones que resuelven un problema y no nos tenemos que preocupar de lo que hay dentro. + #### 12.1.1. Ejemplo - Cálculo de combinaciones +Supongamos que tenemos que diseñar un algoritmo que permita saber cuántas combinaciones de colores se pueden hacer si disponemos de `n` telas, cada una de un color diferente. Concretamente se quiere disponer de un algoritmo genérico que dados `n` colores calcule cuantas combinaciones se pueden hacer utilizando cada vez `m` colores, donde `m` y `n` son enteros y `m ≤ n`. + +Para diseñar el algoritmo deberemos leer los valores de n y m y haberlo indicado en la fórmula. Quedaría: + +```c +algorithm combinations + var + {definimos las variables, dos enteros que hay que leer y una donde almacenar el resultado} + totalNumColors, colorsToCombine: integer ; + result: integer; + end var + + {leemos los dos números} + totalNumColors:= readInteger(); + colorsToCombine:= readInteger(); + + {Hacemos un esbozo del cálculo, suponiendo que disponemos de una manera de calcular el factorial de un número } + result := totalNumColors! div (colorsToCombine! * (totalNumColors-colorsToCombine)!) ; + writeInteger(result); +end algorithm + +#include + +int main() { + int totalNumColors; + int colorsToCombine; + int result; + result = totalNumColors! / (colorsToCombine! * (totalNumColors-colorsToCombine)!) ; + printf ("%d ", result); + return 0; +} +``` + +Ahora bien, para calcular el factorial de las posible combinaciones se necesita hacer una iteración para ir calculando el producto de todos los enteros, además de la necesidad de variables donde guardar los cálculos parciales:: + +```c +var + factorial: integer; + i: integer; + n: integer; +end var + +n:= readInteger(); +factorial:= 1; +i:=1; + +while i ≤ n do + factorial:=factorial*i; + i:=i+1; +end while + +#include + +int main() { + int factorial; + int i; + int n; + + scanf("%d", &n); + factorial=1; + i=1; + + while(i <= n) { + factorial = factorial*i; + i++; + } + return 0; +} + +``` +Si incorporamos este código en el algoritmo anterior, el código se complica y se hace menos legible. Deberíamos repetir el código cada vez que necesitaramos el cálculo. La solución es encapsular este código en una **acción** o **función** que se encargue de calcular el factorial de un número cada vez que se necesite con una llamada que pase el parámetro. De esta forma, el algoritmo principal quedaría más limpio y fácil de entender. + ### 12.2 Funciones +Una función es un conjunto independiente de instrucciones que resuelven una tarea concreta, que devuelve un valor y que puede ser referenciada (invocada o llamada) desde otro punto del programa. + #### 12.2.1. Sintaxis para la declaración de una función +Para definir una función necesitaremos los siguientes elementos: +- **Nombre** para poder identificarla. +- Una serie de valores sobre los que se quieran hacer los cálculos, que se denominan **parámetros formales** de la función. Es necesario indicar el nombre y el tipo de dato de cada uno de los parámetros. Actúan como variables locales a la función. +- Las instrucciones de la función, es decir, el **cuerpo de la función**. +- El valor resultante de la función al que se debe indicar el tipo de dato que se va a devolver. Este valor se devuelve mediante la instrucción `return`. + +Sintaxis: + +```c +function name(param1: type1 , param2: type2, ..., paramn: typen): returnType + ... + ... + return expression; +end function + + +returnType name(type1 param1, type2 param2, ..., typen paramn) { + ... + ... + /* returnValue es el valor que calcula la función */ + return returnValue; +} + +``` + +La **cabecera** es la parte que describe cómo debe llamarse a una función. Indica su nombre, los parámetros que espera y el tipo que retorna. La estructura de la cabecera es la siguiente: + +```c +function name(param1: type1 , param2: type2, ..., paramn: typen): returntype; + +returnType name(type1 param1, type2 param2, ..., typen param); +``` + +Volviendo a nuestro ejemplo del factorial, la función sería: + +```c +function factorial (number: integer): integer + var + fact: integer; + i: integer; + end var + + fact:= 1; + i:=1; + + while i ≤ number do + fact:= fact*i; + i:= i+1; + end while + + return fact; + +end function + +#include + +int factorial(int number) { + int fact; + int i; + + fact=1; + i=1; + + while(i <= number) { + fact = fact*i; + i++; + } + return fact; +} + +``` + +Aquí vemos: +- El nombre factorial para identificar la función. +- Un único parámetro, en este caso un entero, para identificar el número sobre el que queremos hacer el cálculo del factorial. +- El tipo de retorno, que también será entero. +- El cuerpo de la función, que hace los cálculos. + +Recordemos que las variables son locales a la función, es decir, que no se pueden utilizar fuera de la función. ### 12.3. Acciones +A veces interesa un subalgoritmo que realice una serie de instrucciones sin devolver un valor. En estos casos, el lenguaje algorítmico generaliza el concepto de función y define la **acción**. + +Una acción es un conjunto de instrucciones que resuelven una tarea concreta y que puede ser referenciada (invocada o llamada). Se diferencia de una función porque: +- No devuelve ningún valor. +- Cuando modifica un parámetro formal, puede estar también modificando el parámetro real. +- Se debe indicar delante del nombre del parámetro cómo se pasa. Utilizamos las palabras clave `in`, `out` o `inout`. #### 12.3.1. Sintaxis para la declaración de una función +Para definir una acción necesitaremos los siguientes elementos: +- **Nombre** para poder identificarla. +- Una serie de valores sobre los que se quieran hacer los cálculos, que se denominan **parámetros formales** de la acción. Es necesario indicar el nombre y el tipo de dato de cada uno de los parámetros. Actúan como variables locales a la acción. +- Las instrucciones de la acción, es decir, el **cuerpo de la acción**. + +Sintaxis: + +```c +action name(class1 param1: type1 , class2 param2: type2, ..., classn paramn: typen) + ... + ... +end action + +void name(type1 param1, type2 param2, ..., typen paramn) { + ... + ... +} +``` ### 12.4. Cómo utilizar (llamar) a una función y/o acción +Una vez definida la acción o función esta se puede utilizar desde el algoritmo principal o desde otra función o desde otra acción. + +Para llamar a una función o acción se utiliza el nombre de la función o acción seguido de los parámetros que se le pasan entre paréntesis. Los **parámetros reales** pueden ser variables, constantes o expresiones, que deben coincidir en número, tipo y orden con los parámetros formales. + #### 12.4.1. Llamada a una función +Ejemplo: + +```c +{Si suponemos que tenemos la siguiente cabecera} +function name(param1: integer , param2: real, param3: char): integer; + +{y suponemos que tenemos la declaración de las siguientes variables} +var + a: integer; + b: real; + c: char; + result: integer; +end var + +{La manera de llamar a la función sería:} +result := name(a, b, c); +{Las variables a, b y c son los parámetros actuales de la función} + +//Si suponemos que tenemos la siguiente cabecera: +int name(int param1, float param2, char param3); + +//Y suponemos que tenemos la declaración de las siguientes variables: +int a; +float b; +char c; +int result; + +//La manera de llamar a la función en C sería: +result = name(a, b, c); +//Las variables a, b y c son los parámetros actuales de la función +``` + +En el caso del factorial, la cabecera era: + +```c +function factorial (number: integer): integer; +``` + +Las posibles maneras de llamar a la función serían: + +```c +n:= factorial(5); /* utilizando una constante como parámetro actual*/ + +n:= factorial(p) /* utilizando una variable como parámetro actual */ + +n:= factorial(m+2) /* utilizando una expresión como parámetro actual */ + +``` + +Ahora ya podemos ver el algoritmo completo del factorial: + +```c +function factorial (number: integer): integer + var + fact: integer; + i: integer; + end var + + fact:= 1; + i:=1; + + while i ≤ number do + fact:=fact*i; + i:=i+1; + end while + + return fact; + +end function + +algorithm combinations + var + {definimos las variables, dos enteros que hay que leer y una donde almacenar el resultado } + totalNumColors, colorsToCombine: integer ; + result: integer; + end var + + {leemos los números} + totalNumColors:= readInteger(); + colorsToCombine:= readInteger(); + + result := factorial(totalNumColors) div ( factorial(colorsToCombine) * factorial(totalNumColors-colorsToCombine)); + + writeInteger(result); + +end algorithm + +#include + +int factorial(int number) { + int fact; + int i; + + fact = 1; + i = 1; + + while(i <= number) { + fact = fact*i; + i++; + } + return fact; +} + +int main() { + int totalNumColors; + int colorsToCombine; + int result; + + scanf("%d", &totalNumColors); + scanf("%d", &colorsToCombine); + + result = factorial(totalNumColors) / ( factorial(colorsToCombine) * factorial(totalNumColors-colorsToCombine)); + + printf ("%d ", result); + + return 0; + +} + +``` #### 12.4.2. Llamada a una acción +Ejemplo: + +```c +{Si suponemos que tenemos la siguiente cabecera de una acción con todos los parámetros de entrada} +action name(in param1: integer , in param2: real, in param3: char); + +{y suponemos que tenemos la declaración de las siguientes variables} +var + a: integer; + b: real; + c: char; +end var + +{La manera de llamar a la acción sería:} +name(a, b, c); +{Las variables a, b y c son los parámetros actuales de la acción} + +//Si suponemos que tenemos la siguiente cabecera: +void name(int param1, float param2, char param3); + +//Y suponemos que tenemos la declaración de las siguientes variables: +int a; +float b; +char c; + +//La manera de llamar a la acción en C sería: +name(a, b, c); +//Las variables a, b y c son los parámetros actuales de la acción + +``` ### 12.5. Clases de parámetros de una acción +Los parámetros de una acción pueden ser de tres tipos: +- **Parámetro de entrada (in)**. El valor del parámetro solamente será consultado y utilizado dentro de la acción. Esta manera de pasar parámetros es típica de funciones matemáticas y es la única manera que puede usarse en las funciones. +- **Parámetro de salida (out)**. Se utiliza para asignar un valor al parámetro. Si el parámetro ya tiene un valor inicial, este valor no se debería utilizar dentro de la acción. Normalmente se pasan los parámetros de esta manera para que sean inicializados por la acción. Puesto que la acción dejará un valor en el parámetro, este debe ser obligatoriamente una variable. +- **Parámetro de entrada/salida (inout)**. Se utilizan para actualizar el valor del parámetro. En este caso, el valor inicial del parámetro se puede utilizar y también se puede modificar, devolviendo un nuevo valor a quien ha llamado a la acción. Puesto que la acción dejará un nuevo valor en el parámetro, este debe ser obligatoriamente una variable. #### 12.5.1. Parámetros de entrada +Disponemos de una acción que, dados el número de piezas compradas a un mayorista y el precio por pieza, calcula el coste total y lo muestra por el canal estándar, teniendo en cuenta que según el número de piezas, se aplicará un tipo de descuento u otro: + +```c +const + MAX_DISCOUNT: real = 0.15; + MIN_DISCOUNT: real = 0.05; + LIMIT: integer = 1000; +end const + +action computeCost(in num: integer, in price: real) + var + val: real; + discount: real; + end var + + if num ≥ LIMIT then + discount:= MAX_DISCOUNT; + else + discount:= MIN_DISCOUNT; + end if + + val:= integerToReal(num) * price * (1.0 - discount); + writeReal(val); +end action + +algorithm example1 + var + num: integer; + price: real; + end var + + num:= 1005; + price:= 10.0; + computeCost(num, price); +end algorithm + +#include + +#define MAX_DISCOUNT 0.15 +#define MIN_DISCOUNT 0.05 +#define LIMIT 1000 + +void computeCost(int num, float price) { + float val; + float discount; + + if (num >= LIMIT) { + discount = MAX_DISCOUNT; + } else { + discount = MIN_DISCOUNT; + } + + val = (float) (num)*price* (1.0 - discount); + printf("%f", val); +} + +int main() { + float price; + int num; + num = 1005; + price = 10.0; + + computeCost(num, price); + + return 0; +} + +``` + +Comentarios: +- Aunque los parámetros formales y actuales tengan el mismo nombre, se trata de variables diferentes. +- Podemos ver que los parámetros actuales no han cambiado. #### 12.5.2. Parámetros de salida +El ejemplo anterior pero con una acción que tiene un parámetro de salida para devolver el coste: + +```c +const + MAX_DISCOUNT: real = 0.15; + MIN_DISCOUNT: real = 0.05; + LIMIT: integer = 1000; +end const + +action computeCost(in num: integer, in price: real, out val: real) + var + discount: real; + end var + + if num ≥ LIMIT then + discount:= MAX_DISCOUNT; + else + discount:= MIN_DISCOUNT; + end if + + val:= integerToReal(num) * price * (1.0 - discount); +end action + +algorithm example2 + var + num: integer; + price: real; + cost: real; + end var + + num:= 1005; + price:= 10.0; + + computeCost(num, price, cost); + writeReal(cost); + +end algorithm + +#include + +#define MAX_DISCOUNT 0.15 +#define MIN_DISCOUNT 0.05 +#define LIMIT 1000 + +void computeCost(int num, float price, float *val) { + float discount; + + if (num >= LIMIT) { + discount = MAX_DISCOUNT; + } else { + discount = MIN_DISCOUNT; + } + + *val = (float) (num)*price* (1.0 - discount); +} + +int main() { + float price; + int num; + float cost; + num = 1005; + price = 10.0; + + computeCost(num, price, &cost); + + printf("%f", cost); + + return 0; + +} +``` + +Comentarios: +- Al tratarse de una acción, se llama sin asignar el retorno a una variable, ya que las acciones no retornan ningún valor. +- La acción tiene un parámetro formal de salida. Esto quiere decir que cada vez que se modifique este parámetro dentro de la acción, automáticamente se modificará con el mismo valor el parámetro actual correspondiente cost. +- Podemos ver que en lenguaje C, los parámetros de salida son en realidad **Punteros**. Más adelante se muestra un ejemplo. #### 12.5.3. Parámetros de entrada/salida +Supongamos ahora que el coste final es la suma de unos gastos mínimos (coste de entrega, etc.) más el coste de +venta. El coste mínimo viene dado por una constante BASIC_COST. + +```c +const + MAX_DISCOUNT: real = 0.15; + MIN_DISCOUNT: real = 0.05; + LIMIT: integer = 1000; + BASIC_COST: real = 100.0; +end const + +action computeCost(in num: integer, in price: real, inout val: real) + var + discount: real; + end var + + if num ≥ LIMIT then + discount:= MAX_DISCOUNT; + else + discount:= MIN_DISCOUNT; + end if + + val:= val + integerToReal(num) * price * (1.0 - discount); + +end action + +algorithm example3 + var + num: integer; + price: real; + cost: real; + end var + + num:= 1005; + price:= 10.0; + cost:= BASIC_COST; + + computeCost(num, price, cost); + + writeReal(cost); + +end algorithm + +#include + +#define MAX_DISCOUNT 0.15 +#define MIN_DISCOUNT 0.05 +#define LIMIT 1000 +#define BASIC_COST 100.0 + +void computeCost(int num, float price, float *val) { + float discount; + + if (num >= LIMIT) { + discount = MAX_DISCOUNT; + } else { + discount = MIN_DISCOUNT; + } + + *val = *val + (float) (num) * price * (1.0 - discount); +} + +int main() { + float price; + int num; + float cost; + num = 1005; + price = 10.0; + cost = BASIC_COST; + + computeCost(num, price, &cost); + + printf("%f", cost); + + return 0; +} +``` + +Comentarios: +- La única diferencia con el ejemplo 2 es que el parámetro val ahora es de entrada y salida y su valor se actualiza sumándole el coste de venta. ### 12.6. Funciones contra acciones +**Función**: el criterio para usar una función es evaluar si esta puede actuar como una función matemática que, dados unos parámetros, puede retornar un valor concreto que se puede utilizar como un miembro de una expresión. Si es así, claramente debemos escribir una función. +**Acción**: si necesitamos actualizar uno de los parámetros o inicializar uno que no lo está, claramente debemos usar una acción. También se utiliza cuando no se retorna ningún valor o cuando se deben devolver más de uno. + +Muchas veces las acciones tienen algunos parámetros de salida, pero se podría dar el caso de que no tenga +ninguno como, por ejemplo, en el caso de que la acción lea la información del canal estándar y saque también los +resultados directamente por el canal estándar. ### 12.7. Acciones y funciones predefinidas +Tanto en el lenguaje algorítmico como en los de programación, hay algunas acciones y funciones ya definidas por defecto. + +Entre otras tenemos: +- Funciones de conversión de tipo. +- Acciones y funciones de entrada y salida de datos. + +Las funciones de conversión de tipo son esas que transforman una variable de un tipo a otro: + +```c +function integerToReal(x: integer): real; +function realToInteger(x: real): integer; +function charToCode(c: char): integer; +function codeToChar(x: integer): char; + +#include + +int main() { + int x; + float y; + char c; + + y = (float) x; + x = (int) y; + x = (int) c; + c = (char) x; +} +``` +Las acciones/funciones de entrada y salida de datos son esas que permiten recibir datos del exterior (teclado, +ficheros, etc.) y mostrar datos al exterior: + +```c +function readInteger(): integer; +function readReal(): real; +function readChar(): char; +function readString(): string; + +action writeInteger(in x: integer); +action writeReal(in x: real); +action writeChar(in x: char); +action writeString(in x: string); + +#include + +int main() { + int x; + float y; + char c; + string s; + + scanf("%d", &x); + scanf("%f", &y); + scanf("%c", &c); + scanf("%s", s); + + printf("%d ", x); + printf("%f ", y); + printf("%c ", c); + printf("%s ", s); + + return 0; +} +``` ### 12.8. Modularización +Para mantener el conjunto de acciones y funciones ordenadas y que se puedan utilizar en diferentes programas de manera fácil, los lenguajes permiten crear **librerías**. -### 12.9 Ejercicios de autoevaluación +Estas librerías constan de dos partes: +- Un fichero de texto que inclute las cabeceras de las acciones y funciones que la componen y que en C tienen siempre la extensión `.h`. +- El código de las acciones y funciones, que también tendrá un extensión `.c`, pero que solamente tendrá el código de la acciones y/o funciones, sin tener un `main`. + +Cuando se tendan que emplear desde algún otro programa solamente se incluirán en el programa el fichero de cabeceras.h. La sintaxis: + +```c +#include "nomLlibreria.h" +``` + +Concretamente, la librería que incluye las cabeceras de las acciones y funciones de entrada y salida de datos se llama en C `stdio.h`: + +```c +#include +``` + +Hay muchos ejemplos de librerías, por ejemplo: +- Librerías de funciones matemáticas. `math.h`, `stdlib.h`, etc. +- Librerías de funciones y acciones de tratamiento de fechas. `time.h`, `date.h`, etc. +- Librerías de funciones de tratamiento de cadenas de caracteres. `string.h`, etc. + +La posibilidad de crear las librerías facilita la reutilización del código y estandarización de la manera de trabajar. -### 12.10 Soluciones +### 12.9 Ejercicios + +1. Diseñad una función que, dado un entero n, calcule la suma de los primeros n números de la serie Fibonacci. + +[Solución en lenguaje C]() + +2. Diseñad una acción que, dados un real que representa el saldo de una cuenta corriente y otro real que representa un cargo o imposición, actualice el saldo de la cuenta. + +[Solución en lenguaje C]() + +3. Diseñad una función que calcule n (real) elevado a la potencia m (entero). + +[Solución en lenguaje C]() + +4. Diseñad una acción que, dados tres números reales que representan los coeficientes de una ecuación de segundo grado, retorne sus raíces, en caso de que existan. En caso contrario, que inicialice las raíces a 0. Recordad que dada la ecuación A * x2 + B * x + C las raíces se calculan: + +``` +x1 = (-B + sqrt(B2 - 4 * A * C)) / (2 * A) +``` +Si la expresión dentro de la raíz es negativa quiere decir que la ecuación no tiene raíces. Podéis suponer que la función raíz cuadrada está ya definida y su cabecera es: +``` +function squareRoot(x: real): real; +``` + +[Solución en lenguaje C]() + +4b. Declarad las variables necesarias para llamar a la acción del ejercicio anterior e indicad cómo se llamaría. + +[Solución en lenguaje C]() + +5. Para cada apartado siguiente, decidid si es mejor una acción o una función y definid su cabecera (solamente se pide la cabecera, no hay que diseñar ningún algoritmo). +- Dadas las dimensiones de una pared (altura y anchura) y el precio de pintura por metro cuadrado, determinad el coste total de pintura necesario para pintar la pared. +- Dado el consumo total de agua acumulado desde el mes de enero hasta junio (ambos incluidos), leed por el canal estándar el consumo del resto de meses (desde julio a diciembre, ambos incluidos), y retornadlo acumulado al total del consumo de los 6 primeros meses. +- Dados dos números enteros intercambiad los valores de ambas variables siempre que ambas sean diferentes de cero. +- Dado un entero retornad la suma de sus divisores. + +[Solución en lenguaje C]() + +5b. Declarad las variables necesarias para llamar a las acciones del ejercicio anterior e indicad cómo se llamarían. + +[Solución en lenguaje C]() +### 12.10 Soluciones en lenguaje algorítmico +1. Solución +```c + +``` + +2. Solución + +```c + +``` + +3. Solución + +```c + +``` + +4. Solución + +```c + +``` + +4b. Solución + +```c + +``` + +5. Solución + +```c + +``` + +5b. Solución + +```c + +```