Bucles y rangos
for-in sobre secuencias, los operadores de rango cerrado y semiabierto, los rangos parciales, stride para pasos arbitrarios, y las etiquetas de bucle con break y continue.
Swift no tiene el for de tres partes de C. Lo retiró del lenguaje en la versión 3 y en su lugar dejó una sola forma de iterar: for-in sobre cualquier cosa que sepa producir elementos. La decisión parece una amputación y es lo contrario: al eliminar el contador manual desaparece la familia entera de errores de frontera, y al hacer del rango un valor con tipo propio, los bucles se vuelven componibles con todo el resto de la biblioteca de colecciones.
- Iterar con
for-insobre secuencias y entender qué exige del tipo recorrido. - Distinguir el rango cerrado del semiabierto y saber cuándo usar cada uno.
- Producir progresiones con paso o dirección arbitraria mediante
stride. - Controlar bucles anidados con etiquetas,
breakycontinue.
for-in: recorrer elementos, no índices
El bucle de Swift pide elementos a una secuencia hasta que se agotan. No hay índice inicial que fijar, ni condición de parada que equivocar, ni incremento que olvidar:
let nombres = ["Ada", "Grace", "Alan"]
for nombre in nombres {
print(nombre)
}
for (indice, nombre) in nombres.enumerated() {
print("\(indice): \(nombre)")
}
for (clave, valor) in ["a": 1, "b": 2] { // sin orden garantizado
print("\(clave) vale \(valor)")
}
Si la variable de iteración no se usa, se sustituye por un guion bajo, y así el compilador no avisa de una constante sin utilizar. Cuando solo interesan algunos elementos, el propio for admite where como filtro, evitando el if de la primera línea del cuerpo:
for _ in 1...3 { print("repetido") }
for n in 1...20 where n % 3 == 0 {
print(n) // 3, 6, 9, 12, 15, 18
}
Lo que hace posible todo esto es el protocolo Sequence: cualquier tipo que sepa entregar un iterador puede recorrerse con for-in, y eso incluye arrays, diccionarios, conjuntos, cadenas, rangos y los tipos que definas tú. El bucle no conoce ninguna colección en particular; solo sabe pedir el siguiente elemento.
Rangos: cerrado, semiabierto y parciales
Un rango no es sintaxis del bucle, es un valor con tipo. 1...5 produce un ClosedRange que incluye ambos extremos; 0..<5 produce un Range que excluye el superior.
let cerrado = 1...5 // 1, 2, 3, 4, 5
let semiabierto = 0..<5 // 0, 1, 2, 3, 4
let letras = ["a", "b", "c", "d"]
for i in 0..<letras.count { print(letras[i]) } // nunca se pasa del final
El operador ..< existe precisamente porque los índices empiezan en cero: 0..<coleccion.count recorre exactamente los índices válidos, sin el - 1 que en tantos lenguajes se olvida o se pone de más. Y como los rangos son valores, se guardan en constantes, se pasan a funciones, se comparan con contains y sirven de patrón dentro de un switch:
func categoria(_ nota: Int) -> String {
switch nota {
case 0..<5: return "suspenso"
case 5..<7: return "aprobado"
case 7..<9: return "notable"
case 9...10: return "sobresaliente"
default: return "nota invalida"
}
}
También existen los rangos parciales, con un solo extremo. Sirven para cortar colecciones y para expresar condiciones abiertas, aunque no se pueden recorrer directamente porque no tienen principio o fin definido:
let numeros = [10, 20, 30, 40, 50]
print(numeros[2...]) // 30, 40, 50
print(numeros[..<2]) // 10, 20
print((...18).contains(edad))
5...1 no es un rango vacío: es un error en tiempo de ejecución que detiene el programa. Un rango exige que su límite inferior no supere al superior, y si los extremos vienen de variables conviene comprobarlo antes. Para recorrer hacia atrás no se invierten los extremos, se usa reversed o stride.
stride: el paso que los rangos no dan
Los rangos avanzan de uno en uno y siempre hacia delante. Para cualquier otra progresión está stride, que devuelve una secuencia con inicio, final y paso explícitos, en dos variantes que se corresponden con los dos tipos de rango:
for i in stride(from: 0, to: 10, by: 2) { print(i) } // 0,2,4,6,8
for i in stride(from: 0, through: 10, by: 2) { print(i) } // 0,2,4,6,8,10
for i in stride(from: 10, through: 0, by: -2){ print(i) } // 10,8,6,4,2,0
for x in stride(from: 0.0, to: 1.0, by: 0.25){ print(x) } // 0.0,0.25,0.5,0.75
for n in (1...5).reversed() { print(n) } // 5,4,3,2,1
to excluye el destino y through lo incluye: la misma distinción que separa ..< de ..., ahora expresada con nombres de argumento. Con paso negativo, stride es la forma correcta de contar hacia atrás.
flowchart TD
A[Que quiero recorrer] --> B{Es una coleccion}
B -->|Si| C[for elemento in coleccion]
B -->|No, son numeros| D{Paso de uno en uno}
D -->|Si y hacia delante| E{Incluyo el final}
E -->|Si| F[Rango cerrado con tres puntos]
E -->|No| G[Rango semiabierto]
D -->|No, otro paso o hacia atras| H[stride con from to o through y by]Etiquetas, break y continue
break abandona el bucle y continue salta a la siguiente vuelta, pero ambos afectan solo al bucle más interno. Para actuar sobre uno exterior se le pone una etiqueta delante y se nombra:
busqueda: for fila in 0..<alto {
for columna in 0..<ancho {
if tablero[fila][columna] == objetivo {
print("hallado en \(fila), \(columna)")
break busqueda // sale de los DOS bucles
}
if columna == saltar {
continue busqueda // pasa a la siguiente fila
}
}
}
Sin etiquetas esto exigiría una bandera booleana comprobada tras el bucle interno: más estado mutable y más superficie de error. Las etiquetas también funcionan sobre while, repeat-while, switch y bloques if, lo que permite salir de una estructura concreta cuando hay varias anidadas. En un switch dentro de un bucle son imprescindibles, porque ahí un break desnudo rompe el switch, no el bucle que lo rodea.
La retirada del for clásico en Swift 3 fue polémica y hoy se lee como una de las decisiones más lúcidas del lenguaje. Un for de tres partes es en realidad tres decisiones independientes -inicializar, comprobar, avanzar- que el programador debe mantener coherentes entre sí en cada bucle que escribe; los errores de frontera no son descuidos ocasionales, son la consecuencia estadística inevitable de repetir esa coordinación miles de veces. Al sustituirlo por for-in sobre Sequence, Swift no elimina esos grados de libertad, los factoriza: la lógica de avance deja de escribirse a mano en cada sitio y pasa a vivir dentro del tipo que se recorre, escrita una vez y verificada una vez. El rango sabe cómo avanzar, stride sabe cómo avanzar con paso arbitrario, reversed sabe cómo hacerlo al revés, enumerated sabe emparejar con índices, y todos ellos son valores de primera clase que se pasan y se componen. La consecuencia práctica es enorme: filtrar, transformar o tomar solo los primeros elementos deja de ser una modificación del bucle para convertirse en un cambio de la secuencia que le das, y el cuerpo del bucle permanece intacto. Ese es el patrón que conviene llevarse más allá de la sintaxis: cuando un fragmento de código exige coordinar varias piezas móviles a mano, la solución rara vez es tener más cuidado, y casi siempre es encontrar la abstracción que hace de esa coordinación un detalle interno de alguien que ya la resolvió.
- Recorre un array con
for-in, luego conenumeratedy luego con0..<array.count, y razona cuál expresa mejor la intención en cada caso. - Escribe con
stridelos múltiplos de siete menores que cien, y después la misma serie en orden descendente. - Usa un rango como patrón en un
switchque clasifique edades en cuatro tramos sin dejar huecos. - Filtra dentro del propio bucle con
for n in 1...50 whereen lugar de unifinterno, y compara la legibilidad. - Busca un valor en una matriz con dos bucles anidados y sal de ambos con una etiqueta; luego reescríbelo con una bandera booleana y cuenta las líneas de estado que has necesitado.