5. Instrucciones Repetitivas
5.1. Introducción
En muchos problemas notamos una regularidad que sugiere que su solución puede lograrse repitiendo un paso que vaya transformando gradualmente el estado del mundo modelado y acercándose a la solución. Instintivamente es lo que hacemos cuando subimos unas escaleras: repetimos el paso de subir un escalón hasta que llegamos al final. Otro ejemplo posible es si suponemos que tenemos en una hoja de papel una lista de palabras sin ningún orden y nos piden buscar si la palabra "casa" está en la lista. El algoritmo que seguimos para realizar está tarea puede ser descrito de la siguiente manera:
- Verifique si la primera palabra es igual a "casa".
- Si lo es, no busque más. Si no lo es, busque la segunda palabra.
- Verifique si la segunda palabra es igual a "casa".
- Si lo es, no busque más. Si no lo es, busque la tercera palabra.
- Repita el procedimiento palabra por palabra, hasta que la encuentre o hasta que no haya más palabras para buscar.
Tarea 1
Objetivo: Explicar el significado de la instrucción repetitiva y usarla para definir un algoritmo que resuelva un problema simple.
Suponga que en el ejemplo anterior, ya no queremos buscar una palabra sino contar el número total de letras que hay en todas las palabras de la hoja.
Escriba el algoritmo para resolver el problema:
5.2. Calcular el Promedio de las Notas
Para resolver el segundo requerimiento del caso de estudio (R2 - calcular el promedio de las notas), debemos calcular la suma de todas las notas del curso para luego dividirlo por el número de estudiantes. Esto se puede hacer con el método que se muestra a continuación:
public double promedio( )
{
double suma = notas[ 0 ] + notas[ 1 ] + notas[ 2 ] +
notas[ 3 ] + notas[ 4 ] + notas[ 5 ] +
notas[ 6 ] + notas[ 7 ] + notas[ 8 ] +
notas[ 9 ] + notas[ 10 ] + notas[ 11 ];
return suma / TOTAL_EST;
}
- Primero sumamos las notas de todos los estudiantes y guardamos el valor en la variable suma.
- El promedio corresponde a dividir dicho valor por el número de estudiantes, representado con la constante
TOTAL_EST
.
Si planteamos el problema de manera iterativa, podemos escribir el mismo método de la siguiente manera, en la cual, en cada paso, acumulamos el valor del siguiente elemento:
public double promedio( )
{
double suma = 0.0;
int indice = 0;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
indice++;
suma += notas[ indice ];
return suma / TOTAL_EST;
}
- Esta solución también calcula el promedio del curso, pero en lugar de hacer referencia directa a las doce casillas del arreglo, utiliza un índice que va desplazando desde 0 hasta 11.
- Por supuesto que es más clara la solución anterior, pero queremos utilizar este ejemplo para introducir las instrucciones iterativas, que expresan esta misma idea de "desplazar" un índice, pero usando una sintaxis mucho más compacta.
- Lo primero que debemos notar es que vamos a ejecutar 12 veces (
TOTAL_EST
veces para ser exactos) un grupo de instrucciones. - Ese grupo de instrucciones es:
suma += notas[ indice ]; indice++
; - Después de ejecutar 12 veces esas dos instrucciones, en la variable suma tendremos el valor total, listo para dividirlo por el número de estudiantes.
- El índice comienza teniendo el valor 0 y termina teniendo el valor 11. De esta manera, cada vez que hacemos referencia al elemento
notas[indice]
, estamos hablando de una casilla distinta del arreglo.
Allí repetimos 12 veces una pareja de instrucciones, una vez por cada elemento del arreglo. Basta un poco de reflexión para ver que lo que necesitamos es poder decir que esas dos instrucciones se deben repetir tantas veces como notas haya en el arreglo. Las instrucciones repetitivas nos permiten hacer eso de manera sencilla. En el siguiente método se ilustra el uso de la instrucción while
para el mismo problema del cálculo del promedio.
public double promedio( )
{
double suma = 0.0;
int indice = 0;
while( indice < TOTAL_EST )
{
suma += notas[ indice ];
indice++;
}
return suma / TOTAL_EST;
}
- La estructura del método sigue siendo la misma, con la única diferencia de que en lugar de repetir 12 veces la pareja de instrucciones, las incluimos dentro de la instrucción
while
, que se encarga de ejecutar repetidamente las instrucciones que tiene en su interior. - La instrucción
while
sirve para decirle al computador que "mientras que" una condición se cumpla, siga ejecutando las instrucciones que están por dentro. - La condición en el ejemplo es
indice < TOTAL_EST
, que equivale a decirle que "mientras que" el índice no llegue a 12, vuelva a ejecutar la pareja de instrucciones que tiene asociadas.
Ahora veremos las partes de las instrucciones repetitivas y su significado.
5.3. Componentes de una Instrucción Repetitiva
La figura 3.5 ilustra la manera en que se ejecuta una instrucción repetitiva. Primero, y por una sola vez, se ejecutan las instrucciones que vamos a llamar de inicio o preparación del ciclo. Allí se le da el valor inicial al índice y a las variables en las que queremos acumular los valores durante el recorrido. Luego, se evalúa la condición del ciclo. Si es falsa, se ejecutan las instrucciones que se encuentran después del ciclo. Si es verdadera, se ejecutan las instrucciones del cuerpo del ciclo para finalmente volver a repetir el mismo proceso. Cada repetición, que incluye la evaluación de la condición y la ejecución del cuerpo del ciclo, recibe el nombre de iteración o bucle.
Fig. 3.5 Ejecución de una instrucción repetitiva |
---|
Usualmente en un lenguaje de programación hay varias formas de escribir una instrucción repetitiva. En Java existen varias formas, pero en este libro sólo vamos a presentar dos de ellas: la instrucción for
y la instrucción while
.
5.3.1. Las Instrucciones for y while
Una instrucción repetitiva con la instrucción while
se escribe de la siguiente manera:
<inicio>
while( <condición> )
{
<cuerpo>
<avance>
}
- Las instrucciones de preparación del ciclo van antes de la instrucción repetitiva.
- La condición que establece si se debe repetir de nuevo el ciclo va siempre entre paréntesis.
- El avance del ciclo es una parte opcional, en la cual se modifican los valores de algunos de los elementos que controlan la salida del ciclo (avanzar el índice con el que se recorre un arreglo sería parte de esta sección).
Una instrucción repetitiva con la instrucción for
se escribe de la siguiente manera:
<inicio1>
for( <inicio2>; <condición>; <avance> )
{
<cuerpo>
}
- El inicio va separado en dos partes: en la primera, va la declaración y la inicialización de las variables que van a ser utilizadas después de terminado el ciclo (la variable
suma
, por ejemplo, en el método del promedio). En la segunda parte de la zona de inicio van las variables que serán utilizadas únicamente dentro de la instrucción repetitiva (la variableíndice
, por ejemplo, que sólo sirve para desplazarse recorriendo las casillas del arreglo). - La segunda parte del inicio, lo mismo que el avance del ciclo, se escriben en el encabezado de la instrucción
for
.
Ejemplo 3
Objetivo: Mostrar la manera de utilizar la instrucción iterativa for
.
En este ejemplo se presenta una implementación del método que calcula el promedio de notas del caso de estudio, en la cual se utiliza la instrucción for
.
public double promedio( )
{
double suma = 0.0;
for(int indice = 0; indice < TOTAL_EST; indice++ )
{
suma += notas[ indice ];
}
return suma / TOTAL_EST;
}
- Puesto que la variable "
suma
" será utilizada por fuera del cuerpo del ciclo, es necesario declararla antes del for. - La variable "
indice
" es interna al ciclo, por eso se declara dentro del encabezado. - El avance del ciclo consiste en incrementar el valor del "
indice
". - En este ejemplo, los corchetes del
for
son opcionales, porque sólo hay una instrucción dentro del cuerpo del ciclo.
Vamos a ver en más detalle cada una de las partes de la instrucción y las ilustraremos con algunos ejemplos.
5.3.2. El Inicio del Ciclo
El objetivo de las instrucciones de inicio o preparación del ciclo es asegurarnos de que vamos a empezar el proceso repetitivo con las variables de trabajo en los valores correctos. En nuestro caso, una variable de trabajo la utilizamos como índice para movernos por el arreglo y la otra para acumular la suma de las notas:
- La suma antes de empezar el ciclo debe ser cero:
double suma = 0.0;
- El índice a partir del cual vamos a iterar debe ser cero:
int indice = 0;
5.3.3. La Condición para Continuar
El objetivo de la condición del ciclo es identificar el caso en el cual se debe volver a hacer una nueva iteración. Esta condición puede ser cualquier expresión lógica: si su evaluación da verdadero, significa que se deben ejecutar de nuevo las instrucciones del ciclo. Si es falsa, el ciclo termina y se continúa con la instrucción que sigue después de la instrucción repetitiva.
Típicamente, cuando se está recorriendo un arreglo con un índice, la condición del ciclo dice que se debe volver a iterar mientras el índice sea menor que el número total de elementos del arreglo. Para indicar este número, se puede utilizar la constante que define su tamaño (TOTAL_EST
) o el operador que calcula el número de elementos de un arreglo (notas.length
).
Dado que los arreglos comienzan en 0, la condición del ciclo debe usar el operador
<
y el número de elementos del arreglo. Son errores comunes comenzar los ciclos con el índice en 1 o tratar de terminar con la condiciónindice <= notas.length
.
5.3.4. El Cuerpo del Ciclo
El cuerpo del ciclo contiene las instrucciones que se van a repetir en cada iteración. Estas instrucciones indican:
- La manera de modificar algunas de las variables de trabajo para ir acercándose a la solución del problema. Por ejemplo, si el problema es encontrar la suma de las notas de todos los estudiantes del curso, con la instrucción
suma += notas[indice]
agregamos un nuevo valor al acumulado. - La manera de modificar los elementos del arreglo, a medida que el índice pasa por cada casilla. Por ejemplo, si queremos sumar una décima a todas las notas, lo hacemos con la instrucción
notas[indice] += 0.1
.
5.3.5. El Avance del Ciclo
Cuando se recorre un arreglo, es necesario mover el índice que indica la posición en la que estamos en un momento dado (indice++
). En algún punto (en el avance o en el cuerpo) debe haber una instrucción que cambie el valor de la condición para que finalmente ésta sea falsa y se detenga así la ejecución de la instrucción iterativa. Si esto no sucede, el programa se quedará en un ciclo infinito.
Si construimos un ciclo en el que la condición nunca sea falsa (por ejemplo, si olvidamos escribir las instrucciones de avance del ciclo), el programa dará la sensación de que está bloqueado en algún lado, o podemos llegar al error: java.lang.OutOfMemoryError
Tarea 2
Objetivo: Practicar el desarrollo de métodos que tengan instrucciones repetitivas.
Para el caso de estudio de las notas de los estudiantes escriba los métodos de la clase Curso que resuelven los problemas planteados.
Calcular el número de estudiantes que sacaron una nota entre 3,0 y 5,0:
public int calcularCantidadAprobados( )
{
}
Calcular la mayor nota del curso:
public double calcularMayorNota( )
{
}
Contar el número de estudiantes que sacaron una nota inferior a la del estudiante que está en la posición del arreglo que se entrega como parámetro. Suponga que el parámetro pPosEst
tiene un valor comprendido entre 0
y TOTAL_EST – 1
.
public int calcularCantidadNotasInferioresA( int pPosEst )
{
}********
Aumentar el 5% todas las notas del curso, sin que ninguna de ellas sobrepase el valor 5,0:
public void hacerCurva( )
{
}
5.4. Patrones de Algoritmo para Instrucciones Repetitivas
Cuando trabajamos con estructuras contenedoras, las soluciones de muchos de los problemas que debemos resolver son similares y obedecen a ciertos esquemas ya conocidos (¿cuántas personas no habrán resuelto ya los mismos problemas que estamos aquí resolviendo?). En esta sección pretendemos identificar tres de los patrones que más se repiten en el momento de escribir un ciclo, y con los cuales se pueden resolver todos los problemas del caso de estudio planteados hasta ahora. Lo ideal sería que, al leer un problema que debemos resolver (el método que debemos escribir), pudiéramos identificar el patrón al cual corresponde y utilizar las guías que existen para resolverlo. Eso simplificaría enormemente la tarea de escribir los métodos que tienen ciclos.
Un patrón de algoritmo se puede ver como una solución genérica para un tipo de problemas, en la cual el programador sólo debe resolver los detalles particulares de su problema específico.
En esta sección vamos a introducir tres patrones que se diferencian por el tipo de recorrido que hacemos sobre la secuencia.
5.4.1. Patrón de Recorrido Total
En muchas ocasiones, para resolver un problema que involucra una secuencia, necesitamos recorrer todos los elementos que ésta contiene para lograr la solución. En el caso de estudio de las notas tenemos varios ejemplos de esto:
- Calcular la suma de todas las notas.
- Contar cuántos en el curso obtuvieron la nota 3,5.
- Contar cuántos estudiantes aprobaron el curso.
- Contar cuántos en el curso están por debajo del promedio (conociendo este valor).
- Aumentar en 10% todas las notas inferiores a 2,0.
¿Qué tienen en común los algoritmos que resuelven esos problemas? La respuesta es que la solución requiere siempre un recorrido de todo el arreglo para poder cumplir el objetivo que se está buscando: debemos pasar una vez por cada una de las casillas del arreglo. Esto significa:
- Que el índice para iniciar el ciclo debe empezar en cero.
- Que la condición para continuar es que el índice sea menor que la longitud del arreglo.
- Que el avance consiste en sumarle uno al índice.
Esa estructura que se repite en todos los algoritmos que necesitan un recorrido total es lo que denominamos el esqueleto del patrón, el cual se puede resumir con el siguiente fragmento de código:
for( int indice = 0; indice < arreglo.length; indice++ )
{
<cuerpo>
}
- Es común que en lugar de la variable "
indice
" se utilice una variable llamada "i
". Esto hace el código un poco más compacto. - En lugar del operador "
length
", se puede utilizar también la constante que indica el número de elementos del arreglo. - Los corchetes del "
for
" sólo son necesarios si el cuerpo tiene más de una instrucción.
Lo que cambia en cada caso es lo que se quiere hacer en el cuerpo del ciclo. Aquí hay dos variantes principales. En la primera, algunos de los elementos del arreglo van a ser modificados siguiendo una regla (por ejemplo, aumentar en 10% todas las notas inferiores a 2,0). Lo único que se hace en ese caso es reemplazar el
Ejemplo 4
Objetivo: Mostrar la primera variante del patrón de recorrido total.
En este ejemplo se presenta la implementación del método de la clase Curso que aumenta en 10% todas las notas inferiores a 2,0.
public void hacerCurva( )
{
for( int i = 0; i < notas.length; i++ )
{
if( notas[ i ] < 2.0 )
{
notas[ i ] = notas[ i ] * 1.1;
}
}
}
- El esqueleto del patrón de algoritmo de recorrido total se copia dentro del cuerpo del método.
- Se reemplaza el cuerpo del patrón por la instrucción condicional que hace la modificación pedida.
- En el cuerpo se indica la modificación que debe sufrir el elemento que está siendo referenciado por el índice con el que se recorre el arreglo.
La segunda variante corresponde a calcular alguna propiedad sobre el conjunto de elementos del arreglo (por ejemplo, contar cuántos estudiantes aprobaron el curso). Esta variante implica cuatro decisiones que definen la manera de completar el esqueleto del patrón:
- Cómo acumular la información que se va llevando a medida que avanza el ciclo.
- Cómo inicializar dicha información.
- Cuál es la condición para modificar dicho acumulado en el punto actual del ciclo.
- Cómo modificar el acumulado.
En el ejemplo 5 se ilustra esta variante.
Ejemplo 5
Objetivo: Mostrar la segunda variante del patrón de recorrido total.
En este ejemplo se presenta la aplicación del patrón de algoritmo de recorrido total, para el problema de contar el número de estudiantes que aprobaron el curso.
- ¿Cómo acumular información?
Vamos a utilizar una variable de tipo entero llamada vanAprobando
, que va llevando durante el ciclo el número de estudiantes que aprobaron el curso.
- ¿Cómo inicializar el acumulado?
La variable vanAprobando
se debe inicializar en 0, puesto que inicialmente no hemos encontrado todavía ningún estudiante que haya pasado el curso.
- ¿Condición para cambiar el acumulado?
Cuando notas[ indice ]
sea mayor o igual a 3,0, porque quiere decir que hemos encontrado otro estudiante que pasó el curso.
- ¿Cómo modificar el acumulado?
El acumulado se modifica incrementándolo en 1.
public int darCantidadAprobados( )
{
int vanAprobando = 0;
for( int i = 0; i < notas.length; i++ )
{
if( notas[ i ] >= 3.0 )
{
vanAprobando++;
}
}
return vanAprobando;
}
- Las cuatro decisiones tomadas anteriormente van a definir la manera de completar el esqueleto del algoritmo definido por el patrón.
- Las decisiones 1 y 2 definen el inicio del ciclo.
- Las decisiones 3 y 4 ayudan a construir el cuerpo del mismo.
A continuación se muestra cómo sería el método anterior utilizando la instrucción for-each.
public int darCantidadAprobados( )
{
int vanAprobando = 0;
for( Double nota: notas )
{
if( nota >= 3.0 )
{
vanAprobando++;
}
}
return vanAprobando;
}
En resumen, si el problema planteado corresponde al patrón de recorrido total, se debe identificar la variante y luego tomar las decisiones que definen la manera de completar el esqueleto.
Tarea 3
Objetivo: Generar habilidad en el uso del patrón de algoritmo de recorrido total.
Escriba los métodos de la clase Curso que resuelven los siguientes problemas, los cuales corresponden a las dos variantes del patrón de algoritmo de recorrido total.
Escriba un método para modificar las notas de los estudiantes de la siguiente manera: a todos los que obtuvieron más de 4,0, les quita 0,5. A todos los que obtuvieron menos de 2,0, les aumenta 0,5. A todos los demás, les deja la nota sin modificar:
public void cambiarNotas( )
{
}
Escriba un método que retorne la menor nota del curso:
public double darMenorNota ( )
{
}
Escriba un método que indique en cuál rango se encuentra la mayoría de las notas del curso. Los rangos están definidos de la siguiente manera: rango 1 de 0,0 a 1,99, rango 2 de 2,0 a 3,49, rango 3 de 3,5 a 5,0. El método debe retornar el número del rango.
public int darRangoConMasNotas( )
{
}
5.4.2. Patrón de Recorrido Parcial
En algunos problemas de manejo de secuencias no es necesario recorrer todos los elementos para lograr el objetivo propuesto. Piense en la solución de los siguientes problemas:
- Informar si algún estudiante obtuvo la nota 5,0.
- Buscar el primer estudiante con nota igual a cero.
- Indicar si más de 3 estudiantes perdieron el curso.
- Aumentar el 10% en la nota del primer estudiante que haya sacado más de 4,0.
En todos esos casos hacemos un recorrido del arreglo, pero éste debe terminar tan pronto hayamos resuelto el problema. Por ejemplo, el método que informa si algún estudiante obtuvo cinco en la nota del curso debe salir del proceso iterativo tan pronto localice el primer estudiante con esa nota. Sólo si no lo encuentra, va a llegar hasta el final de la secuencia.
Un recorrido parcial se caracteriza porque existe una condición que debemos verificar en cada iteración para saber si debemos detener el ciclo o volver a repetirlo.
En este patrón, debemos adaptar el esqueleto del patrón anterior para que tenga en cuenta la condición de salida, de la siguiente manera:
boolean termino = false;
for( int i = 0; i < arreglo.length && !termino; i++ )
{
<cuerpo>
if( <ya se cumplió el objetivo> )
{
termino = true;
}
}
- Primero, declaramos una variable de tipo
boolean
para controlar la salida del ciclo, y la inicializamos en false. - Segundo, en la condición del ciclo usamos el valor de la variable que acabamos de definir: si su valor es verdadero, no debe volver a iterar.
- Tercero, en algún punto del ciclo verificamos si el problema ya ha sido resuelto (si ya se cumplió el objetivo). Si ése es el caso, cambiamos el valor de la variable a verdadero.
for( int i = 0; i < arreglo.length && !<condición>; i++ )
{
<cuerpo>
}
Este patrón de esqueleto es más simple que el anterior, pero sólo se debe usar si la expresión que indica que ya se cumplió el objetivo del ciclo es sencilla.
Cuando se aplica el patrón de recorrido parcial, el primer paso que se debe seguir es identificar la condición que indica que el problema ya fue resuelto. Con esa información se puede tomar la decisión de cuál esqueleto de algoritmo es mejor usar.
Ejemplo 6
Objetivo: Mostrar el uso del patrón de recorrido parcial para resolver un problema.
En este ejemplo se presentan tres soluciones posibles al problema de decidir si algún estudiante obtuvo cinco en la nota del curso.
public boolean hayAlguienConCinco( )
{
boolean termino = false;
for( int i = 0; i < notas.length && !termino; i++ )
{
if( notas[ i ] == 5.0 )
{
termino = true;
}
}
return termino;
}
- La condición para no seguir iterando es que se encuentre una nota igual a 5,0 en la posición
i
. - Al final del método, se retorna el valor de la variable "
termino
", que indica si el objetivo se cumplió. Esto funciona en este caso particular, porque dicha variable dice que en el arreglo se encontró una nota igual al valor buscado.
public boolean hayAlguienConCinco( )
{
int i = 0;
while( i < notas.length && notas[ i ] != 5.0 )
{
i++;
}
return i < notas.length;
}
- Esta es la segunda solución posible, y evita el uso de la variable "
termino
", pero tiene varias consecuencias sobre la instrucción iterativa. - En lugar de la instrucción
for
es más conveniente usar la instrucciónwhile
. - La condición de continuación en el ciclo es que la i-ésima nota sea diferente de 5,0.
- El método debe retornar verdadero si la variable
i
no llegó hasta el final del arreglo, porque esto querría decir que encontró en dicha posición una nota igual a cinco.
public boolean hayAlguienConCinco( )
{
for( int i = 0; i < notas.length; i++ )
{
if( notas[ i ] == 5.0 )
{
return true;
}
}
return false;
}
- Esta es la tercera solución posible. Si dentro del ciclo ya tenemos la respuesta del método, en lugar de utilizar la condición para salir del ciclo, la usamos para salir de todo el método.
- En la última instrucción retorna falso, porque si llega a ese punto quiere decir que no encontró ninguna nota con el valor buscado.
- Esta manera de salir de un ciclo, terminando la ejecución del método en el que éste se encuentra, se debe usar con algún cuidado, puesto que se puede producir código difícil de entender.
Hay muchas soluciones posibles para resolver un problema. Un patrón de algoritmo sólo es una guía que se debe adaptar al problema específico y al estilo preferido del programador.
Para el patrón de recorrido parcial aparecen las mismas dos variantes que para el patrón de recorrido total (ver ejemplo 7):
- En la primera variante se modifican los elementos del arreglo hasta que una condición se cumpla (por ejemplo, encontrar las tres primeras notas con 1,5 y asignarles 2,5). En ese caso, en el cuerpo del método va la modificación que hay que hacerle al elemento que se encuentra en el índice actual, pero se debe controlar que cuando haya llegado a la tercera modificación termine el ciclo.
- En la segunda variante, se deben tomar las mismas cuatro decisiones que se tomaban con el patrón de recorrido total, respecto de la manera de acumular la información para calcular la respuesta que está buscando el método.
Ejemplo 7
Objetivo: Mostrar el uso del patrón de recorrido parcial, en sus dos variantes.
En este ejemplo se presentan dos métodos de la clase Curso, en cada uno de los cuales se ilustra una de las variantes del patrón de recorrido parcial.
Encontrar las primeras tres notas iguales a 1,5 y asignarles 2,5:
public void subirNotas( )
{
int numNotas = 0;
for( int i = 0; i < notas.length && numNotas < 3; i++ )
{
if( notas[ i ] == 1,5 )
{
numNotas++;
notas[ i ] = 2,5;
}
}
}
Este método corresponde a la primera variante, porque hace una modificación de los elementos del arreglo hasta que una condición se cumpla. En el método del ejemplo, debemos contar el número de modificaciones que hacemos, para detenernos al llegar a la tercera.
Retornar la posición en la secuencia de la tercera nota con valor 5,0. Si dicha nota no aparece al menos 3 veces, el método debe retornar el valor –1:
public int darTercerCinco( )
{
int cuantosCincos = 0;
int posicion = -1;
for( int i = 0; i < notas.length && posicion == -1; i++ )
{
if( notas[ i ] == 5,0 )
{
cuantosCincos++;
if( cuantosCincos == 3 )
{
posicion = i;
}
}
}
return posicion;
}
- ¿Cómo acumular información? En este caso necesitamos dos variables para acumular la información: la primera para llevar el número de notas iguales a 5,0 que han aparecido (
cuantosCincos
), la segunda para indicar la posición de la tercera nota 5,0 (posicion
). - ¿Cómo inicializar el acumulado? La variable
cuantosCincos
debe comenzar en 0. La variableposicion
debe comenzar en menos 1. - ¿Condición para cambiar el acumulado? Si la nota actual es 5,0 debemos cambiar nuestro acumulado.
- ¿Cómo modificar el acumulado? Debe cambiar la variable
cuantosCincos
, incrementándose en 1. Si es el tercer 5,0 de la secuencia, la variableposicion
debe cambiar su valor, tomando el valor del índice actual.
Tarea 4
Objetivo: Generar habilidad en el uso del patrón de algoritmo de recorrido parcial.
Escriba los métodos de la clase Curso que resuelven los siguientes problemas, los cuales corresponden a las dos variantes del patrón de algoritmo de recorrido parcial.
Reemplazar todas las notas del curso por 0,0, hasta que aparezca la primera nota superior a 3,0.
public void cambiarNotasACero( )
{
}
Calcular el número mínimo de notas del curso necesarias para que la suma supere el valor 30, recorriéndolas desde la posición 0 en adelante. Si al sumar todas las notas no se llega a ese valor, el método debe retornar –1.
public int sumadasDanTreinta( )
{
}
5.4.3. Patrón de Doble Recorrido
El último de los patrones que vamos a ver en este capítulo es el de doble recorrido. Este patrón se utiliza como solución de aquellos problemas en los cuales, por cada elemento de la secuencia, se debe hacer un recorrido completo. Piense en el problema de encontrar la nota que aparece un mayor número de veces en el curso. La solución evidente es tomar la primera nota y hacer un recorrido completo del arreglo contando el número de veces que ésta vuelve a aparecer. Luego, haríamos lo mismo con los demás elementos del arreglo y escogeríamos al final aquélla que aparezca un mayor número de veces.
El esqueleto básico del algoritmo con el que se resuelven los problemas que siguen este patrón es el siguiente:
for( int indice1 = 0; indice1 < arreglo.length; indice1++ )
{
for( int indice2 = 0; indice2 < arreglo.length; indice2++ )
{
<cuerpo del ciclo interno>
}
<cuerpo del ciclo externo>
}
- El ciclo de afuera está controlado por la variable "
indice1
", mientras que el ciclo interno utiliza la variable "indice2
". - Dentro del cuerpo del ciclo interno se puede hacer referencia a la variable "
indice1
".
Las variantes y las decisiones son las mismas que identificamos en los patrones anteriores. La estrategia de solución consiste en considerar el problema como dos problemas independientes, y aplicar los patrones antes vistos, tal como se muestra en el ejemplo 8.
Ejemplo 8
Objetivo: Mostrar el uso del patrón de algoritmo de recorrido total con doble recorrido.
En este ejemplo se muestra el método de la clase Curso que retorna la nota que aparece un mayor número de veces. Para escribirlo procederemos por etapas, las cuales se describen en la parte derecha.
public double darNotaMasRecurrente( )
{
double notaMasRecurrente = 0.0;
for( int i = 0; i < notas.length; i++ )
{
for( int j = 0; j < notas.length; j++ )
{
//Por completar
}
}
return notaMasRecurrente ;
}
- Primera etapa: armar la estructura del método a partir del esqueleto del patrón.
- Utilizamos las variables
i
yj
para llevar los índices en cada uno de los ciclos. - Decidimos que el resultado lo vamos a dejar en una variable llamada
notaMasRecurrente
, la cual retornamos al final del método. - Una vez construida la base del método, identificamos los dos problemas que debemos resolver en su interior: (1) contar el número de veces que aparece en el arreglo el valor que está en la casilla i; (2) encontrar el mayor valor entre los que son calculados por el primer problema.
public double darNotaMasRecurrente( )
{
double notaMasRecurrente = 0.0;
for( int i = 0; i < notas.length; i++ )
{
double notaBuscada = notas[ i ];
int contador = 0;
for( int j = 0; j < notas.length; j++ )
{
if( notas[ j ] == notaBuscada )
{
contador++;
}
}
//Por completar
}
return notaMasRecurrente ;
}
- Segunda etapa: Resolvemos el primero de los problemas identificados, usando para eso el ciclo interno.
- Para facilitar el trabajo, vamos a dejar en la variable
notaBuscada
, la nota para la cual queremos contar el número de ocurrencias. Dicha variable la inicializamos con la nota de la casilla i. - Usamos una segunda variable llamada
contador
para acumular allí el número de veces que aparezca el valor buscado dentro del arreglo. Dicho valor será incrementado cuandonotaBuscada == notas[ j ]
. - Al final del ciclo, en la variable contador quedará el número de veces que el valor de la casilla i aparece en todo el arreglo.
public double darNotaMasRecurrente( )
{
double notaMasRecurrente = 0.0;
int cantidadOcurrencias= 0;
for( int i = 0; i < notas.length; i++ )
{
double notaBuscada = notas[ i ];
int contador = 0;
for( int j = 0; j < notas.length; j++ )
{
if( notas[ j ] == notaBuscada )
{
contador++;
}
}
if( contador > cantidadOcurrencias)
{
notaMasRecurrente = notaBuscada;
cantidadOcurrencias= contador;
}
}
return notaMasRecurrente ;
}
- Tercera etapa: Usamos el ciclo externo para encontrar la nota que más veces aparece.
- Usamos para eso dos variables:
notaMasRecurrente
que indica la nota que hasta el momento más veces aparece, ycantidadOcurrencias
para saber cuántas veces aparece dicha nota. - Luego definimos el caso en el cual debemos cambiar el acumulado: si encontramos un valor que aparezca más veces que el que teníamos hasta el momento (
contador > cantidadOcurrencias
) debemos actualizar los valores de nuestras variables.
En general, este patrón dice que para resolver un problema que implique un doble recorrido, primero debemos identificar los dos problemas que queremos resolver (uno con cada ciclo) y, luego, debemos tratar de resolverlos independientemente, usando los patrones de recorrido total o parcial.
Si para resolver un problema se necesita un tercer ciclo anidado, debemos escribir métodos separados que ayuden a resolver cada problema individualmente, tal como se plantea en el nivel 4, porque la solución directa es muy compleja y propensa a errores.
Tarea 5
Objetivo: Generar habilidad en el uso del patrón de algoritmo de doble recorrido.
Escriba el método de la clase Curso que resuelve el siguiente problema, que corresponde al patrón de algoritmo de doble recorrido.
Calcular una nota del curso (si hay varias que lo cumplan puede retornar cualquiera) tal que la mitad de las notas sean menores o iguales a ella.
public double notaMediana( )
{
}