Autor: José C. Riquelme (Universidad de Sevilla) TEMA 5. Tratamientos secuenciales 1. Introducción Es habitual que en numerosos cursos de programación universitarios en titulaciones de Ingeniería se aprenda a resolver determinados problemas sin ninguna metodología específica, sino siguiendo la técnica de “prueba y error”. Esto es, se escribe una aproximación al algoritmo solución y después se va depurando hasta conseguir la solución al problema. En estos cursos se plantean a los alumnos diferentes problemas que se solucionan todos de forma distinta cuando muchos de ellos tienen una solución común. Esta forma de enseñar a programar es la que confiere a la programación ese (nefasto) matiz de “arte”, en el sentido de que hacer un programa se parece a estar pintando un cuadro y se van dando pinceladas aquí y allá hasta que el resultado guste. En esos (malos) cursos se incide demasiado en la solución específica de un problema determinado, sin dar al alumno una visión ingenieril de la programación. En este manual se va a intentar agrupar un conjunto de problemas que son muy comunes en programación y cuya solución presenta las suficientes similitudes como para conseguir una solución sistemática para ellos. Supongamos tenemos una colección de datos que pueden ser de muy diversos tipos: los empleados de una empresa, los alumnos de un curso, los pacientes de un hospital, las ventas de unos grandes almacenes, los productos de un almacén, los resultados de un experimento biológico, los vuelos de un aeropuerto, los resultados de una encuesta electoral… Todos estos datos estarán formados preferentemente por valores numéricos (enteros o reales) y cadenas de caracteres almacenados en arrays (de una o dos dimensiones). Por ejemplo, el dni o el sueldo de un empleado, las notas de los alumnos, la edad de los pacientes, el código de una venta, el precio de un producto, el pH de un tejido, la hora de salida de un avión, el número de votantes a un partido… ¿Cuáles son las preguntas típicas que se pueden consultar sobre este tipo de datos? Pongamos algunos ejemplos: ¿Cuánto vale la suma de todas las nominas de la empresa de mayores de 50 años? ¿Cuántos alumnos han aprobado todas las asignaturas? ¿Ha habido alguna venta superior a 1000 euros hoy? ¿Cuál es el producto con mayor stock en el almacén? ¿Cuál es el próximo avión con plazas libres y destino Madrid? ¿Todos los tejidos superan un pH de cuatro? ¿Cuál es el partido mayoritario entre los mayores de 60 años? En general, estas preguntas se pueden simplificar en las siguientes: Sumar o acumular Contar Tema 5. Tratamientos secuenciales Existe algún elemento Todos los elementos cumplen Hallar el máximo Hallar el mínimo ¿Es posible que todas estas preguntas tengan una base común para su solución algorítmica? La respuesta es sí. 2. Esquemas algorítmicos básicos Todos los problemas sobre colecciones deben hacer un recorrido sobre la misma mediante un bucle. Por otro lado, si no se requiere realizar el cálculo sobre toda la colección se deben filtrar los elementos que intervienen mediante una bifurcación. Por tanto, el esquema general debe ser: Para todo elemento de la colección Si el elemento cumple la propiedad Realizar cálculo sobre elemento Todas las preguntas se responden mediante la actualización de una variable que almacena los resultados intermedios al recorrer la colección. Por ejemplo, si estamos hallando una suma, la suma parcial hasta el elemento que estamos tratando, el contador parcial, el máximo hasta ese momento, etc. Dependiendo de la pregunta que se responda el tipo y la inicialización de la variable será diferente. Por ejemplo, si la pregunta es hallar la suma de valores reales, el tipo será numérico real y la inicialización a cero, si es un contador será de tipo entero y también inicializada a cero, si es un máximo o mínimo será del tipo del elemento de la colección y se pueden inicializar con el primer valor de la colección, si la pregunta es existe o para todo la salida es un valor booleano de cierto o falso. Podemos entonces refinar entonces el seudocódigo anterior: Inicializa variable a devolver con un valor neutro Para todo elemento de la colección Si el elemento cumple la propiedad variable = funcion (variable, elemento) Finsi FinPara Devuelve variable La función que actualiza variable lógicamente depende del problema: en un contador será sumar uno, en una suma, sumar variable y elemento, en un existe será una disyunción lógica entre variable y una propiedad de elemento, etc. Concretemos el seudocódigo anterior en lenguaje C con varios ejercicios de enunciado similar sobre una misma colección pero con preguntas diferentes. En todos los Autor: José C. Riquelme (Universidad de Sevilla) ejercicios se pide construir una función en C que reciba un array y devuelva un valor sobre el mismo (suma, contador, máximo,…). El array será un parámetro constante puesto que no se modificará en la función y como siempre la función deberá recibir también el tamaño real del array, porque ya sabemos que no tiene por qué estar completo. 2.1 Esquema contador. Dado un array de números reales y un umbral p codifique una función que devuelva el número de elementos del array que son mayores estrictos que p. Entrada: un array de reales, un entero con el tamaño y un real con el umbral Salida: un entero con el contador int contador(const TablaReales t, int n, float p){ int i; int cont=0; for (i=0;i<n;i++){ if (t[i]>p){ cont ++; } } return cont; } 2.2 Esquema suma. Dado un array de números reales y un umbral p codifique una función que devuelva la suma de los elementos del array mayores estrictos que p. Entrada: un array de reales, un entero con el tamaño y un real con el umbral Salida: un real con la suma float sumador(const TablaReales t, int n, float p){ int i; float suma=0.; for (i=0;i<n;i++){ if (t[i]>p){ suma = suma + t[i]; } } return suma; } 2.2 Esquema existe. Dado un array de números reales y un umbral p codifique una función que devuelva si existe algún elemento del array que sea mayor estricto que p. Entrada: un array de reales, un entero con el tamaño y un real con el umbral Salida: un lógico que devuelve cierto si existe y falso en caso contrario Logico existe(const TablaReales t, int n, float p){ int i; Logico hay=falso; Tema 5. Tratamientos secuenciales for (i=0;i<n;i++){ hay = hay || (t[i]>p); } return hay; } En este caso, el if sería redundante por eso se ha eliminado. Otra solución para este problema sería: Logico existe(const TablaReales t, int n, float p){ int i; Logico hay=falso; for (i=0;i<n;i++){ if ((t[i]>p){ hay = cierto; break; } } return hay; } La principal diferencia con respecto al esquema anterior es que el bucle termina cuando encuentra un elemento cumpliendo la condición. 2.3 Esquema paraTodo. Dado un array de números reales y un umbral p codifique una función que devuelva si todos los elementos del array son mayores estricto que p Entrada: un array de reales, un entero con el tamaño y un real con el umbral Salida: un lógico que devuelve cierto si todos cumplen y falso en caso contrario Logico paraTodo(const TablaReales t, int n, float p){ int i; Logico todo=cierto; for (i=0;i<n;i++){ todo = todo && (t[i]>p); } return todo; } 2.4 Esquema máximo sin filtro. Dado un array de números reales codifique una función que devuelva el mayor de los elementos del array Entrada: un array de reales, un entero con el tamaño Salida: un valor real con el máximo del array float maximo(const TablaReales t, int n){ int i; float max; Autor: José C. Riquelme (Universidad de Sevilla) max=t[0]; for (i=0;i<n;i++){ max = mayor(max,t[i]); } return max; } float mayor(float x,float y){ float m; if (x>y) m=x; else m=y; return m; } 2.5 Esquema mínimo sin filtro. Dado un array de números reales codifique una función que devuelva el menor de los elementos del array Entrada: un array de reales, un entero con el tamaño Salida: un real con el valor mínimo del array float minimo(const TablaReales t, int n){ int i; float min; min=t[0]; for (i=0;i<n;i++){ min = menor(min,t[i]); } return min; } float menor(float x,float y){ float m; if (x>y) m=y; else m=x; return m; } Los dos ejercicios de máximo y mínimo están suponiendo que en el array hay al menos un elemento. Si n fuera 0 esta función podría dar un error de ejecución. Esa cuestión debe ser indicada en la documentación de la función, de forma que cuando se invocara en el programa principal debería comprobarse antes de su llamada. Esto es, void main(void){ int numelem; numelem = leeTabla(v); if (numelem>0){ printf("maximo: %f\n",maximo(v,numelem)); printf("minimo: %f\n",minimo(v,numelem)); } } Tema 5. Tratamientos secuenciales 2.6 Esquemas máximo y mínimo con filtro. Calcular el máximo o mínimo de los elementos de una colección que cumplan alguna condición, tiene una dificultad añadida y es qué debería devolver la función si ningún elemento de la colección cumple la condición. Además la inicialización de la variable a devolver tampoco se podría hacer con el primer elemento pues puede que éste no cumpla la condición. Dejaremos este ejercicio para más adelante. 3. Otros esquemas algorítmicos Hay otros esquemas algorítmicos que no cumplen exactamente el patrón anterior pero se aproximan bastante. En concreto son los esquemas de búsqueda y filtrado. 3.1 Esquema búsqueda. Dado un array de números reales y un valor real p devolver la primera posición del array donde se encuentra p ó -1 si no estuviera. Entrada: un array de números reales, un entero con el tamaño, un valor real a buscar Salida: un entero con la primera posición del primer elemento del array igual al argumento que se busca ó -1 si no está. int busca(const TablaReales t, int n, float p){ int i; int pos=-1; for(i=0;i<n;i++){ if (t[i]==p){ pos=i; break; } } return pos; } Obsérvese como el empleo de la sentencia break consigue que la primera vez que p es encontrado se abandona el bucle de búsqueda. Si el problema en vez de localizar la primera posición donde está p, buscara la última, en la solución anterior bastaría con quitar el break. El programa principal podría ser: void main(void){ TablaReales v={1.2, 3.4, 5.4, 6.4, 7.8}; int pos; float valor; printf("Introduce valor a buscar: "); scanf("%f",&valor); pos = busca(v,5,valor); Autor: José C. Riquelme (Universidad de Sevilla) if (pos==-1) printf("El elemento %f no está en el array\n",valor); else printf("El elemento %f está en la posición %d\n",valor,pos); } 3.2 Esquema filtrado. Dado un array de números reales y un valor real p, devolver otro array con los valores del primero que son mayores estrictos que p. Entrada: un array de números reales, un entero con el tamaño, un valor real umbral. Salida: un array de números reales, un entero con su tamaño. Este problema tiene una novedad con respecto a los resueltos anteriormente y es que por primera vez la función a construir debe devolver dos datos diferentes: el array filtrado y su tamaño. Debemos hacer hincapié otra vez que si se va a devolver un array con los elementos filtrados a la función main, debe también devolverse el número de elementos que tiene. Para ello y teniendo en cuenta que los arrays son argumentos que pueden cambiar en la función si no se les pone la clausula const, haremos que la función devuelva el tamaño del array filtrado en el nombre de la función. int filtra(const TablaReales t, int n, TablaReales v, float p){ int i; int pos=0; for (i=0;i<n;i++){ if (t[i]>p){ v[pos]=t[i]; pos++; } } return pos; } La función principal podría ser: void main(void){ TablaReales v={1.2, 3.4, 5.4, 6.4, 7.8}; TablaReales v2; int nelem; float valor; printf("Introduce valor umbral: "); scanf("%f",&valor); nelem = filtra(v,5,v2,valor); imprime(v2,nelem); } Tema 5. Tratamientos secuenciales Para visualizar en pantalla un array de reales podemos implementar una función imprime: void imprime(const TablaReales t, int n){ int i; printf("\n["); for(i=0;i<n-1;i++){ printf("%6.2f, ",t[i]); } printf("%6.2f ]\n",t[n-1]); } Donde el último elemento se imprime fuera del bucle para cerrar el corchete en vez de poner una coma. 4. Problemas. 1. Construye una función que lea un entero n y a continuación lea n valores reales y los guarde en un array. Para cada una de las siguientes cuestiones cree una función que recibirá como argumentos un array del tipo correspondiente, el número de elementos y si fuera necesario algún valor más que se indique. Cada función devolverá el valor solicitado del tipo int, double o Lógico. 2. Dado un array que contiene el registro diario de aumento de peso de un paciente durante los 365 días de un año, indicar cuál ha sido el peso ganado (o perdido). 3. Dado un array que contiene los nucleótidos de una secuencia de ADN, indique cuántas Timinas (‘T’) hay. 4. Generalice la función del problema anterior para que sea capaz de calcular cuántos nucleótidos hay de cada tipo. 5. Dado un array de valores enteros escriba una función que devuelva el valor máximo de entre los elementos pares. 6. Dado un array de valores de frecuencia cardiaca (enteros) y el intervalo compuesto por dos valores [a, b], que pueden indicar los límites de la taquicardia [100, 175] o la bradicardia [35, 60], escriba una función que indique si alguno de los valores del array está comprendido en el intervalo. 7. Dado un array que contiene los datos de frecuencia cardiaco (enteros) de unos pacientes, indicar cuál es el más bajo. Autor: José C. Riquelme (Universidad de Sevilla) 8. Dado un array que contiene un codón (grupo de tres nucleótidos que codifican un aminoácido) indique si se trata de Lisina (AAA). 9. Generalice el problema anterior a cualquier número de nucleótidos y cualquier repetición de éstos. 10. Dado un array que representa una secuencia de ADN, donde cada posición contiene un nucleótido, escriba una función que nos indique si alguno de los que contiene es la Timina (‘T’). 11. Generalice el problema anterior para cualquier nucleótido. 12. Dado un array con los sueldos de unos empleados escriba una función que indique si todos tienen un sueldo superior a 1000€. 13. Generalice la función a cualquier cantidad. 14. Dado un array con las edades de un grupo de personas, escriba una función que devuelva otro array con los valores correspondientes a la edad de trabajar, es decir, comprendida entre 16 y 65 años. 15. Generalice el problema anterior a otras edades por si cambia las edades de primer contrato o jubilación. 16. Dado un array de valores reales y un intervalo compuesto por dos valores [a,b], escriba una función que devuelva la posición en el array del primer elemento que está contenido en el intervalo. En caso de que ninguno esté contenido, devuelva el valor -1. 17. Dado un array con edades de pacientes de un hospital, donde cada posición en el array se corresponde con la habitación que ocupa, indique en qué habitación se encuentra el paciente más joven. 18. Dado un array de valores de ritmo cardiaco (enteros), filtre aquellos que están por encima de los límites de normalidad (100). 19. Dado un array que contiene nucleótidos de una secuencia de ADN, recupere en otro array todas aquellas apariciones de Adenina y Timina. 20. Dado un array que contiene nucleótidos de una secuencia de ADN, encuentre la secuencia complementaria, esto es, una secuencia en la que la Adenina se sustituya por Timina y la Guanina por Citosina.