Laboratorio 0: Practicando en Python

MDS7202: Laboratorio de Programación Científica para Ciencia de Datos - Primavera 2021

Cuerpo Docente:¶

  • Profesor: Matías Rojas y Mauricio Araneda
  • Auxiliar: Ignacio Meza D.
  • Ayudante: Rodrigo Guerra

Objetivos del lab:¶

  • El objetivo de este lab es que ustedes puedan practicar con el lenguaje de python, aplicando los conocimientos vistos hasta ahora en el curso. La realización de este laboratorio es completamente opcional.

Problemas con Listas y Strings 💻¶

Por cada uno de estos problemas, elabore una función que los resuelva:

Pregunta 1¶

Dada una lista de $n$ elementos, no necesariamente ordenada, defina funciones que busquen:

  • El elemento menor.
  • El elemento mayor.
  • El promedio de los elementos en la lista.

Ejemplo:

listaN = [12,4,34,1,2,52,1,54,98,5,235,76,57,87,123,32,32]
menor = 1
mayor = 235

Además, implemente un par de test que verifiquen la correctitud de las funciones.

Nota: Idealmente se hacen tests sobre los casos bordes. ¿Qué casos bordes tendríamos aquí?

In [1]:
from __future__ import annotations
In [2]:
def menor_elemento(lista: list[int]) -> int|float:
    """Entrega el menor elemento de la lista de enteros.
    Si la lista es vacía, se retorna un float infinito."""
    menor_elemento_visto = float("inf")  # OBS: Python no implementa un int infinito,
                                         # por lo cual debemos retornar un float
    for elemento in lista:
        if elemento < menor_elemento_visto:
            menor_elemento_visto = elemento
    return menor_elemento_visto


def mayor_elemento(lista: list[int]) -> int|float:
    """Entrega el mayor elemento de la lista de enteros.
    Si la lista es vacía, se retorna un float infinito negativo."""
    return -menor_elemento([-elemento for elemento in lista])


def promedio(lista: list[int]) -> float:
    """Entrega el promedio de una lista de enteros.
    Si la lista es vacía, levanta un ZeroDivisionError."""
    try:
        return sum(lista)/len(lista)
    except ZeroDivisionError:
        raise ZeroDivisionError("Promedio de una lista vacía está indefinido")
In [3]:
def imprimir_resultados_parte_1(lista: list[int]) -> None:
    """Imprime el menor, mayor y promedio de los elementos de la lista."""
    print(f"lista: {lista}")
    print(f"menor elemento: {menor_elemento(lista)}")
    print(f"mayor elemento: {mayor_elemento(lista)}")
    try:
        print(f"promedio: {promedio(lista)}")
    except Exception as e:
        print(f"promedio levanta excepción: {e}")
In [4]:
listaN = [12,4,34,1,2,52,1,54,98,5,235,76,57,87,123,32,32]
imprimir_resultados_parte_1(listaN)
lista: [12, 4, 34, 1, 2, 52, 1, 54, 98, 5, 235, 76, 57, 87, 123, 32, 32]
menor elemento: 1
mayor elemento: 235
promedio: 53.23529411764706
In [5]:
imprimir_resultados_parte_1([])
lista: []
menor elemento: inf
mayor elemento: -inf
promedio levanta excepción: Promedio de una lista vacía está indefinido
In [52]:
imprimir_resultados_parte_1([0])
lista: [0]
menor elemento: 0
mayor elemento: 0
promedio: 0.0

Pregunta 2¶

Dada una lista de n números, no ordenados, genere una función que ordene la lista de mayor a menor.

def ordenar(lista):
    pass
In [53]:
def ordenar(lista: list[int]) -> None:
    """Ordena la lista de enteros descendentemente."""
    copy = lista.copy()
    for i, _ in enumerate(lista):
        lista[i] = copy.pop(copy.index(max(copy)))


def imprimir_resultados_parte_2(lista: list[int]):
    copia = lista.copy()
    print(f"Lista original: {copia}")
    ordenar(copia)
    print(f"Lista ordenada de mayor a menor: {copia}")
In [54]:
listaN = [12,4,34,1,2,52,1,54,98,5,235,76,57,87,123,32,32]
imprimir_resultados_parte_2(listaN)
Lista original: [12, 4, 34, 1, 2, 52, 1, 54, 98, 5, 235, 76, 57, 87, 123, 32, 32]
Lista ordenada de mayor a menor: [235, 123, 98, 87, 76, 57, 54, 52, 34, 32, 32, 12, 5, 4, 2, 1, 1]

Pregunta 3¶

Genere una función que obtenga la moda de una lista usando diccionarios.

In [55]:
def diccionario_frecuencias(lista: list) -> dict:
    """Entrega un diccionario que asocia cada elemento de la lista a su frecuencia."""
    return {valor: len([elemento for elemento in lista if elemento == valor])
                   for valor in lista}


def moda(lista: list):
    """Entrega la moda de la lista, buscándo la llave del valor máximo del diccionario de frecuencias."""
    return max(diccionario_frecuencias(lista).items(), key=lambda x: x[1])[0]


def imprimir_resultados_parte_3(lista: list):
    print(f"Lista: {lista}")
    print(f"Frecuencias de cada elemento de la lista: {diccionario_frecuencias(lista)}")
    print(f"Moda de la lista: {moda(lista)}")
In [56]:
imprimir_resultados_parte_3(listaN)
Lista: [12, 4, 34, 1, 2, 52, 1, 54, 98, 5, 235, 76, 57, 87, 123, 32, 32]
Frecuencias de cada elemento de la lista: {12: 1, 4: 1, 34: 1, 1: 2, 2: 1, 52: 1, 54: 1, 98: 1, 5: 1, 235: 1, 76: 1, 57: 1, 87: 1, 123: 1, 32: 2}
Moda de la lista: 1

Notar que la lista era bimodal, pero retornamos solamente una de sus modas.

Pregunta 4¶

Vea si un número es palíndromo o no. Los números palíndromos son aquellos que al leerlos al revés y al derecho son iguales, por ejemplo el 123321 o el 98789.

In [33]:
def es_palindromo(numero: int) -> bool:
    """Indica si un número es palíndromo o no."""
    as_str = str(numero)
    return as_str == as_str[::-1]


def imprimir_resultados_parte_4(numero: int):
    print(f"¿Es {numero} palíndromo?: {es_palindromo(numero)}")
In [59]:
imprimir_resultados_parte_4(123321)
imprimir_resultados_parte_4(98789)
imprimir_resultados_parte_4(564135)
imprimir_resultados_parte_4(2020)
imprimir_resultados_parte_4(123)
imprimir_resultados_parte_4(202)
¿Es 123321 palíndromo?: True
¿Es 98789 palíndromo?: True
¿Es 564135 palíndromo?: False
¿Es 2020 palíndromo?: False
¿Es 123 palíndromo?: False
¿Es 202 palíndromo?: True

Pregunta 5¶

Dado un texto, conviértalo en estilo Camel Case, vea el siguiente ejemplo:

Texto inicial: “Este es un texto de ejemplo para mostrar el Camel Case”.

Texto final: “EsteEsUnTextoDeEjemploParaMostrarElCamelCase”.
In [60]:
def convertir_a_camel_case(texto: str) -> str:
    """Dado un texto, retorna su representación en Camel Case."""
    return "".join(map(str.title, texto.split(" ")))


def imprimir_resultados_parte_5(text: str):
    print(f"Texto inicial: {text}")
    print(f"Texto final: {convertir_a_camel_case(text)}")
In [61]:
texto_inicial = "Este es un texto de ejemplo para mostrar el Camel Case"
imprimir_resultados_parte_5(texto_inicial)
Texto inicial: Este es un texto de ejemplo para mostrar el Camel Case
Texto final: EsteEsUnTextoDeEjemploParaMostrarElCamelCase

Pregunta 6¶

Dado un texto y una letra indique cuantas veces se repite la letra dentro del texto, vea el siguiente ejemplo:

Texto inicial: “Este es un texto de ejemplo para contar la cantidad de veces que aparece cierta letra”.

Letra a buscar: e
Respuesta: “La letra e aparece 15 veces.
Observación: Las mayúsculas y minúsculas deben ser contadas.
In [62]:
def imprimir_frecuencia_caracter(texto: str, caracter: str):
    """Dado un texto y un caracter, imprime la cantidad de veces que aparece esta letra dentro del texto."""
    frecuencias = diccionario_frecuencias(list(texto.lower()))
    print(f"La letra {caracter} aparece {frecuencias.get(caracter.lower(), 0)} veces")
In [63]:
texto = "Este es un texto de ejemplo para contar la cantidad de veces que aparece cierta letra"
imprimir_frecuencia_caracter(texto, "e")
imprimir_frecuencia_caracter(texto, "z")
La letra e aparece 15 veces
La letra z aparece 0 veces

Problemas Varios 🤩¶

A continuación se presentan diferentes problemas, elabore una función que los resuelva.

Nota: A diferencia de la primera parte, en esta sección puede aplicar todos sus conocimientos para el desarrollo de las funciones.

Pregunta 1:¶

Un monto inicial de dinero (por ejemplo un millón de pesos) se puede depositar en un banco con un interés anual de 5%. Esto significa que cumplido un año se dispondrá de \$1.050.000. Si se deposita por dos años, entonces al finalizar el 2º año se dispondrá de \\$1.102.500, es decir, \$1.050.000 reajustado en 5%.

Al respecto, si se invoca a la función calc_capital_final(1000000, 5, 2) entrega el resultado 1050000.0.

Escriba la función calc_capital_final

$$C_f = C_i(1+i)^n$$
  • $C_f$ = Capital Final
  • $C_i$ = Capital Inicial
  • $i$ = Tasa de interés
  • $n$ = Periodo del ahorro

Además, incluya tests que certifiquen el correcto funcionamiento de la función.

In [68]:
def calc_capital_final(capital_inicial: float, tasa_interes: float, años: float) -> float:
    return capital_inicial * (1 + tasa_interes/100)**años
In [71]:
print(f"Capital inicial: {calc_capital_final(1e6, 5, 0)}")
print(f"Capital final después de 1 año: {calc_capital_final(1e6, 5, 1)}")
print(f"Capital final después de 2 años: {calc_capital_final(1e6, 5, 2)}")
Capital inicial: 1000000.0
Capital final después de 1 año: 1050000.0
Capital final después de 2 años: 1102500.0

Pregunta 2:¶

Escriba una función que para $x \ne 0$, calcule $\frac{1}{x^0} + \frac{1}{x^1} + \frac{1}{x^2} + \dots + \frac{1}{x^n}$. Para esto, considere x como un int o float y a n como un int en la siguiente función:

def sumatoria(x,n): pass

Use la función anterior en un programa que determine si la suma $\frac{1}{x^0} + \frac{1}{x^1} + \frac{1}{x^2} + \dots + \frac{1}{x^n}$ converge o no. Utilice un iterador para probar diferentes n en la función sumatoria creada, donde el programa deberá finalizar si cumple o falla los siguientes puntos de convergencia: - Se considera que la suma converge si la diferencia entre dos sumas sucesivas resulta menor que $10^{-3}$. Ejemplo: $sumatoria(x,1) - sumatoria(x,0) < 10^{-3}$ - Se considera que no converge si para $n=20$ aún no se alcanza el criterio de convergencia.

In [72]:
def sumatoria(x: float, n: int) -> float:
    return sum(x**(-i) for i in range(n + 1))


def es_convergente(x: float) -> bool:
    # return 1/x**20 < 10**(-3)  # Utilizando la monotonía de las diferencias de términos sucesivos
    # return abs(x) > 1  # Utilizando el radio de convergencia de una serie geométrica
    for i in range(20):
        if sumatoria(x, i + 1) - sumatoria(x, i) < 1e-3:
            return True
    return False
In [78]:
print(f"sumatoria(1, 10) = {sumatoria(1, 10)}")
print(f"sumatoria(1, 20) = {sumatoria(1, 20)}")
print(f"sumatoria(.5, 10) = {sumatoria(.5, 10)}")
print(f"sumatoria(.5, 20) = {sumatoria(.5, 20)}")
print(f"sumatoria(2, 10) = {sumatoria(2, 10)}")
print(f"sumatoria(2, 20) = {sumatoria(2, 20)}")
sumatoria(1, 10) = 11.0
sumatoria(1, 20) = 21.0
sumatoria(.5, 10) = 2047.0
sumatoria(.5, 20) = 2097151.0
sumatoria(2, 10) = 1.9990234375
sumatoria(2, 20) = 1.9999990463256836
In [79]:
print(f"es_convergente(1) = {es_convergente(1)}")
print(f"es_convergente(.5) = {es_convergente(.5)}")
print(f"es_convergente(2) = {es_convergente(2)}")
es_convergente(1) = False
es_convergente(.5) = False
es_convergente(2) = True

Pregunta 3:¶

Un anagrama es una palabra que resulta de la transposición de letras de otra palabra o frase.

Por ejemplo, las palabras “caso” y “caos” sonan agramas de “saco” y las palabras “acaso” y “ocaso” no son anagramas de “caso”. Las anagramas poseen las mismas letras, con la misma cantidad de apariciones, pero en un orden diferente

Escriba la función def anagrama(x,y) que reciba dos palabras y entregue True si son anagramas y False si no.

In [89]:
def anagrama(x: str, y: str) -> bool:
    """Recibe dos palabras y entrega True si son anagramas y False si no."""
    frecuencias_x = diccionario_frecuencias(list(x.lower()))
    frecuencias_y = diccionario_frecuencias(list(y.lower()))
    return frecuencias_x == frecuencias_y

def imprimir_resultados_pregunta_3(x, y):
    print(f"¿Es '{x}' anagrama de '{y}'?: {anagrama(x, y)}")
In [90]:
imprimir_resultados_pregunta_3("caso", "caos")
imprimir_resultados_pregunta_3("caso", "saco")
imprimir_resultados_pregunta_3("caos", "saco")
imprimir_resultados_pregunta_3("acaso", "caso")
imprimir_resultados_pregunta_3("ocaso", "caso")
imprimir_resultados_pregunta_3("ocaso", "acaso")
¿Es 'caso' anagrama de 'caos'?: True
¿Es 'caso' anagrama de 'saco'?: True
¿Es 'caos' anagrama de 'saco'?: True
¿Es 'acaso' anagrama de 'caso'?: False
¿Es 'ocaso' anagrama de 'caso'?: False
¿Es 'ocaso' anagrama de 'acaso'?: False

Pregunta 4:¶

Programe el juego gato. Para esto:

  • Cree una matriz inicial (lista de listas) que contenga el estado actual del gato (al inicio).

  • Cree la función es_tachable la cual reciba un par de coordenadas (i, j) y que retorne si la posición ingresada se puede tachar (True) o ya se encuentra ocupada (False).

  • Cree una función tachar que modifiqua el estado del gato para reflejar la marca de uno de los jugadores. Esta debe recibir una coordenada (i, j) y el valor de la posicion v (solo 'X' o 'O') y debe variar el estado del gato de tal manera que refleje una jugada. La función debe retornar True si fue capaz de o False en caso contrario.

  • Cree la función existe_ganador que verifique si luego de una jugada existe algún ganador. Si existe, retorna True. False en caso contrario.

  • Implemente un ciclo while que en cada momento no se detenga hasta que exista un jugador ganador. En este ciclo:
    • Informa sobre el estado actual del gato y sobre el jugador que le corresponde marcar una casilla.
    • Se debe solicitar al jugador que ingrese la coordenada
    • Se debe preguntar si la jugada del jugador actual es válida.
    • Se debe actualizar el estado del gato
    • Se debe cambiar al siguiente jugador.
In [103]:
matriz = [["" for _ in range(3)] for __ in range(3)]

def inicializar_matriz():
    """Rellena la matriz (lista de listas) que con el estado inicial del gato."""
    for i in range(3):
        for j in range(3):
            matriz[i][j] = ""

def es_tachable(i: int, j: int) -> bool:
    """Reciba un par de coordenadas (i, j) y que retorna si la posición ingresada se puede tachar (True) o ya se encuentra ocupada (False).
    En el caso de recibir coordenadas fuera de la matriz, también retorna False.
    """
    try:
        return not matriz[i][j]
    except IndexError:  # Si se da un índice fuera de la matriz
        return False


def tachar(i: int, j: int, v: str) -> bool:
    """Modifica el estado del gato para reflejar la marca de uno de los jugadores.
    Recibe una coordenada (i, j) y el valor de la posicion v (solo 'X' o 'O'),
    y debe varia el estado del gato de tal manera que refleje una jugada.
    La función retorna True si fue capaz de tachar la posición o False en caso contrario."""
    if es_tachable(i, j):
        matriz[i][j] = v
        return True
    return False


def tres_en_raya(marcas: list[str]) -> bool:
    """Función auxiliar que determina si una lista de marcas consiste de 3 'X' o de 3 'O'."""
    return marcas == 3*["X"] or marcas == 3*["O"]


def existe_ganador() -> bool:
    """Verifica si luego de una jugada existe algún ganador.
     Si existe, retorna True. False en caso contrario."""
    for i in range(3):
        if tres_en_raya(matriz[i]):  # Fila
            return True
        if tres_en_raya([row[i] for row in matriz]):  # Columna
            return True
    if tres_en_raya([matriz[i][i] for i in range(3)]):  # Diagonal
        return True
    if tres_en_raya([matriz[i][2 - i] for i in range(3)]):  # Diagonal
        return True
    return False


def esta_la_matriz_llena() -> bool:
    """Función auxiliar que determina si la matriz está llena o no."""
    for fila in matriz:
        if "" in fila:
            return False
    return True

def representar_matriz() -> str:
    """Función auxiliar para visualizar la matriz como tablero."""
    return "\n".join(str(row) for row in matriz)
In [112]:
def jugar():
    # Inicializa la matriz
    inicializar_matriz()
    turno_de_X = True
    # Implementa el juego
    while not (existe_ganador() or esta_la_matriz_llena()):  # Implementa un ciclo while que en cada momento no se detenga hasta que exista un jugador ganador
        # Informa sobre el estado actual del gato y sobre el jugador que le corresponde marcar una casilla.
        print(f"Tablero actual:\n{representar_matriz()}")
        jugador = "X" if turno_de_X else "O"
        print(f"jugador: {jugador}")
        # Se debe solicitar al jugador que ingrese la coordenada
        fila = int(input("Ingrese la fila: "))
        columna = int(input("Ingrese la columna: "))
        while not es_tachable(fila, columna):  # Se debe preguntar si la jugada del jugador actual es válida.
            print(f"Coordenadas {(fila, columna)} son inválidas. Intente de nuevo.")
            fila = int(input("Ingrese la fila: "))
            columna = int(input("Ingrese la columna: "))
        tachar(fila, columna, jugador)  # Se debe actualizar el estado del gato
        turno_de_X = not turno_de_X  # Se debe cambiar al siguiente jugador
    # Informa el resultado
    if existe_ganador():
        print(f"Ganó {jugador}!")
    else:
        print("Empate!")
In [113]:
jugar()
Tablero actual:
['', '', '']
['', '', '']
['', '', '']
jugador: X
Ingrese la fila: 0
Ingrese la columna: 1
Tablero actual:
['', 'X', '']
['', '', '']
['', '', '']
jugador: O
Ingrese la fila: 1
Ingrese la columna: 1
Tablero actual:
['', 'X', '']
['', 'O', '']
['', '', '']
jugador: X
Ingrese la fila: 0
Ingrese la columna: 0
Tablero actual:
['X', 'X', '']
['', 'O', '']
['', '', '']
jugador: O
Ingrese la fila: 1
Ingrese la columna: 0
Tablero actual:
['X', 'X', '']
['O', 'O', '']
['', '', '']
jugador: X
Ingrese la fila: 0
Ingrese la columna: 2
Ganó X!