Apéndice S — Iterables, Mapeo, Filtros y Reducciones.
Objetivo. Revisar elementos avanzados de Python para crear códigos más concisos.
S.1 Iterables
En Python existen objetos que contienen secuencias de otros objetos.
Estos objetos se pueden recorrer usando ciclos for ... in ....
A estos objetos se les conoce también como iterables (objetos iterables, secuencias iterables, contenedores iterables, conjunto iterable, entre otros).
Ejemplo S.1:
Crear una cadena, una lista, una tupla, un diccionario, un conjunto y leer un archivo; posteriormente recorrer cada uno de estos iterables usando un ciclo for:
mi_cadena ="pythonico"mi_lista = ['p','y','t','h','o','n','i','c','o']mi_tupla = ('p','y','t','h','o','n','i','c','o')mi_dict = {'p':1,'y':2,'t':3,'h':4,'o':5,'n':6,'i':7,'c':8,'o':9}mi_conj = {'p','y','t','h','o','n','i','c','o'}mi_archivo =open("./gatos.txt")print('\nCadena:', end=' ')# Recorremos la cadena e imprimimos cada elemento for char in mi_cadena:print(char, end=' ')print('\nLista:', end=' ')# Recorremos la lista e imprimimos cada elemento for element in mi_lista:print(element, end=' ')print("\nTupla: ", end='')# Recorremos la tupla e imprimimos cada elemento for element in mi_tupla:print(element, end=' ')print("\nDiccionario (claves): ", end='') # Recorremos el diccionario e imprimimos cada clave for key in mi_dict.keys():print(key, end=' ')print("\nDiccionario (valores): ", end='') # Recorremos el diccionario e imprimimos cada valor for key in mi_dict.values():print(key, end=' ')print("\nConjunto: ", end='') # Recorremos el conjunt e imprimimos cada elemento for s in mi_conj:print(s, end =' ')print("\nArchivo: ") # Recorremos el archivo e imprimimos cada elemento for line in mi_archivo:print(line, end ='')
En los casos del diccionario y del conjunto sucede:
Diccionario: cuando hay claves repetidas, se sustituye el último valor que toma la clave ('0':9).
Conjunto: los elementos se ordenan y no se admiten elementos repetidos.
S.2 Mapeo.
En análisis matemático, un Mapeo es una regla que asigna a cada elemento de un primer conjunto, un único elemento de un segundo conjunto, simbólicamente se representa como sigue:
En Python existe la función para realizar este tipo de mapeo que se llama map(function, sequence) cuyo primer parámetro es una función la cual se va a aplicar a una secuencia, que es el segundo parámetro. El resultado será una nueva secuencia con los elementos obtenidos de aplicar la función a cada elemento de la secuencia de entrada.
Ejemplo S.2:
Crear el siguiente mapeo con una lista, una tupla y un conjunto \[
f(x) = x^2
\]\[
\left[
\begin{matrix}
0 \\
1 \\
2 \\
3 \\
4
\end{matrix}
\right]
\begin{matrix}
\longrightarrow \\
\longrightarrow \\
\longrightarrow \\
\longrightarrow \\
\longrightarrow
\end{matrix}
\left[
\begin{matrix}
0 \\
1 \\
4 \\
9 \\
16
\end{matrix}
\right]
\]
# Primero definimos la funcióndef square(x):""" Calcula el cuadrado de x. """return x**2# Luego definimos las secuenciasl = [0,1,2,3,4]t = (0,1,2,3,4)s = {0,1,2,3,4}# Ahora creamos los mapeoslmap =map(square, l)tmap =map(square, t)smap =map(square, s)# Checamos el tipo de cada mapeoprint(type(lmap), type(tmap), type(smap))print('Lista {}'.format(l))print('Mapeo {}\n'.format(list(lmap))) # Convertimos el mapeo a lista# para que se pueda imprimirprint('Tupla {}'.format(t))print('Mapeo {}\n'.format(tuple(tmap)))print('Conj {}'.format(s))print('Mapeo {}\n'.format(set(smap)))
Observa que el resultado del mapeo es un objeto de tipo <class 'map'> por lo que debemos convertirlo en un tipo que pueda ser desplegado para imprimir. Lo anterior se puede realizar también con una función anónima lambda. Por ejemplo, para la lista haríamos lo siguiente:
Lista original [0, 1, 2, 3, 4]
Resultado [0, 1, 4, 9, 16]
.
Ejemplo S.3:
Crear un mapeo para convertir grados Fahrenheit a Celsius y viceversa:
Primer definimos las funciones de conversión:
def toFahrenheit(T):""" Transforma los elementos de T en grados Farenheit. """return (9/5)*T +32def toCelsius(T):""" Transforma los elementos de T en grados Celsius. """return (5/9)*(T-32)
Celsius \(\to\) Fahrenheit
# Lista original con los datosc = [0, 22.5, 40, 100]# Construimos el mapeo y lo nombramos `fmap`.fmap =map(toFahrenheit, c)
# Imprimimos a lista original y el mapeoprint("Grados centígrados (original):", c)print("Grados Farenheit (conversión):", list(fmap))
Solo se puede usar el mapeo una vez, si vuelves a ejecutar la celda anterior el resultado del mapeo estará vacío. Para volverlo a generar debes ejecutar la celda donde se construye el mapeo.
Lo anterior se puede realizar en una sola línea: crear el mapeo, convertir a lista e imprimir.
print("Grados centígrados (original):", c)# Conversión en una sola línea:print("Grados Farenheit (conversión):", list(map(toFahrenheit,c)))
Crear un mapeo para sumar los elementos de tres listas que contienen números enteros.
Primero usando una función definida con def.
Segundo usando una función lambda.
NotaNota.
La función map() se puede aplicar a más de un conjunto iterable, siempre y cuando los iterables tengan la misma longitud y la función que se aplique tenga los parámetros correspondientes.
# Función normaldef suma(x,y,z):""" Suma los números x, y, z. """return x+y+z# Tres listas con enterosa = [1,2,3,4]b = [5,6,7,8]c = [9,10,11,12]# Aplicación del mapeoprint(list(map(suma, a,b,c)))
[15, 18, 21, 24]
Usando una función lambda:
# Función lambdasumaL =lambda x,y,z: x + y + z# Tres listas con enterosa = [1,2,3,4]b = [5,6,7,8]c = [9,10,11,12]# Aplicación del mapeoprint(list(map(sumaL, a,b,c)))
[15, 18, 21, 24]
Todo en una sola línea:
# Tres listas con enterosa = [1,2,3,4]b = [5,6,7,8]c = [9,10,11,12]# Aplicación del mapeoprint(list(map(lambda x,y,z: x + y + z, a,b,c)))
[15, 18, 21, 24]
S.3 Filtrado.
Filtrar es un procedimiento para seleccionar elementos de un conjunto o para impedir su paso libremente.
En matemáticas, un filtro es un subconjunto especial de un conjunto parcialmente ordenado que simbólicamente se ve como sigue:
En Python existe la función filter(function, sequence) para realizar un filtrado cuyo primer parámetro es una función la cual se va a aplicar a una secuencia, que es el segundo parámetro. La función debe regresar un objeto de tipo Booleano: True o False. El resultado será una nueva secuencia con los elementos obtenidos de aplicar la función a cada elemento de la secuencia de entrada.
Ejemplo S.5:
Usando la función filter(), encontrar los números pares en una lista.
def esPar(n):""" Función que determina si un número es par o impar. """if n%2==0:returnTrueelse:returnFalse
# Probamos la funciónprint(esPar(10))print(esPar(9))
True
False
# Creamos una lista de números, del 0 al 19numeros =list(range(20))print(numeros)
Paso 2. Escribimos una función que verifique si una entrada es de tipo int.
def esEntero(i):""" Función que determina si un número es entero. """ifisinstance(i, int): # Checamos si la entrada es de tipo intreturnTrueelse:returnFalse
# Todo en una sola líneaprint(list(filter(esPar, list(filter(esEntero,lista)))))
[4, 8, 10]
Observa que se aplicó dos veces la función filter(), la primera para determinar si el elemento de la lista es entero usando la función esEntero(), la segunda para determinar si el número es par.
Se pueden usar funcione anónimas y tener el código completo en una sola línea:
Sin embargo, la solución anterior es un poco complicada de leer y entender.
.
Ejemplo S.7:
Encontrar los números primos en el conjunto \(\{2, \dots, 50\}\).
def noPrimo():""" Determina la lista de números que no son primos en el rango [2, 50] """ np_list = []for i inrange(2,8):for j inrange(i*2, 50, i): np_list.append(j)return np_listlista_no_primo = noPrimo()print("Números NO primos en el rango [2, 50] \n{}".format(lista_no_primo))
def esPrimo(number):""" Determina si un número es primo o no. """ np_list = noPrimo()if(number notin np_list):returnTrueelse:returnFalse# Creación de la lista de números enteros de 2 a 50numeros =list(range(2,50))# Calculamos los primos usando filter(), con # la función esPrimo() y la lista números.lista_primos =list(filter(esPrimo, numeros))print("\nNúmeros primos en el rango [2, 50] \n{}".format(lista_primos))
Números primos en el rango [2, 50]
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
¿Podrías sustituir la declaración if ... else ... en la función esPrimo() usando el operador ternario?
S.4 Reducción.
Reducción : Disminuir algo en tamaño, cantidad, grado, importancia, ..
La operación de reducción es útil en muchos ámbitos, el objetivo es reducir un conjunto de objetos en un objeto más simple.
Una reducción se hace como sigue:
Dada la función \(f()\) y la secuencia \([s_1, s_2, s_3, s_4]\) se tiene que
En Python existe la función reduce(function, sequence) cuyo primer parámetro es una función la cual se va a aplicar a una secuencia, que es el segundo parámetro. La función debe regresar un objeto que es el resultado de la reducción.
Ejemplo S.8:
Calcular la siguiente serie:
\(1 + 2 + \dots + n = \sum\limits_{i=1}^{n} i = \dfrac{n(n+1)}{2}\)
Si \(n = 4\) entonces 1+2+3+4 = 10
# La función reduce() debe importarse del módulo functoolsfrom functools importreduce
# Creamos la listanums = [1,2,3,4]print(nums)# Calculamos la serie usando reduce y una función lambdasuma =reduce(lambda x, y: x + y, nums)print(suma)
[1, 2, 3, 4]
10
# Se pueden usar arreglos de numpyimport numpy as np# Construimos un arreglo de 1'sa = np.ones(20)print(a)suma =reduce(lambda x, y: x + y, a)print(suma)
numeros = [23,5,23,56,87,98,23]maximo =reduce(lambda a,b: a if (a > b) else b, numeros)print(maximo)
98
S.5 Ejemplos adicionales.
Ejemplo S.11: Recursión.
Calcular el factorial de un número.
El factorial de un número \(n\) se define como el producto de todos los números enteros positivos desde n hasta 1. Matemáticamente, se puede expresar como:
\[
n! = n \times (n−1)!
\]
Observa que para calcular el factorial de \(n\), primero debes calcular el factorial de \(n-1\). El factorial de \(0! = 1\).
La recursión es un concepto en programación donde una función se llama a sí misma para resolver un problema.
Una función recursiva divide un problema en subproblemas más pequeños y sencillos, resolviéndolos de manera repetida hasta llegar a una condición base: un caso simple que la función puede resolver sin más llamadas recursivas.
La clave en la recursión es que siempre se requiere de lo siguiente:
Un caso base: Es la condición que detiene la recursión y evita que la función se llame indefinidamente.
Una llamada recursiva: La función se llama a sí misma con un problema más pequeño o una versión simplificada del problema original.
En el caso del factorial de un número \(n\), el caso base es: \(0! = 1\).
def factorial1(n):""" Función recursiva que calcula el factorial de n. """if n ==0: # Caso basereturn1return n * factorial1(n-1) # Se llama a factorial() nuevamente
factorial1(5)
120
Usando reduce y una función lambda es posible calcular el factorial:
reduce(lambda x, y: x * y, range(1,6))
120
Podemos entonces modificar la función factorial combinándola con reduce como sigue:
def factorial2(n):""" Función recursiva que calcula el factorial de n. """if n ==0: # Caso basereturn1returnreduce(lambda x, y: x * y, range(1, n +1))
factorial2(5)
120
Si usamos ahora el operador ternario, podemos escribir la función factorial aún más compacta:
def factorial3(n):return1if n ==0elsereduce(lambda x, y: x * y, range(1, n +1))
factorial3(5)
120
Consideraciones sobre Recursión:
La recursión puede ser elegante y fácil de entender para problemas que se pueden dividir de manera similar (como el factorial).
Sin embargo, no siempre es la solución más eficiente, ya que una recursión profunda consume más memoria por las llamadas acumuladas en la pila de ejecución. Esto puede llevar a un desbordamiento de la memoria si no se maneja adecuadamente o si no hay un caso base claro.
Ejemplo S.12:
Contar el número de caracteres de un texto sin tomar en cuenta los espacios en blanco, combinando reduce(), map( ) y lambda.
Paso 1. Definimos un texto.
texto ='Hola Mundo Pythónico!'
Contar los caracteres, sin tomar en cuenta los espacios en blanco, es fácil con len() y str.count():
print(len(texto))print(texto.count(" "))
21
2
print("Número de caracteres: {}".format(len(texto)-texto.count(" ")))
Número de caracteres: 19
Con este valor podemos comprobar si hicimos correctamente el ejercicio.
Paso 2. Separamos el texto por palabras.
palabras = texto.split()print(palabras)
['Hola', 'Mundo', 'Pythónico!']
Paso 3. Creamos una función lambda para contar los caracteres de una palabra.
# Utilizamos un gestor de contextowithopen('./QueLesQuedaALosJovenes.txt','r') as archivo: suma =0for linea in archivo: palabras = linea.split() suma += countChar(palabras)print(suma)
854
.
Ejemplo S.13:
Encontrar todos los números pares de una lista combinando filter() con lambda.
# Lista de númerosnums = [0, 2, 5, 8, 10, 23, 31, 35, 36, 47, 50, 77, 93]# Aplicación de filter y lambdaresult =filter(lambda x : x %2==0, nums)print(list(result))
[0, 2, 8, 10, 36, 50]
.
Ejemplo S.14:
Encontrar todos los números primos en el conjunto \(\{2, \dots, 50\}\) combinando combinando filter() con lambda.
# Lista de números de 2 a 50nums =list(range(2, 51)) # Cálculo de los números primos usando filter y lambdafor i inrange(2, 8): nums =list(filter(lambda x: x == i or x % i, nums))print(nums)
La siguiente función es impura porque modifica la lista:
def cuadradosImpuros(lista):
for i, v in enumerate(lista):
lista[i] = v ** 2
return lista
numeros_originales = list(range(5))
print(numeros_originales)
print(cuadradosImpuros(numeros_originales))
print(numeros_originales)
La salida del código anterior es el siguiente:
[0, 1, 2, 3, 4]
[0, 1, 4, 9, 16]
[0, 1, 4, 9, 16]
Escribe una versión pura de la función cuadradosImpuros(lista) usando map() y lambda.
numeros_originales =list(range(5))print(numeros_originales)print(list(map(lambda x: x **2, numeros_originales)))print(numeros_originales)
[0, 1, 2, 3, 4]
[0, 1, 4, 9, 16]
[0, 1, 2, 3, 4]
.
Ejemplo S.16:
Convertir el siguiente diccionario que contiene valores de grados Fahrenheit {'t1':32.0, 't2':72.5, 't3':104.0, 't4':212.0} a otro diccionario que contenga los correspondientes valores en grados Celsius.