Desplázate para cambiar de página
Computer Science A · AP Computer Science A
if, while y for
Un programa ejecuta sus instrucciones una tras otra, en orden. Dos cosas cambian ese orden: la selección elige qué instrucciones se ejecutan, y la iteración vuelve a ejecutar instrucciones.
El programa de la figura empieza con n = 5. Mientras n no sea 1, divide n a la mitad si es par, o lo triplica y le suma 1 si es impar, y cuenta un paso. El rombo de arriba es la pregunta del ciclo; el de abajo es la del if. La ficha roja es el punto de ejecución, y el código a su lado ilumina la línea que se ejecuta.
int n = 5; int steps = 0; while (n != 1) { if (n % 2 == 0) { n = n / 2; } else { n = 3 * n + 1; } steps++; } System.out.println(steps); n va 5, 16, 8, 4, 2, 1. La condición del ciclo se revisa 6 veces: verdadera 5 veces y luego falsa una vez, y el programa imprime 5.
Este capítulo es la Unidad 2 de AP Computer Science A, el 25–35% de la calificación de opción múltiple: expresiones booleanas e instrucciones if (2.1–2.6), ciclos while y for (2.7–2.8), los algoritmos estándar construidos con ellos (2.9–2.10), ciclos dentro de ciclos (2.11) y el conteo de cuántas veces se ejecuta una instrucción (2.12).
los tres bloques fundamentales
Todo algoritmo se construye con tres cosas: secuenciación (pasos en orden), selección (una decisión) y repetición (un ciclo).
La selección ocurre cuando el camino que sigue un algoritmo depende de una decisión verdadera o falsa. La repetición es un proceso que se repite hasta alcanzar el resultado deseado. Ambas aparecen en las instrucciones de todos los días: “si está lloviendo, lleva un paraguas” es selección; “revuelve hasta que el azúcar se disuelva” es repetición.
Los algoritmos se pueden escribir con palabras o dibujar como diagrama de flujo: una caja por cada paso, un rombo por cada decisión de verdadero o falso y una flecha que regresa hacia arriba para la repetición. La figura ahora escribe el mismo algoritmo con palabras: “¿n es par?”, “dividirlo a la mitad”, “contar un paso”. El código dice exactamente lo mismo en Java.
El orden de los pasos cambia el resultado. Estos dos segmentos tienen las mismas instrucciones:
int x = 3; x = x * 2; x = x + 1;
int x = 3; x = x + 1; x = x * 2;
El primero deja x en 7; el segundo, en 8. En la figura, contar un paso después del ciclo en lugar de dentro de él imprimiría 1, sin importar cuánto durara el ciclo.
operadores relacionales
Una expresión booleana se evalúa a uno de dos valores, true o false. Las más sencillas comparan dos valores con un operador relacional.
== y != comprueban si dos valores son iguales o distintos. <, >, <= y >= comparan números. Con tipos primitivos como int, double y boolean, == compara los valores reales. Con tipos de referencia compara las referencias: si dos variables se refieren al mismo objeto (2.6).
En la figura a guarda 7 y b toma los valores 4, 7, 9. Cada comparación produce un bloque, rojo si es verdadera:
| b = 4 | b = 7 | b = 9 | |
|---|---|---|---|
a == b | false | true | false |
a != b | true | false | true |
a < b | false | false | true |
a > b | true | false | false |
a <= b | false | true | true |
a >= b | true | true | false |
= asigna; == compara. Un resultado booleano se puede guardar como cualquier otro valor: boolean same = a == b;
selección de una vía y de dos vías
Las instrucciones de selección cambian la ejecución secuencial de las instrucciones. Una instrucción if ejecuta distintos segmentos de código según el valor de una expresión booleana.
Una selección de una vía (un if) tiene un segmento, que se ejecuta solo cuando la condición es verdadera:
if (temp >= 30) { System.out.println("hot"); } System.out.println("done"); Con temp en 34 imprime hot then done; en 22, solo done. La última línea está después del if, así que siempre se ejecuta.
Una selección de dos vías (un if-else) tiene dos segmentos: el cuerpo del if cuando la condición es verdadera y el cuerpo del else cuando es falsa. Se ejecuta exactamente uno de ellos.
if (temp >= 30) { System.out.println("hot"); } else { System.out.println("mild"); } System.out.println("done"); La figura envía dos temperaturas por él. En 34 la ficha toma el carril verdadero e imprime hot; en 22, el carril falso, mild. Después ambas imprimen done.
if, else if, else
Una selección múltiple (if-else-if) prueba una serie de condiciones en orden. La primera que es verdadera ejecuta su segmento, y no se ejecuta más de uno.
if (score >= 90) { grade = "A"; } else if (score >= 80) { grade = "B"; } else if (score >= 70) { grade = "C"; } else { grade = "F"; } La figura deja caer tres calificaciones por la cadena. 83 no cumple score >= 90, cumple score >= 80 y queda atrapada: calificación "B". Las condiciones de abajo nunca se revisan. 95 queda atrapada en la primera compuerta, "A". 64 no cumple ninguna de las tres, así que se ejecuta el else final: "F". Sin un else final, un valor que no cumple ninguna condición no ejecuta ningún segmento.
Como gana la primera condición verdadera, el orden importa. Si pruebas score >= 70 primero, 95 obtiene "C": toda calificación de 70 o más se detiene en la primera compuerta. Pon primero la prueba más exigente.
un if dentro de un if
Las instrucciones if anidadas son instrucciones if, if-else o if-else-if dentro de otra instrucción if, if-else o if-else-if.
if (age >= 16) { if (hasPermit) { System.out.println("may drive"); } else { System.out.println("needs a permit"); } } System.out.println("done"); La condición interna se evalúa solo si la externa es verdadera. La figura hace pasar a tres personas. La edad 17 con permiso pasa ambas e imprime may drive. La edad 17 sin permiso pasa la prueba externa y no cumple la interna: needs a permit. La edad 14 no cumple la prueba externa, así que la ficha rodea toda la caja: hasPermit ni siquiera se mira, y solo se imprime done.
Las llaves deciden a qué if pertenece un else. Aquí el else está dentro de las llaves externas, así que se empareja con if (hasPermit): alguien menor de 16 no recibe ningún mensaje sobre un permiso.
!, && y ||
Los operadores lógicos ! (no), && (y) y || (o) combinan expresiones booleanas en una que a su vez es verdadera o falsa.
!a es verdadera cuando a es falsa, y falsa en caso contrario. a && b es verdadera solo cuando ambas son verdaderas. a || b es verdadera cuando a es verdadera, b es verdadera, o ambas. La figura recorre todas las combinaciones, una fila a la vez:
| a | b | a && b | a || b | !a |
|---|---|---|---|---|
| true | true | true | true | false |
| true | false | false | true | false |
| false | true | false | true | true |
| false | false | false | false | true |
El orden de precedencia es ! primero, luego && y luego ||. Así, con a = false, b = true, c = false, la expresión !a || b && c significa (!a) || (b && c): true. Agruparla como (!a || b) && c da en cambio false. Los paréntesis aclaran el orden.
Los operadores relacionales van antes que los lógicos, así que x > 0 && x < 10 compara primero y combina después: es verdadera para todo x estrictamente entre 0 y 10.
puede que el lado derecho nunca se ejecute
Cuando el operando izquierdo de && o || ya decide el resultado, Java no evalúa el derecho. Esto es la evaluación de cortocircuito.
false && anything es falsa, y true || anything es verdadera, diga lo que diga el lado derecho. Así que el lado derecho se omite. La figura evalúa dos expresiones, primero con x = 4 y luego con x = 0:
x != 0 && 10 / x > 2 x == 0 || 10 / x > 2
Con x = 4 se ejecutan ambos lados de ambas. 10 / 4 es 2 (división entera), no más que 2, así que la primera es false y la segunda también.
Con x = 0, x != 0 es falsa, así que la primera es false de inmediato; y x == 0 es verdadera, así que la segunda es true de inmediato. En ninguna se ejecuta 10 / x, lo cual es una suerte: dividir un int entre cero lanza una ArithmeticException.
Eso convierte al operando izquierdo en una guarda. Escrita al revés, 10 / x > 2 && x != 0 divide primero y se cae cuando x es 0.
tablas de verdad y leyes de De Morgan
Dos expresiones booleanas son equivalentes si se evalúan al mismo valor en todos los casos. Una tabla de verdad, con una fila por cada combinación de entradas, lo puede demostrar.
Las leyes de De Morgan dan dos equivalencias:
!(a && b) es equivalente a !a || !b !(a || b) es equivalente a !a && !b
Para meter un ! dentro de paréntesis, niega cada parte e intercambia && por ||, o || por &&. En la figura cada columna de la segunda expresión se desliza sobre la primera: los colores coinciden en todas las filas.
| a | b | !(a && b) | !a || !b | !(a || b) | !a && !b |
|---|---|---|---|---|---|
| true | true | false | false | false | false |
| true | false | true | true | false | false |
| false | true | true | true | false | false |
| false | false | true | true | true | true |
Olvidar intercambiar el operador lo rompe: !(a && b) y !a && !b difieren en 2 de las 4 filas. Una comparación negada invierte el operador: !(x > 5) es x <= 5. Así que !(x >= 1 && x <= 10) es equivalente a x < 1 || x > 10.
== frente a equals
Dos variables distintas pueden guardar referencias al mismo objeto. == y != sobre referencias preguntan si se refieren al mismo objeto, no si dos objetos tienen el mismo contenido.
String s1 = new String("atlas"); String s2 = s1; String s3 = new String("atlas"); String s4 = null; s2 = s1 copia la referencia, así que s1 y s2 apuntan a un objeto: s1 == s2 es true. s3 se refiere a un segundo objeto con las mismas letras: s1 == s3 es false.
Las clases suelen definir un método equals que dice cuándo dos objetos se consideran iguales, normalmente comparando sus atributos. El de Cuerda compara los caracteres, así que s1.equals(s3) es true. Compara cadenas con equals, nunca con ==.
Una referencia se puede comparar con null usando == o !=, para saber si se refiere a algún objeto: s4 == null es true. (Sobrescribir el método equals queda fuera del alcance del curso.)
revisar, ejecutar, revisar de nuevo
La iteración es una forma de repetición: una instrucción de iteración repite un segmento de código cero o más veces, mientras su expresión booleana sea verdadera.
En un ciclo while la expresión booleana se evalúa antes de cada pasada por el cuerpo, incluida la primera. Mientras es verdadera se ejecuta el cuerpo; cuando es falsa el ciclo termina y el programa continúa después de él.
int sum = 0; int k = 1; while (k <= 4) { sum += k; k++; } System.out.println(sum); Una tabla de rastreo registra las variables cada vez que se revisa la condición. La figura la llena fila por fila:
| k | sum | k <= 4 |
|---|---|---|
| 1 | 0 | true |
| 2 | 1 | true |
| 3 | 3 | true |
| 4 | 6 | true |
| 5 | 10 | false |
La condición se revisa 5 veces y el cuerpo se ejecuta 4 veces: la última revisión es la que da falso. El ciclo termina con k = 5, y se imprime 10.
nunca, para siempre, uno de menos
La mayoría de los errores de ciclos son de tres tipos: el ciclo nunca se ejecuta, nunca se detiene, o se ejecuta una vez de más o una de menos.
Si la expresión booleana es falsa la primera vez, el cuerpo nunca se ejecuta. while (k < 5) con k en 10 revisa una vez y sigue adelante (fila de arriba).
Un ciclo infinito ocurre cuando la expresión booleana siempre se evalúa a verdadera. En la fila de en medio k va 1, 3, 5, 7, 9, … y nunca es 6, así que while (k != 6) nunca se vuelve falsa. Un ciclo cuyo cuerpo nunca cambia las variables de su condición hace lo mismo.
Un error por uno (off-by-one) es un ciclo que se ejecuta una vez de más o una de menos. Pensado para imprimir de 1 a 5, el ciclo de abajo imprime 1234: while (k < 5) se detiene antes de 5. k <= 5 lo corrige.
int k = 1; while (k < 5) { System.out.print(k); k++; } Revisa un ciclo en sus extremos: la primera pasada, la última pasada y la revisión que lo termina.
inicialización, expresión booleana, actualización
Un ciclo for pone en su encabezado las tres partes de un ciclo de conteo: la inicialización, la expresión booleana y la actualización.
for (int i = 0; i < 3; i++) { System.out.println(i); } La inicialización se ejecuta una vez, antes de la primera revisión. La variable que fija es la variable de control del ciclo. La expresión booleana se evalúa justo después de la inicialización, y de nuevo después de cada actualización, hasta que es falsa. En cada pasada la actualización se ejecuta después de todo el cuerpo y antes de la siguiente revisión.
La ficha de la figura sigue inicialización → revisión → cuerpo → actualización → revisión …. Los conteos bajo cada estación: inicialización 1, revisión 4, cuerpo 3, actualización 3. Imprime 0, 1, 2, cada uno en su propia línea.
Un ciclo for se puede reescribir como un ciclo while equivalente, y al revés:
int i = 0; while (i < 3) { System.out.println(i); i++; } Una diferencia: i declarada en el encabezado del for existe solo dentro del ciclo; declarada antes de un ciclo while, sigue existiendo después.
% 10, / 10 y % k
Dos algoritmos estándar usan el operador de residuo: descomponer un entero dígito por dígito y comprobar si un entero es divisible exactamente entre otro.
Para un int n positivo, n % 10 es su último dígito y n / 10 (división entera) es n sin ese dígito. Repite hasta que n sea 0:
| n | n % 10 | n / 10 |
|---|---|---|
| 3725 | 5 | 372 |
| 372 | 2 | 37 |
| 37 | 7 | 3 |
| 3 | 3 | 0 |
int n = 3725; int sum = 0; int max = 0; int odd = 0; while (n > 0) { int d = n % 10; sum += d; if (d > max) { max = d; } if (d % 2 == 1) { odd++; } n /= 10; } La figura separa los dígitos de 3725 de derecha a izquierda, 5, 2, 7, 3, y cada uno pasa por d.
a % b == 0 es verdadera exactamente cuando a es divisible entre b. 3725 % 5 es 0, así que 3725 es divisible entre 5; 3725 % 3 es 2, así que no es divisible entre 3. La misma prueba con 2 separa pares de impares, como hace d % 2 == 1 en el ciclo.
acumular mientras corre el ciclo
Tres algoritmos estándar más guardan un valor acumulado en una variable declarada antes del ciclo y actualizada dentro de él: una suma, un mínimo o máximo y un conteo.
Una suma empieza en 0 y agrega cada valor: sum += d. Para 3725, sum termina en 17.
Un promedio es la suma dividida entre cuántos valores hubo. Ojo con la división entera:
double avg = (double) sum / 4; int avgInt = sum / 4;
avg es 4.25; avgInt es 4, con la fracción descartada. Haz la conversión de tipo antes de dividir, no después.
Un máximo guarda el valor más grande hasta el momento y lo reemplaza cada vez que llega uno mayor: if (d > max). Aquí max termina en 7. Empieza con el primer valor, o con un valor por debajo del cual ningún valor real pueda estar: 0 funciona solo porque los dígitos nunca son negativos; en general usa Integer.MIN_VALUE. Un mínimo es la imagen especular: empieza en Integer.MAX_VALUE y prueba <; el dígito más pequeño es 2.
Un conteo de cuántas veces se cumple un criterio empieza en 0 y suma 1 dentro de un if: if (d % 2 == 1) cuenta los dígitos impares, 3 de ellos.
invertir y contar subcadenas
Los algoritmos con cadenas recorren un índice a lo largo de una cadena y toman una subcadena en cada posición: para construir una cadena nueva, o para probar o contar subcadenas que tengan cierta propiedad.
Para invertir una cadena, pon cada carácter delante de lo que ya se ha construido. substring(i, i + 1) es el único carácter en el índice i:
String word = "drawer"; String rev = ""; for (int i = 0; i < word.length(); i++) { rev = word.substring(i, i + 1) + rev; } rev crece "d", "rd", "ard", "ward", "eward", "reward". (rev = rev + word.substring(i, i + 1) copiaría la palabra sin cambios.)
Para contar subcadenas que cumplen un criterio, desliza una ventana a lo largo y prueba cada una:
String s = "banana"; int count = 0; for (int i = 0; i < s.length() - 1; i++) { if (s.substring(i, i + 2).equals("an")) { count++; } } La ventana substring(i, i + 2) mira "ba", "an", "na", "an", "na": 2 coinciden, así que count es 2. La última ventana empieza en el índice 4, así que i debe detenerse antes de s.length() - 1; un paso más, y substring(5, 7) se pasa del final y lanza una StringIndexOutOfBoundsException.
Compara subcadenas con equals. Para saber si alguna subcadena tiene la propiedad, el mismo ciclo puede detenerse en la primera que encuentre, o indexOf devuelve dónde aparece por primera vez una subcadena dada, −1 si no aparece en ningún lado.
un ciclo dentro de otro ciclo
La iteración anidada es un ciclo en el cuerpo de otro ciclo. El ciclo interno ejecuta todas sus iteraciones antes de que el ciclo externo pase a la siguiente.
for (int r = 0; r < 3; r++) { for (int c = 0; c < 4; c++) { System.out.print(r * c + " "); } System.out.println(); } Para r = 0, el ciclo interno recorre c de 0 a 3; luego println termina la línea, y solo entonces r pasa a valer 1. La figura ilumina la cuadrícula celda por celda, fila tras fila: el cuerpo interno se ejecuta 3 × 4 = 12 veces e imprime
0 0 0 0 0 1 2 3 0 2 4 6
Los límites del ciclo interno pueden depender de la variable externa. Aquí el ciclo interno se ejecuta r + 1 veces, así que las pasadas son 1, 2, 3, 4:
int count = 0; for (int r = 0; r < 4; r++) { for (int c = 0; c <= r; c++) { count++; } } count termina en 10, un triángulo en lugar de un rectángulo.
contar cuántas veces se ejecuta una instrucción
Un conteo de ejecuciones de una instrucción es el número de veces que esta se ejecuta. Por lo general se obtiene de manera informal, siguiendo unas cuantas pasadas y razonando sobre los ciclos.
for (int i = 0; i < n; i++) { count++; } Con i de 0 a n − 1, count++ se ejecuta n veces. La comprobación i < n se ejecuta n + 1 veces: una más, para terminar el ciclo.
for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; } } Anidado, cada una de las n pasadas externas ejecuta el ciclo interno n veces: count++ se ejecuta n × n veces. Para n = 6 eso es 6 contra 36; para n = 8, 8 contra 64. La figura crece en ambos casos al subir n: al duplicar n, uno se duplica y el otro se cuadruplica.
Cuenta a partir de los límites del ciclo. for (int i = 1; i <= n; i++) también se ejecuta n veces; i < n empezando en 1 se ejecuta n − 1; un triángulo como el de la última página se ejecuta 1 + 2 + … + n veces. Comparar estos conteos es la manera de decir que un ciclo es más rápido que otro sin cronómetro.