LISTAS II

PUESTA DE SOL DESDE LA PLAYA DE LA ARENA, OESTE DE TENERIFE.

      Una vez vistos los métodos de las listas, pasamos a estudiar otros aspectos propios del objeto que nos ocupa. Por ejemplo, la forma de obtener dos o más elementos o ítems de una lista. A parte  de las slices contamos con otra fórmula alternativa: el DESEMPAQUETADO DE SECUENCIAS. Este método, por así decirlo, es aplicable a toda colección o seriación de elementos que cumplan con las características de las secuencias, básicamente, su condición de iterables, recordemos una vez más, su cualidad de ser recorridas por un bucle for/in de principio a fin por cada uno de sus elementos, en orden de izquierda a derecha.
Para conseguirlo debemos recurrir a un operador (podemos imaginar que un operador es como una microfunción representada por un signo exclusivo que permite hacer una cosa determinada con uno o más objetos. Por ejemplo, el operador aritmético + permite sumar dos datos numéricos o concatenar dos strings) que interpolamos como operador de desempaquetado de secuencias y que se representa por un asterisco.
Cuando lo empleamos con dos o más variables del lado izquierdo de una asignación, una de las cuales va precedida por nuestro flamante operador de desempaquetado de secuencias, *, se asignarán los elementos a las variables, mientras que todos los elementos o ítems que queden se asignarán a su vez a la afortunada variable poseedora del mencionado operador.


Fijémonos cómo realizan la misma función y, en consecuencia devuelven un resultado idéntico, tanto el recurso al operador de desempaquetado de secuencia como la rebanada, como comprobamos en 1. y 2.
Valga la redundancia, a la expresión a la que se le aplica el operador * de desempaquetado de secuencia, se la denomina expresión con asterisco. ¿A quién se le ocurriría?
Por otro lado, y aprovechando que ya estamos metidos en faena, vamos a mencionar aquí el concepto de argumentos con asterisco, aunque ello nos va a "obligar" a mostrar un nuevo elemento de Python que no vamos a ver aún (tardaremos un poco para eso, pues es necesario conocer algunos objetos más antes de llegar a él), que es la función predefinida, las funciones def, y que, puedo prometer y prometo, estudiaremos con la debida profundidad en su momento:


¿Cómo distinguimos si el signo * es en realidad un operador de multiplicación o un operador de desempaquetado? Muy sencillo: si aparece a la izquierda de una variable, siempre será un operador de desempaquetado de secuencia; y si aparece a la derecha, normalmente (no siempre, pero casi), será un operador de multiplicación.


PRÓLOGO A LAS LISTAS POR COMPRENSIÓN:

  

  ¿Asociaciones ideales? Pues se me ocurren el guante y la mano; una pelota de tenis y su raqueta; el sol y la playa; un bolígrafo, una hoja en blanco y una buena historia que contar; una camita mullida para perros y mi lomo blanco y peludo;...
Una asociación ideal la forman también la función range(), el bucle for/in y las listas.
Antes de proseguir, una pequeña advertencia: sobre la función range() aprenderemos ahora algunas cosas, pero la desarrollaremos un poco más en la entrada que propondremos al final de este artículo. Sobre el bucle (loop) hablaremos por extenso en su apartado correspondiente. Sobre las listas... seguimos en ello.
Por cierto, contaremos además con una entrada nueva dedicada a la función del().


LISTAS POR COMPRENSIÓN.



De la misma manera que en Python, como ya sabemos, disponemos de la posibilidad de construir conjuntos por comprensión, disponemos también de esa misma posibilidad para el caso de las listas.
Igual que sucedía en los conjuntos debemos recurrir a una sintaxis particular que requiere de una expresión y de un bucle for/in, a la que puede seguir o no una condición.
Al igual que sucede con los conjuntos, la comprensión de listas busca facilitar la codificación de listas que se presuponen largas, con muchos elementos o, lo que es lo mismo, con un valor de la función len() alto o muy alto. Y decimos "presuponen" porque muchas veces desconoceremos el número de ítems y/o los valores exactos de estos.

ALTOS DE ADEJE, SUROESTE DE TENERIFE.


Veamos este ejemplo tomado de Python 3, de Mark Summerfield, ed. Anaya Multimedia. donde construimos una lista con los años bisiestos que se extienden desde 1900 a 1940. Podemos, para resolver la cuestión, elaborar el siguiente módulo:


En esta ocasión, nos hemos permitido explicar sucíntamente el proceso recurriendo a los comentarios de Python, lo que poco menos que ha conseguido convertir nuestro ejemplo en un salpicón de pimientos. En lo que respecto al programa en sí, sencillito, ¿verdad?
Explicitamos a continuación la sintaxis más fácil para construir una lista por comprensión:
                                                                                    
                                                                                      [elemento for elemento in range()]

Sin embargo, sobre esta estructura básica podemos hacer dos modificaciones que aumentan su potencial para codificar.
      A) Cambiamos el primer elemento por una expresión:
                                                                                 
                                                                                       [expresión for elemento in range()]
 
   B) Podemos añadir una condición implementando if al final del constructo sintáctico para asegurarnos que los elementos que se devuelvan en el resultado cumplan una determinada condición.

                                                                                   [expresión for elemento in range() if condición]

Este último tipo es el que se corresponde con el que utilizamos en el modelo anterior:
                                                                                 
                                                                                    lista =[]
                                                                                    for element in range:
                                                                                          if condición:
                                                                                                    lista.append(expresión)

En el caso de que optemos por utilizar una lista por comprensión, no es necesario declarar una lista vacía primero para que los resultados se muestren en una lista: fijémonos en que la sintaxis de una lista por comprensión se muestra ya entre corchetes, por lo que necesariamente los resultados se nos devolverán igualmente entre corchetes, como corresponde al tipo de dato list.
Aplicamos, pues, el formato de lista por comprensión al ejemplo precedente:


Donde 1. y 2. devuelven resultados equivalentes, demostrando que una lista por comprensión formulada con una sintaxis básica supone lo mismo que codificar una lista simple.



¿A qué resulta sencillo, verdad?
Podemos construir de la misma manera una versión completa de nuestro programa de ejemplo:


Que es la conjugación de todo cuanto hemos visto hasta ahora.
No olvidemos que la razón fundamental de la existencia de las listas por comprensión es la de reducir y simplificar código, uno de los mantras más repetidos en la filosofía de Python. En nuestro ejemplo reducimos algo de bloque, pero en programas mayores, con listas anidadas, por ejemplo, podremos ver resultados más obvios: simplemente imaginemos aplicar una lista por comprensión a cada una de las listas anidadas. ¡Ah!¿Que nos cuesta creerlo? Pues aquí va un ejemplo: imaginemos (otra vez) que queremos elaborar una lista por encargo de una licorería con todas las identificaciones posibles para un conjunto de bebidas: con alcohol, 'c' y sin alcohol, 's'; frías, 'f' y a temperatura ambiente, 'm'; envase en lata, 'l'; tetrabrick, 'b' y cristal, 'r'. Para hacerlo debemos recurrir a tres bucles for/in encadenados, uno por cada condición: 1º ···> c y s; 2º ···> f y m; 3º ···> l, b y r. Veámoslo:



De esta manera establecemos una permutación con todos los elementos de las tres strings (alcohol, temperatura y envase).
Todo esto es reducible por comprensión a lo siguiente:


A que mola, ¿eh?
En el caso de que la lista que se ha construido fuera demasiado grande, podemos optar por generar cada elemento de la misma conforme lo vayamos necesitando y no hacerlo todo de una vez. Para ello recurrimos a un generator en lugar de una lista por comprensión. Pero esta es otra historia que veremos en Python  más adelante en una página en este mismo blog y que, por ahora, no nos interesa.

VOLCÁN DE CHINYERO, ARTÍFICE DE LA ÚLTIMA ERUPCIÓN HISTÓRICA EN LA ISLA DE TENERIFE HACIA 1909, CON SU MANTO DE PICÓN Y BOSQUECILLO RALO DE PINAR COMENZANDO A COLONIZAR LOS ALREDEDORES, EN EL NOROESTE DE TENERIFE.

En resumidas cuentas:


Y nuevo ejemplo al canto:


LISTAS ANIDADAS.


Ya las hemos mencionado varias veces en esta sección dedicada al tipo de objeto list de Python.
Incluso hasta cierto punto, si nos paramos a reflexionar sobre ello, ya las hemos visto como índices (podemos considerar un indexado como una sublista con un único elemento) y como rebanadas. Ahondaremos un poco más en ellas. Una lista anidada o multinivel es aquélla que contiene a su vez cualquier otro iterable, como otras listas, tuplas, etc. aunque, comúnmente, nos referimos a listas dentro de otras listas.


Es posible acceder a los elementos constitutivos de las listas a través de la jerarquía de índices, tanto más complejos cuantos más niveles de profundidad tengamos:


SLICING PARTIENDO DEL LADO IZQUIERDO.


Esta técnica entronca directamente con el tipo de dato list de Python, con las listas, por su enorme capacidad para ser modificada sobre la marcha, a parte de con los métodos propios para el caso o el recurso a operadores, por medio de las slices, nuestras entrañables rebanadas que ya conocemos de apartados anteriores. Tengamos presente que el troceado de una secuencia genera un nuevo objeto de Python sin que ello afecte a la estructura original de la secuencia, que continúa siendo la misma. Esta técnica viene como anillo al dedo cada vez que queramos modificar una lista cualquiera en una sola expresión. Vamos a ello:




Podemos deducir fácilmente que si queremos cambiar una lista por una rebanada extraída de ésta, pongamos por caso, por un proceso de depuración, tan sólo tenemos que reasignar el nombre de nuestra lista original:
                                                                                           
                                                                                              >>> lista = lista[x : y : [z]]
                                                                                              >>> lista
                                                                                              [x : y : [z]]

Dicho esto, pasamos a estudiar con mayor profundidad el troceado desde el lado izquierdo. Veamos el siguiente caso:



Hemos visto que la lista original y modificada conservan un mismo identificador de espacio de memoria, su id, donde la longitud (recordemos que la longitud de una secuencia la obtenemos a través de la función len()), tanto de la slice como de la lista que le asignamos es la misma.

  • Evidentemente, se pueden dar tres posibilidades más: que la longitud de la lista asignada a la slice fuera mayor, que fuera menor o que fuera una lista vacía. Cuando la lista asignada a la rebanada es mayor, los elementos se incorporan sin ningún problema a la lista original y en la posición propuesta en la rebanada:



  • Cuando la lista asignada a la rebanada es menor, se suprimen los objetos sobrantes y sólo quedan los que formen parte de la mencionada lista:



  • Cuando la lista asignada a la rebanada es una lista vacía tendremos la supresión total de los elementos de la lista original que engloben los índices de inicio y final de la rebanada. Podría decirse que se trata de una suerte de de método list.pop(slice) en lugar de list.pop(índice):



LADERAS NEVADAS BAJO EL PINAR DE LA FORTALEZA, VALLE DE LA OROTAVA, CENTRO NORTE DE TENERIFE.

REBANADAS DE LONGITUD 0 (LEN(REBANADA) = 0.


Lógicamente, una rebanada de longitud 0, es decir, aquélla donde el índice de inicio y el índice final son coincidentes, devuelve una lista vacía, en tanto que el índice final, siendo el mismo que el índice de inicio, recordemos, no se cuenta por lo que lista[3:3] = []. De manera similar, obtenemos el mismo resultado cuando el índice final es menor que el índice de inicio. Veámoslo:



Si aplicáramos a una rebanada con len(rebanada) = 0 una lista, dado que no se sustituye nada sino que se adiciona, el resultado no sería una lista vacía sino una nueva lista con sus elementos incorporados en la posición que apuntan ambos índices, o a partir de ella si adicionamos una lista con más de un elemento, lo que redunda en la similitud aparente entre esta forma de trocear y el método list.insert(índicer, item). lo ilustramos con un ejemplo:



      ESTA TÉCNICA DE TROCEADO, AL CONTRARIO QUE EL MÉTODO LIST.INSERT(ÍNDICE, ÍTEM) NOS PERMITE AÑADIR MÁS DE UN ELEMENTO EN UNA SOLO EXPRESIÓN.




Si quisiéramos extender por la derecha desde nuestro troceado por el lado izquierdo, debemos incluir como índice de inicio el valor de len(lista) -1, por lo que los elementos de la lista asignada se insertarán a partir del último elemento de la lista original. Con un único elemento equivaldría al método list.append(ítem), mientras que con más de un elemento equivale a una concatenación de listas con el operador + o la aplicación del método list.extend(lista).



De hecho, una rebanada con el índice de inicio similar a la longitud de la lista original devuelve siempre una lista vacía, independientemente de si le proporcionamos o no un índice final cualquiera que fuera el valor del mismo. Sin embargo, si a este troceado le asignamos una lista cualquiera la añadirá al final de la lista original tal cual hemos visto en la captura anterior.


El índice de inicio puede ser cualquier número entero, da igual, dado que el resultado será el mismo.


Recomendamos la lectura de las siguientes entradas que nos ayudarán a completar nuestro conocimiento sobre las listas de Python, y prepararnos bien para comprender los apartados que siguen. Venga, hagamos el esfuerzo.

T1. LA FUNCIÓN RANGE(): HASTA AQUÍ HEMOS LLEGADO.
T2. LA SENTENCIA DEL: FUE BONITO MIENTRAS DURÓ.

 Practiquemos lo aprendido resolviendo los siguientes ejercicios:


T4. BLOQUE DE EJERCICIOS 4. SOLUCIONES.


ÑAMERAS EN LOS AGUAJES DEL BARRANCO DE EL BATÁN, ANAGA, NORESTE DE TENERIFE.


No hay comentarios:

Publicar un comentario