Programando una Lotería Mexicana en Python

Loteria Mexicana Python

Uno de los juegos mexicanos más populares es sin duda La Lotería Mexicana. Todos la hemos jugado alguna vez y la verdad es un juego muy divertido para pasar un rato en familia (pero sin celulares por favor).

Una forma de aprender lenguajes de programación es reproduciendo juegos sencillos. En el 2014 ya había hecho este mismo experimento pero usando Ruby. Pero de un tiempo para acá me he adaptado mucho mejor a Python, así que decidí hacer el mismo ejercicio con este lenguaje.

El azar en los juegos

El planteamiento es muy sencillo, en una lista almacenare todos los nombres de las cartas. En otra lista voy a almacenar la misma información pero en desorden gracias a la función sample a la que llamare reborujado.

Para usar la función es necesario importarla de random que es la librería de números pseudoaleatorios de Python. Que es lo que le añade esa pizca de azar al juego.

from random import sample

Lo bonito de la función sample es que puede extraer un subconjunto de la lista cartas. Para eso le paso como parámetro el tamaño total de la lista con la función len, para no excluir ninguna carta.

Una pausa por favor

Otra función que agrego al código es sleep para darle una pequeña pausa a la ejecución del código y dar la impresión de que se están cantando las cartas. La librería time incluye algunas funciones relacionadas con el tiempo, pero como se maneja el tiempo en Linux creo que es tema para un artículo completo aparte, ya saben que me obsesionan esos temas.

from time import sleep

En este caso ajuste la pausa a 3 segundos, pero se puede modificar fácilmente en el código.

El código

Ahora solo falta mostrarlo todo junto.

from random import sample
from time import sleep

velocidad = 3

cartas = ["1  El Gallo",
          "2  El Diablito",
          "3  La Dama",
          "4  El catrín",
          "5  El paraguas",
          "6  La sirena",
          "7  La escalera",
          "8  La botella",
          "9  El barril",
          "10 El árbol",
          "11 El melón",
          "12 El valiente",
          "13 El gorrito",
          "14 La muerte",
          "15 La pera",
          "16 La bandera",
          "17 El bandolón",
          "18 El violoncello",
          "19 La garza",
          "20 El pájaro",
          "21 La mano",
          "22 La bota",
          "23 La luna",
          "24 El cotorro",
          "25 El borracho",
          "26 El negrito",
          "27 El corazón",
          "28 La sandía", 
          "29 El tambor",
          "30 El camarón",
          "31 Las jaras",
          "32 El músico",
          "33 La araña",
          "34 El soldado",
          "35 La estrella",
          "36 El cazo",
          "37 El mundo",
          "38 El apache",
          "39 El nopal",
          "40 El alacrán",
          "41 La rosa",
          "42 La calavera",
          "43 La campana",
          "44 El cantarito",
          "45 El venado",
          "46 El sol",
          "47 La corona",
          "48 La chalupa",
          "49 El pino",
          "50 El pescado",
          "51 La palma",
          "52 La maceta",
          "53 El arpa",
          "54 La rana"]

# A reborujar las cartas
reborujado = sample(cartas, len(cartas))

# Correeeee correeee y se va.
print("Correeeee correeee y se va !!!")

for carta in reborujado:
    sleep(velocidad)
    print(carta)

print("\n¡Lotería!")

https://linuxmanr4.com/wp-content/uploads/2021/09/loteria_mexicana_python_2.webm
¡Corre, corre y se vaaaaa!

¡Lotería!

Gracias al comentario de CHAPEAU mi mente divagó un poco. Con este programa puedo jugar muchas veces ¿Existirá una tabla ganadora? De niños cuando no ganábamos era común cambiar de tabla por que la que teníamos no tenía “suerte”. Pero ¿Y si no era suerte?.

Para averiguarlo tuve que conseguir unas tablas, no tenía ninguna a la mano, así que busqué un poco y encontré estas. Las tablas ocupan 16 figuras de las cartas, pero ninguna se repite. Todas las tablas de un juego de lotería son diferentes.

Para efectos de código decidí omitir los nombres de las figuras y quedarme solamente con los números. Entonces declaré 3 variables principales.

  • cartas. Una lista de los números de las cartas del 1 al 54.
  • tablas. Una matriz con el contenido de figuras de cada tabla.
  • resultados. Del lado izquierdo de la matriz voy acumulando las veces que ha ganado esa tabla. En el lado derecho voy juntando los “frijolitos”.

Durante el juego cada vez que sale una carta reviso todas las tablas para ver si esta presente. Si es así, entonces le pongo un frijolito a la tabla. Es tabla llena si logra acumular 16 frijolitos.

En caso de que una tabla sea tabla llena incremento la cantidad de veces que ha ganado. Pero esto lo tengo que revisar en todas las tablas, porque se puede dar el caso de tener más de una tabla ganadora con la misma carta. A todos nos ha pasado.

Luego tuve que ajustar el código para hacer procedimientos para hacerlo un poco más modular y claro el código.

from random import sample

# Para mostrar de una mejor forma las matrices.
def matriz2cadena(matriz):
    cadena = ''
    for i in range(len(matriz)):
        cadena += '['
        for j in range(len(matriz[i])):
            cadena += '{:>4s}'.format(str(matriz[i][j]))
        cadena += ']\n'
    return cadena

# Juego completo, hasta encontrar la tabla ganadora.
def juego():
    # A reborujar las cartas
    reborujado = sample(cartas, len(cartas))

    for carta in reborujado:
        for x in range(0, 10):
            # Buscamos la carta en todas las tablas (puede aparecer en más de una).
            if carta in tablas[x]:
                # Si aparece, agregamos un frijolito a la tabla.
                resultados[x][1] += 1                
        if tabla_llena():
            break    

# Dice si ya existe una (o varias tablas) con 16 frijolitos (tabla llena).
def tabla_llena():
    conteo = 0
    for x in range(0, 10):
        if resultados[x][1] == 16:
            conteo += 1
    if conteo > 0:
        return True
    else:
        return False

# Lleva un contador de las tablas ganadoras.
def acumular_resultados():
    for x in range(0,10): 
        if resultados[x][1] == 16:
            resultados[x][0] += 1
        resultados[x][1] = 0  # Quita los frijolitos de la tabla.


cartas = list(range(1, 55))

# https://tuloteriamexicana.com/wp-content/uploads/CartasNormal4x4_1_10.pdf
tablas = [
    [1, 2, 3, 4, 10, 11, 12, 13, 19, 20, 21, 22, 28, 29, 30, 31],
    [6, 7, 8, 9, 15, 16, 17, 18, 24, 25, 26, 27, 33, 34, 35, 36],
    [2, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 20],
    [43, 44, 45, 21, 52, 53, 54, 26, 7, 8, 9, 31, 16, 17, 18, 36],
    [22, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40],
    [21, 22, 23, 24, 30, 31, 32, 33, 39, 40, 41, 42, 48, 49, 50, 51],
    [25, 26, 27, 41, 34, 35, 36, 46, 43, 44, 45, 51, 52, 53, 54, 32],
    [42, 43, 44, 45, 47, 48, 49, 50, 52, 53, 54, 1, 40, 10, 19, 20],
    [41, 42, 37, 38, 50, 51, 46, 47, 5, 6, 1, 2, 14, 15, 10, 11],
    [39, 40, 19, 20, 48, 49, 28, 29, 3, 4, 37, 38, 12, 13, 46, 47]
]

# Del lado izquierdo llevo el conteo de las veces que la tabla ha ganado.
# Y en el lado derecho los frijolitos que acumula cada tabla.
resultados = [
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0]
]

# Ejecutamos 100 mil juegos. Veamos que pasa.
for x in range(0,100000):
    juego()
    acumular_resultados()

print(matriz2cadena(resultados))



No sean muy rudos con el código, no está optimizado, pero eso sí, se aceptan sugerencias.

¡Tenemos un resultado!

Okey, llegó el momento de la verdad. Ejecuté el código con ciclos de 100 juegos varias veces, pero no noté diferencias. En unas ocasiones ganaba una tabla, luego otra, nada concluyente.

Lo mismo pasó al hacer ciclos de 1000 juegos. Decidí echar toda la carne al asador e hice ciclos de 100 mil juegos. Entonces una tabla empezó a destacar.

La tabla que ganó en más ocasiones después de 100 mil juegos fue (redoble de tambores) ¡La Tabla 08!

Consistentemente fue la más ganadora.

¡Esta es la buena!

Con pocas modificaciones se puede ajustar a un mayor número de tablas o con distinta distribución. Por el momento, si imprimo estas tablas definitivamente me quedo con la que aparece en la imagen.

Ahora si, mi mente ya puede descansar un poco, simplemente no me pude quedar con la duda ¿Les ha pasado?

¡Hasta la próxima!

La conjetura de Collatz con Python, algoritmo tradicional y recursivo

La conjetura de Collatz es uno de esos problemas matemáticos que son muy fáciles de enunciar pero que encierran una complejidad maravillosa.

No es la primera vez que me enfrento a este problema, es un clásico como ejercicio para cualquier lenguaje de programación, pero en esta ocasión usaré Python que es mi favorito últimamente.

Conjetura de Collatz

Pero primero lo primero ¿Qué es la conjetura de Collatz?

Empezamos con un número entero positivo. Lo evaluamos, si el número es par entonces lo dividimos entre 2. Si es impar, entonces se multiplica por 3 y se le suma 1. Al resultado lo volvemos a evaluar y nuevamente aplicamos las operaciones correspondientes.

Al final, la secuencia de números termina con 4 , 2, 1

Pero esto no se ha demostrado que ocurra en todos los números enteros positivos, por eso es una conjetura.

Veamos un ejemplo muy simple:

numero = int(input("Teclee un número entero positivo : "))

if numero > 0:
    while numero != 1:
        if numero % 2:  
            numero = numero * 3 + 1
        else:
            numero //= 2
        print(numero)
else:
    print("El número que tecleo no es válido")

Un detalle curioso es como se evalúa si un número es par o impar.

numero % 2

Como pueden ver, se utiliza el operador módulo % que regresa el residuo de dividir el número entre 2.

Si el residuo es cero, entonces es par, si es uno entonces es impar.

True es igual a 1, False es igual a 0.

En el if de una forma prácticamente directa evaluamos si es par o impar.

Ahora veamos el resultado:

Teclee un número entero positivo : 11
34
17
52
26
13
40
20
10
5
16
8
4
2
1

La ejecución del programa se detiene cuando el valor de numero llega a 1.

Ahora en modo recursivo.

La recursión es una forma muy interesante de atacar un problema. Porque para llegar a la solución se llama nuevamente a la misma función.

Si tiene curiosidad haga una pausa y declare una función que haga las operaciones y podrá entender el ejercicio mental que es declarar una función recursiva.

El peligro es la condición de salida, porque si no se cumple, el programa puede entrar en un bucle infinito y no queremos eso.

numero = int(input("Teclee un número entero positivo : "))


def collatz(numero):
    print(numero)
    while numero != 1:
        if numero % 2:
            return collatz(numero * 3 + 1)
        else:
            return collatz(numero // 2)


if numero > 0:
    collatz(numero)
else:
    print("El número que tecleo no es válido")

Todavía tengo sentimientos encontrados sobre la ubicación de la instrucción print(numero) , si son observadores notarán la diferencia.

Teclee un número entero positivo : 11
11
34
17
52
26
13
40
20
10
5
16
8
4
2
1
 

Pero para lo único que la necesito es para “ver” el comportamiento de la variable numero durante la ejecución del programa.

Bonus track

¿Ahora que sigue? esto lo voy a hacer como ejercicio personal. Voy a poner todos los resultados intermedios en una variable que sea una lista para mostrarla al final. Y probablemente haré un test que comprobará los resultados.

Doctest

Pues al final no me quedé con las ganas e incluí algunas pruebas dentro del código. En Python hay varias formas, pero me decidí por la que considero la más integrada y más sencilla para mí doctest. Las pruebas se integran perfectamente a manera de comentarios con datos de entrada y el resultado que se espera.

Este es el código que incluye las pruebas:

def collatz(n):
    """
    Regresa en una lista los valores de una serie de Collatz

    >>> collatz(11)
    [11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]

    >>> collatz(12)
    [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]

    >>> collatz(27)
    [27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]

    """

    resultado = []
    resultado.append(n)
    while n != 1:
        if n % 2:
            n = n * 3 + 1
        else:
            n //= 2
        resultado.append(n)
    return resultado


numero = int(input("Teclee un número entero positivo : "))

if numero > 0:
    print(collatz(numero))
else:
    print("El número que tecleo no es válido")


if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=False)

El código se ejecuta de manera normal, así que para invocar las pruebas se hace de la siguiente forma.

python -m doctest -v collatz.py

Teclee un número entero positivo : 7
[7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
Trying:
    collatz(11)
Expecting:
    [11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
ok
Trying:
    collatz(12)
Expecting:
    [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]
ok
Trying:
    collatz(27)
Expecting:
    [27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
ok
1 items had no tests:
    collatz
1 items passed all tests:
   3 tests in collatz.collatz
3 tests in 2 items.
3 passed and 0 failed.
Test passed.

Con esto logró dos objetivos pendientes. Almacenar el resultado en una lista y las pruebas del código.

El tema de la Conjetura de Collatz es fascinante, pero no soy un divulgador así que si quieren saber más del tema les recomiendo este video.

Sientanse libres de comentar el código, de esta forma se exploran nuevos enfoques que uno no conoce.

¡Saludos y hasta el próximo código!

Python, MySQL y como mezclarlos para hacer consultas

Siempre es interesante juntar Python y MySQL, de esta mezcla pueden resultar programas muy útiles.

Por ejemplo, se pueden hacer consultas a una base de datos y el resultado enviarlo por Telegram, guardarlo en una hoja de cálculo, enviar esa información por correo, hacer cálculos o simplemente mostrarlo en pantalla.

La potencia de una base de datos es guardar grandes cantidades de información para luego filtrarla a gran velocidad, las ventajas con muchas.

En este artículo verán un ejemplo sencillo de como hacer una consulta con Python a una base de datos MySQL ¡Comenzamos!

PyMySQL es el que hace la magia

Hacer consultas con Python es relativamente sencillo. Para lograr una conexión a una base de datos usé la librería PyMySQL.

Es importante recalcar que esta librería sirve tanto para MySQL como para MariaDB y no hay muchas diferencias en cuanto a su uso.

Para instalarla solo es necesario:

python3 -m pip install PyMySQL

Hecho esto lo que sigue es puro código.

Importar la librería y establecer la conexión.

Claro que para que este código funcione primero hay que tener un servidor MySQL junto con un usuario y contraseña que permita una conexión.

Veamos el siguiente ejemplo:

import pymysql

# Variables generales de conexión a la base de datos.
servidor_ip = '192.168.10.40'
usuario = 'usuariodb'
contraseña = '12345678'
base_de_datos = 'world'

# Establecemos la conexión con el servidor MySQL.
db = pymysql.connect(host=servidor_ip,
                     user=usuario,
                     password=contraseña,
                     database=base_de_datos,
                     charset='utf8',
                     cursorclass=pymysql.cursors.DictCursor)

SQL entra en acción

Llegó el momento de ponerse serios, el verdadero poder de un servidor de base de datos son las consultas.

A mi me gusta trabajar las consultas con editor gráfico como HeidiSQL para luego copiarla en el código.

HeidiSQL y su editor de consultas.

Esta es una base de datos de ejemplo que guarda información de países, ciudades y sus idiomas. Es la famosa base de datos World que se puede descargar para practicar.

Supongamos que queremos una lista de países que hablan Español. La consulta quedaría más o menos así:

# El cursor nos permite trabajar con la base de datos.
cursor = db.cursor()

# SQL entra en acción.
consulta = "SELECT country.Name " + \
    "FROM country " + \
    "INNER JOIN countrylanguage ON countrylanguage.CountryCode = Code " + \
    "WHERE Language = 'Spanish';"

Los resultados de la consulta

Ahora viene lo interesante, ejecutar la consulta y guardar los resultados en una variable en la que podamos trabajar.

# Ejecutamos la consulta :)
cursor.execute(consulta)

# Y los resultados los almacenamos en la variable datos.
datos = cursor.fetchall()

print(datos)

Pero si sólo hago un print(datos) el resultado es el siguiente:

[{'Name': 'Aruba'}, {'Name': 'Andorra'}, {'Name': 'Argentina'}, {'Name': 'Belize'}, {'Name': 'Bolivia'}, {'Name': 'Canada'}, {'Name': 'Chile'}, {'Name': 'Colombia'}, {'Name': 'Costa Rica'}, {'Name': 'Cuba'}, {'Name': 'Dominican Republic'}, {'Name': 'Ecuador'}, {'Name': 'Spain'}, {'Name': 'France'}, {'Name': 'Guatemala'}, {'Name': 'Honduras'}, {'Name': 'Mexico'}, {'Name': 'Nicaragua'}, {'Name': 'Panama'}, {'Name': 'Peru'}, {'Name': 'Puerto Rico'}, {'Name': 'Paraguay'}, {'Name': 'El Salvador'}, {'Name': 'Sweden'}, {'Name': 'Uruguay'}, {'Name': 'United States'}, {'Name': 'Venezuela'}, {'Name': 'Virgin Islands, U.S.'}]

Que visualmente no es muy agradable, así que vamos a enchular un poco el resultado de la consulta recorriendo el diccionario datos.

for i in datos:
    print(i['Name'])

Que da como resultado una salida mucho más legible:

Aruba
Andorra
Argentina
Belize
Bolivia
Canada
Chile
Colombia
Costa Rica
Cuba
Dominican Republic
Ecuador
Spain
France
Guatemala
Honduras
Mexico
Nicaragua
Panama
Peru
Puerto Rico
Paraguay
El Salvador
Sweden
Uruguay
United States
Venezuela
Virgin Islands, U.S.

Cierre la puerta al salir

Claro que una vez que todo termina, es una buena idea cerrar la conexión. Para evitar que el servidor le siga asignando recursos y este atento a las peticiones.

db.close()

Todo junto

Al final todo el código sería así

import pymysql

# Variables generales de conexión a la base de datos.
servidor_ip = '192.168.10.40'
usuario = 'usuariodb'
contraseña = '12345678'
base_de_datos = 'world'

# Establecemos la conexión con el servidor MySQL.
db = pymysql.connect(host=servidor_ip,
                     user=usuario,
                     password=contraseña,
                     database=base_de_datos,
                     charset='utf8',
                     cursorclass=pymysql.cursors.DictCursor)

# El cursor nos permite trabajar con la base de datos.
cursor = db.cursor()

# SQL entra en acción.
consulta = "SELECT country.Name " + \
    "FROM country " + \
    "INNER JOIN countrylanguage ON countrylanguage.CountryCode = Code " + \
    "WHERE Language = 'Spanish' " + \
    "ORDER BY Name;"

# Ejecutamos la consulta :)
cursor.execute(consulta)

# Y los resultados los almacenamos en la variable datos.
datos = cursor.fetchall()

for i in datos:
    print(i['Name'])

# Cerrar la conexión al terminar
db.close()
El programa funcionando ¡Cooool!

Conclusiones

Como ven las posibilidades son muchas, no solamente para hacer consultas, también es posible alimentar una base de datos de diversas fuentes, archivos de texto, hojas de cálculo, incluso desde la misma terminal.

¿Qué les pareció este artículo?

¿Saben de otra forma de hacer consultas a una base de datos MySQL con Python?