BUCLE FOR/IN

MONTEVERDE DE SANTA ÚRSULA, NORTE DE TENERIFE

      Acabamos de aprender que la sentencia while nos permite iterar elementos, esto es, evaluar una condición concreta por cada elemento o ítem de una secuencia dada y que, además, puede hacerlo de manera dinámica, del resultado de una ejecución al siguiente, y luego al siguiente, y luego al siguiente, y luego... mientras la condición se evalúe como True, sin que el intérprete de Python conozca a priori los resultados. Pues bien, esta cualidad de iterar sobre los elementos de una lista dada es también definitoria del bucle for/in. Podemos comprobarlo analizando el siguiente esquema:




El  bucle for/in, a estas alturas, ya es un viejo conocido nuestro, una poderosa herramienta de control de flujo de datos a la que ya hemos recurrido en algunas ocasiones, en nuestros ejemplos, y cuyo mecanismo y sintaxis no nos resultarán del todo desconocidos.
Aún así, repasemos de nuevo su estructura sintáctica:

                                                                           FOR EXPRESIÓN IN ITERABLE (lista, tupla,                                                                                                                                                  string):
                                                                                      BLOQUE DE CÓDIGO DE FOR/IN
                                                                            ELSE (opcional):
                                                                                      BLOQUE DE CÓDIGO DE ELSE

La sentencia  for/in tiene como cometido aplicar una o más sentencias inscritas en su correspondiente bloque de código, sobre cada uno de los ítems o elementos constitutivos de la secuencia, respetando el orden en que se han escrito éstos. En el caso de los diccionarios (y conjuntos), el resultado se mostrará desordenado y con el dato (key) o clave oportuno, pero no con su valor asignado (value):



Veamos a continuación un ejemplo de funcionamiento típico del bucle:



Veamos a continuación diferentes maneras de mostrar los resultados obtenidos en pantalla explorando las posibilidades de la función print():



Como ya sabemos, las herramientas de control de flujo, esto es, if/elif/else, while y for/in, son sentencias que se ejecutan dinámicamente, es decir, elemento por elemento de la secuencia de datos dada hasta cumplirse la condición requerida. Pues bien, en el caso de que por una razón u otra necesitemos modificar la secuencia sobre la que estamos iterando aún estando dentro del proceso, por si acaso, conviene hacer una copia de la misma. Esta acción no se ejecuta de manera automática o de forma implícita cuando se está llevando a cabo la iteración. ¿Cómo podemos hacerlo? Pues echando mano de las rebanadas (slices). Estudiemos el siguiente ejemplo:



Tenemos una secuencia de datos, en este caso, un tipo de dato lista (list) asignado a un nombre de identificador de espacio en memoria (variable) que llamamos a. A continuación, en 1., introducimos un bucle for/in con el añadido de una slice vacía que, como ya sabemos, refiere a todos los elementos de a. Lo confirmamos con un simple testeo:



Consecuentemente, salvo para lo que queremos conseguir, la sintaxis for x in a[:]: sería equivalente a for x in a.
Establecemos a continuación en 2. el bloque for/in (si la longitud de cada elemento de a es menor o igual a 7), y en 3. proponemos la declaración de la sentencia if, en este caso, un método de las listas, el método list.insert(index, item). Para nuestro ejemplo, en la posición de índice 0, colocaremos los ítems (x) que cumplan la declaración propuesta en el bloque for/in.
Cuando pedimos a obtenemos como resultado una lista, la nueva lista a, donde se inserta en la posición 0 (índice 0) los dos únicos ítems que cumplen con los criterios de la declaración del bloque for/in, esto es, Navarra y Galicia y, a continuación, una copia de la lista original: Galicia, Asturias, Cantabria, País Vasco, Navarra. Llamando a la slice adecuada, en nuestro ejemplo,  a[2:7], podríamos recuperar la lista original.



Veamos a continuación un ejemplo sencillo de uso de cláusula else en un bucle for/in:



VISTA PARCIAL DE ICOD DE LOS VINOS DESDE LA PROXIMIDAD AL PARQUE DE LOS DRAGOS, NORTE DE TENERIFE.

Volviendo a echar un vistazo a la sintaxis, la expresión puede estar conformada por una sola variable o por varias. En el caso de que se utilice una tupla o una lista como expresión se producirá automáticamente un desempaquetado (v. LISTAS II) de los elementos.








      CON UNA SENTENCIA continue ENCAJADA EN UN BLOQUE for/in, SE DEVUELVE DE MANERA INMEDIATA EL CONTROL DEL FLUJO AL INICIO DEL BUCLE, DANDO PASO A LA ITERACIÓN SIGUIENTE, COMO SI NADA HUBIERA PASADO. CON LA SENTENCIA break, EN CAMBIO, SE DETIENE LA EJECUCIÓN TRAS LA PRIMERA ITERACIÓN. PROBEMOS:




Existe en Python una función que empata divinamente con nuestro bucle for/in y que ya hemos visto someramente en alguna ocasión anterior: la función range().
La función range(), "rango" o "margen" traducido del inglés, nos permite acotar un espacio sobre el que actuará el bucle: a lo mejor, dada una secuencia, en lugar de iterar por todos los elementos constitutivos de la misma sólo nos interesa hacerlo sobre unos cuantos. Para hacerlo tan sólo tenemos que delimitar desde dónde y hasta dónde queremos que actúe el bucle. ¿Y cómo lo hacemos? Pues a través de la función integrada range() construida expresamente para cumplir con ese cometido. Veámoslo con un ejemplo:






      FÁCIL, ¿VERDAD? PERO...¡AY! TIENE UN DEFECTILLO: 
SÓLO FUNCIONA CON NÚMEROS. MIRA ABAJO LO QUE SUCEDE CUANDO PASAMOS UNA SECUENCIA DE TIPO LISTA CON STRINGS:










CAIDEROS DE AGUA EN LA LAURISILVA DE LAS CARBONERAS, MACIZO DE ANAGA, NORESTE DE TENERIFE.

La función range(), asociada sin referencia a secuencia alguna en un bucle for/in, genera una progresión aritmética  donde el valor final, que podemos llamar, por ejemplo,  x, siempre será x-1, partiendo siempre de 0. Para este supuesto, la función range() soporta un único argumento:



Con dos argumentos, establecemos una progresión con un punto de partida concreto, específico, siendo el segundo argumento el final de la progresión:



Pero añadiendo aún un tercer argumento podremos determinar cómo que se produzca esa progresión: de dos en dos, de tres en tres, de cinco en cinco, de n en n.



Los valores predefinidos de range() son start que se corresponde con el valor 0 (index = 0) y step, que es igual a 1, en el caso de que sólo pasemos un argumento, éste lo interpretará como stop, mientras que si le pasamos dos argumentos, el primero será start y el segundo stop que, como índices que son, puedan ser tanto positivos como negativos:





consultar la entrada: LA FUNCIÓN RANGE(). HASTA AQUÍ HEMOS LLEGADO.

Como hemos podido dilucidar hasta ahora, la sentencia for/in asociada a una función range() nos proporciona una iteración de números, bien fuera aplicándolos a una secuencia previa de datos (lista, tupla,...), o bien a partir sólo de las propiedades de la función range(). Sobre esta iteración podemos establecer un límite e implementar una bifurcación a través de una sentencia if/elif/else o while (de aquí, recordémoslo, les viene aquéllo de "control de flujo"). Comprobémoslo en un ejemplo donde generamos una lista de números contados de 5 en 5 hasta alcanzar el valor 50, donde se detiene la iteración.




MANTO DE PIÑAS EN EL PINAR DE
LA ESPERANZA, CENTRO DE
TENERIFE


Podemos obtener un resultado similar al anterior adosando una slice (rebanada) inmediatamente al lado de la función range(), mediante la sintaxis:

                                                   for x in range(a, b, c)[:n]:

donde a es el valor de inicio, b el valor final y c es el modo de conteo. En el caso de la slice, el índice de inicio se deja en blanco para que coincida con a (si quisiéramos que no fuera así, podemos incluir el valor que estipuláramos como índice de inicio dentro del rango de resultados posibles, pues de lo contrario el intérprete de Python lanzaría una excepción), mientras que en el caso del índice final podemos poner un número n que acote la iteración. Si no introducimos ninguno, coincidirá con c. Una rebanada completamente en blanco devolverá un resultado similar al que arrojaría la ejecución del bucle for/in sin la existencia de tal rebanada. A continuación mostramos un par de ejemplos:







¿A que mola mogollón? ¿Eh?
Por supuesto, todo esto funciona igual con números negativos.



Y ya que le hemos cogido el gustillo a los índices vamos a seguir practicando un poco con ellos. Veamos a continuación un ejemplo de cómo podemos iterar sobre los índices de una secuencia dada recurriendo para ello a las funciones range() y len():



1. En este caso partimos de una lista de compositores de música clásica encuadrados en el romanticismo alemán del siglo XIX. A continuación, en 2., implementamos el bucle for/in con una función range() asociada a la que, en este caso, pasamos como parámetro la función len(), con el parámetro a, a su vez, que es el identificador de la variable que guarda nuestra lista en memoria. Como tenemos siete elementos en la lista, su valor será de 7. Finalmente, en 3. definimos la declaración, con una llamada a la función print() que nos mostrará en pantalla, y por este orden (veremos que no coincide con el orden de la secuencia de parámetros que hemos pasado a la función), su índice, el ítem de la lista al que indexa y la string asociada correspondiente. También hemos hemos reproducido el ejemplo con el 7 (= len(a)) como argumento único de range() con un resultado similar.
Abundando más en las excelencias de la función range() encontramos que podemos construir secuencias de una manera rápida y eficaz con sólo pasarle los argumentos que deseemos:



¡Qué maravilla!¡Cuánto trabajo ahorrado no teniendo que escribir ítem por ítem sino dejar que el propio Python lo haga por nosotros! Si es que...
Sin embargo, como nada es perfecto, debemos aclarar que la función range() no admite decimales (float).
Lo cierto es que la sentencia, bucle, loop, o herramienta de control de flujo for/in, que a todas estas categorías responde y la podemos encontrar en distintos cursos, manuales y tutoriales, comprende un buen número de posibilidades programáticas. Esto es así porque al contrario de lo que sucede con los condicionales if/elif/else y while, el bucle for/in no está supeditado a una bifurcación per sé, a una resolución booleana True o False. Si, por ejemplo, implementamos un condicional if de modo que nos pasen listas de números enteros y como expresión utilizamos la cadena "números enteros", y estableciéramos aún una cláusula else con la cadena "contiene decimales" y le pasamos una lista a con los cinco primeros números naturales, esto es, a = [1,2,3,4,5], nuestro if dirá que son "números enteros" dado que la condición evaluada deviene True, y adiós, muy buenas. En el caso de una nueva lista b que incluya los cinco primeros números naturales más 7.63, esto es, b = [1,2,3,4,5, 7.63], por ejemplo, la condición evaluada deviene False y, en consecuencia, se dispara la cláusula else: "contiene decimales". Como el bucle for/in itera sin sujeción a condición alguna,, sin depender de una evaluación booleana True/False, si le añadimos una cláusula else la leerá una vez cumpla su cometido. Nos puede servir de ayuda pensar que en el caso de los condicionales, la cláusula else se puede interpretar como "en caso de que no sea así hacemos esto, lo que hemos codificado en el bloque else", mientras que en relación con el bucle for/in, se puede interpretar "cuando acabe de iterar hacemos esto también, lo que hemos codificado en el bloque else". Por esta razón, si necesitamos establecer un condicional sobre cada uno de los elementos de una colección de datos, y no sobre la colección en sí misma debemos codificar las expresiones correspondientes indentados, es decir, dentro del bloque de código que pertenece al bucle for/in ese condicional que acabamos de mencionar, con su cláusula else como respuesta al análisis booleano. Podemos, por ejemplo, concatenar strings tal y como mostramos a continuación:



Podemos establecer una iteración simple, aplicarle una declaración y aún consignar una cláusula else, que en el caso del bucle for/in, se ejecutará siempre una vez concluida la iteración.



VISTA DEL BARRANCO DE NATEROS, MASCA, MACIZO DE TENO, NOROESTE DE TENERIFE

Procedamos a crear ahora un programa algo más complejo que nos permitirá determinar qué números son primos y cuáles no de una serie dada. El resultado lo mostramos a continuación:



En 1. establecemos mediante un bucle for/in la secuencia de números sobre la que vamos a actuar, en nuestro ejemplo, del 1 al 7. (el 7 no incluido) apoyándonos en la función range(), lo que nos faculta para proceder sobre cada uno de los ítems que integran la secuencia. En 2. creamos un nuevo bucle for/in con una nueva selección de números en su respectiva función range(). ¿Qué números? Pues el 2, cuya inclusión es necesaria para identificar a los números pares (aquéllos cuya división entre 2 arroja un resto igual a cero, %=0), más todos los números incluidos en 1., esto es, 1, 2, 3, 4, 5, 6, y 7. En 3. ya incluimos el condicional if que procederá a la bifurcación en cada caso (o es primo o no lo es) y cuya condición declaramos mediante la expresión x%y == 0, es decir, que el resto de la división entre cada ítem de la serie 1-7 entre cada ítem a su vez de la serie 2-x devuelva 0 como resto. En 4. hacemos un break para el instante en que la condición se cumpla (notemos que break está dentro del bloque de código de if y no fuera de él) y, finalmente, en 5. establecemos una cláusula else dentro del bloque del segundo bucle for/in para apuntar a aquellos números que no cumplen con la condición propuesta en if y que, consiguientemente, son primos.


      EXISTE UNA MANERA DE CODIFICAR BUCLES for/in ANIDADOS EN UNA ÚNICA LÍNEA. DE  MODO QUE EL RESULTADO SEAN TUPLAS CON TANTOS ELEMENTOS COMO ITERABLES USEMOS (O TANTOS COMO NIVELES DE BUCLE INSTAUREMOS... TANTO MONTA, MONTA TANTO). ASÍ, OBTENDREMOS UNA COMBINACIÓN ENTRE LOS ELEMENTOS DE LOS ITERABLES. NO ES UNA FÓRMULA MUY USUAL NI TAMPOCO RESULTA INTUITIVA A LA HORA DE VERLA, PERO FUNCIONAR, FUNCIONA, ES SIMPLE Y REDUCE CÓDIGO.


El código molón de arriba equivale al siguiente:


Podemos usar tantos iterables como estimemos oportuno siempre que conservemos la lógica de la sintaxis. Además, los iterables pueden tener cualquier longitud.


Veamos finalmente un pequeño esquema para ayudarnos a entender mejor el proceso.




En fin, ha llegado el momento de los ejercicios. Veamos qué tal se nos dan estos:


T4. BLOQUE DE EJERCICIOS 9. SOLUCIONES.


MAR DE NUBES EN EL ENTORNO DE IGUESTE DE CANDELARIA, SURESTE DE TENERIFE.


No hay comentarios:

Publicar un comentario