Mostrando entradas con la etiqueta azar. Mostrar todas las entradas
Mostrando entradas con la etiqueta azar. Mostrar todas las entradas

martes, 16 de junio de 2026

Matrices cuadradas aleatorias con determinante 1 o -1

Objetivo: generar una matriz cuadrada de números enteros aleatorios cuyo determinante sea 1 o -1.

Una primera idea puede ser realizar un bucle en el que generamos una matriz cuadrada de números enteros aleatorios hasta que se satisfaga la condición de que su determinante sea 1 o -1. El problema estaría resuelto, pero el algoritmo no es muy eficiente.

Voy a proponer a continuación otra solución. 

Problema previo: generar una matriz triangular de números enteros aleatorios cuyo determinante sea 1 o -1.

Este problema es muy sencillo teniendo en cuenta que el determinante de una matriz triangular es igual al producto de los elementos de su diagonal.

Únicamente necesitamos rellenar la diagonal con elementos del conjunto {-1, 1}, ceros por encima (o por debajo) de la diagonal y números enteros aleatorios en el resto de elementos de la matriz. 

Ejemplo de función en Python que calcula una matriz triangular de enteros aleatorios (entre -10 y 10, ambos incluidos) utilizando la librería NumPy.

import numpy as np
# Función para crear una matriz triangular de enteros con determinante 1 o -1
# n es la dimensión de la matriz
def matriz_det1(n):
    # Se crea una matriz M_azar con todos sus elementos aleatorios
    M_azar = np.random.randint(-10, 11, size=(n, n))
    # Se elige al azar si se construye una matriz triangular superior o inferior
    if np.random.randint(2):
        # Triangular superior: se cogen los elementos de M_azar por encima de la diagonal
        # con el resto de elementos iguales a 0 y se le suma una matriz diagonal con elementos 1 y -1
        MU = np.triu(M_azar, k=1) + np.diag(np.random.choice([-1, 1], size=n))
    else:
        # Triangular inferior: se cogen los elementos de M_azar por debajo de la diagonal
        # con el resto de elementos iguales a 0 y se le suma una matriz diagonal con elementos 1 y -1
        MU = np.tril(M_azar, k=-1)+ np.diag(np.random.choice([-1, 1], size=n))
    return MU
 

Ejemplo de matriz (10x10) creada con dicho algoritmo:

 

Teniendo resuelto el problema previo, ahora podemos abordar el problema original. Las matrices resultantes del algoritmo anterior tienen el "inconveniente" de no ser lo suficientemente aleatorias, dado que son triangulares y tienen demasiados elementos 0 no aleatorios.

Sin embargo, sabemos que: 

- El determinante del producto de matrices es igual al producto de los determinates (|A·B|=|A|·|B|). Por tanto, si multiplicamos matrices cuyo determinante es 1 o -1 el resultado será una matriz cuyo determinante es 1 o -1.

- El producto de matrices triangulares no tiene porqué ser una matriz triangular.

Utilizando esto, podemos idear un algoritmo para el objetivo principal que consista en crear varias matrices triangulares resultantes del algoritmo anterior y multiplicarlas:

# Función para crear una matriz con determinante 1 o -1
# mediante la multiplicación de matrices triangulares
# n es la dimensión de la matriz y m el número de iteraciones
def genU(n, m):
    # Comenzamos con la matriz identidad
    U = np.eye(n, dtype=int)
    # Creamos "m" matrices triangulares con determinante 1 o -1 y las multiplicamos
    for k in range(m):
        U = U @ matriz_det1(n)
    return U

 Ejemplo de matriz creada con dicho algoritmo (n=5, m=10):

 

 

 


viernes, 1 de agosto de 2025

Sacar la bola "n" en la extracción número "n" (soluciones y sorpresa matemática)

En una entrada anterior se planteaba el siguiente problema:

Tenemos una urna con 5 bolas numeradas del 1 al 5. Hay que sacarlas de una en una al azar, sin mirar. Se pierde el juego si sacamos la bola "n" en la extracción número "n".


Abordar dicho problema con un enfoque de probabilidad clásica realizando el diagrama de árbol es factible pero muy tedioso, teniendo en cuenta que con 5 bolas el árbol completo tiene 5! = 120 ramas. En realidad, dicho proceso no dista mucho de calcular todas las permutaciones y comprobar cuáles cumplen las condiciones (método exhaustivo).

Si aumentamos el número de bolas en la urna olvidémonos del método exhaustivo manual. Podemos utilizar programación para que realice dicho método exhaustivo. El problema es el tiempo de computación. Para hacernos una idea, con 13 bolas en la urna tenemos 6.227.020.800 permutaciones diferentes que crear y comprobar si cumplen o no la condición del enunciado (aproximadamente 52 minutos con mi ordenador).

Muchos problemas no tienen mucho interés desde el punto de vista algorítmico-teórico porque es fácil definir el algoritmo que resuelve el problema, pero llevado a la práctica con los ordenadores de los que disponemos actualmente el tiempo de computación de dicho algoritmo puede hacer inviable su resolución en un tiempo "razonable". Y esto obliga a tener que optimizar los algoritmos o buscar alternativas a los mismos.

Volviendo al problema, he creado un programa en Python que resuelve la generalización del problema con N bolas. Como se planteaba al final de la entrada anterior.

He utilizado dos métodos. Empiezo por el segundo, método exhaustivo. Se calculan todas las permutaciones con la librería itertools de Python y se recorren comprobando si cumplen o no la condición de que ninguna bola salga en la posición que indica su número. Para N pequeño (<10) el tiempo de ejecución es razonable, no así conforme se aumenta el número de bolas.

El primer método utilizado es matemáticamente mucho más interesante. Hace uso del concepto de desarreglo, que expliqué en la entrada anterior.

No es difícil ver la relación de los desarreglos con el problema planteado de las bolas numeradas. Que ninguna bola salga en el orden que indica su número equivale a decir que en la permutación del conjunto {1, 2, 3, 4, 5} ningún elemento coincida con su posición original. Por tanto, si calculamos el número de desarreglos del conjunto {1, 2, ..., N} la probabilidad de ganar el juego será: número de desarreglos / permutaciones totales. Es decir, el subfactorial dividido entre el factorial.

p(ganar juego con N bolas) = !N / N! 

Una fórmula muy bonita y computacionalmente mucho más rápida que el método exhaustivo.

Nota: Para calcular el factorial y el subfactorial en Python he utilizado la librería SymPy.

 

En el caso del problema original, N = 5, la probabilidad de ganar es aproximadamente 0.36666666666666664. Por lo que tenemos la probabilidad cuantificada y podemos decir que es más fácil perder el juego que ganarlo.

Además se planteaba la pregunta de si nos parecía lucrativo que nos doblaran la apuesta en caso de ganar, teniendo en cuenta el riesgo que se asume. La respuesta es negativa, dado que a la larga ganaremos aproximadamente un 36'67% de las veces que juguemos y si sólo nos doblan la apuesta lo más probable es acabar perdiendo dinero en dicho juego.

Por último, se planteaba la siguiente pregunta:

Conforme aumentamos el número de bolas, ganar el juego ¿es más fácil, más difícil o va variando?

He creado otro programa en Python que devuelve las probabilidades de ganar el juego variando el número de bolas. El resultado obtenido se recoge en la siguiente tabla: 

N Probabilidad de ganar
2 0.5
3 0.3333333333333333
4 0.375
5 0.36666666666666664
6 0.3680555555555556
7 0.3678571428571429
8 0.36788194444444444
9 0.36787918871252206
10 0.3678794642857143
11 0.3678794392336059
12 0.3678794413212816
13 0.36787944116069116
14 0.3678794411721619
15 0.3678794411713972
16 0.367879441171445
17 0.36787944117144217
18 0.36787944117144233
19 0.36787944117144233
20 0.36787944117144233
21 0.36787944117144233
22 0.36787944117144233
23 0.36787944117144233
24 0.36787944117144233
25 0.36787944117144233
26 0.36787944117144233
27 0.36787944117144233
28 0.36787944117144233
29 0.36787944117144233
30 0.36787944117144233

Vemos que en las primeras N la probabilidad una veces sube y otras baja pero que conforme aumenta N la probabilidad se va estabilizando en torno a un valor: 0.36787944117144233. Podríamos decir que a nivel práctico a partir de 5 bolas la probabilidad de ganar el juego no cambia demasiado.

Y aquí podría acabar esta entrada. Pero las matemáticas están repletas de conexiones sorprendentes y en ocasiones inesperadas. ¿Alguien al leer el problema de las bolas numeradas pensó en el famoso número e?

Pues aquí va su aparición estelar: ese número al cual tiende la probabilidad conforme aumentamos el número de bolas, 0.3678794411714423..., es exactamente el inverso del número e.

1/e =  0.3678794411714423...

Dejo como ejercicio a quien quiera la explicación de este hecho (pista: mirar en la entrada anterior la fórmula del subfactorial).

miércoles, 30 de julio de 2025

Sacar la bola "n" en la extracción número "n" (enunciado y preguntas)

 


Tenemos una urna con 5 bolas numeradas del 1 al 5. Hay que sacarlas de una en una al azar, sin mirar. Se pierde el juego si sacamos la bola "n" en la extracción número "n".

 

Ejemplo de victoria

Primera bola: 2

Segunda bola: 5

Tercera bola: 1

Cuarta bola: 3

Quinta bola: 4 

Ninguna bola se ha extraído en la posición que indica su número. Ganamos el juego.

 

Ejemplo de derrota

Primera bola: 3

Segunda bola: 1

Tercera bola: 5

Cuarta bola: 4

Aquí coincide el número de la bola con la posición en la que se ha extraído. Ya no hace falta seguir. Hemos perdido.

 

Antes de realizar ningún cálculo, contesta a las siguientes preguntas según tu intuición.

- ¿Es fácil ganar el juego?

- Te dicen que si ganas el juego te devuelven el doble de la apuesta realizada. ¿Te parece lucrativo en base al riesgo que asumes?

 

Ahora cuantifica la probabilidad de ganar y vuelve a contestar las preguntas anteriores.

Puedes compartir tu solución en los comentarios.

 

Más preguntas para pensar.

- Generalizar el problema para "n" bolas. 

- Conforme aumentamos el número de bolas, ganar el juego ¿es más fácil, más difícil o va variando?

sábado, 12 de abril de 2025

Problema de los cartones de bingo especiales: una solución con Python

 En la entrada anterior puse un problema en el que teníamos que contar cuántos cartones de bingo diferentes podíamos construir siguiendo unas reglas dadas: sin que se repita ninguna fila, columna o diagonal.

 Que ninguna pareja de cartones tenga alguna fila igual se entiende perfectamente. Lo mismo con columnas. Pero, ¿qué se entiende por diagonal del cartón? Si vemos el cartón como una matriz de 5x5, ¿hablamos sólo de la diagonal principal de la matriz? ¿también la diagonal secundaria (antidiagonal)? En este sentido el enunciado me genera dudas de interpretación. Así que desarrollaré un programa que pueda contestar a la pregunta interpretando ambas opciones.

El código en Python puede encontrarse en el repositorio de Matemática Recreativa que creé en GitHub.

Si queremos quitar la condición de la antidiagonal podemos comentar las líneas:

    if antidiagonal in antidiagonales:
        continue

 De hecho, el resultado no varía. Pero es que tampoco lo hace si quitamos la condición de las diagonales. Y aún más "sorprendente" es que tampoco varía si quitamos la condición de que no pueda haber dos matrices con alguna fila común.

A Deep Dive Into Iterators and Itertools in Python - YouTube
Imagen extraída de https://www.youtube.com/watch?v=aumxFs2DO5o

 Pero eso no lo explica el algoritmo, sino que lo hacen las Matemáticas. La columna con menos combinaciones es la tercera, dado que hay una casilla negra a la que no hay que asignarle un número. Como en dicha columna podemos poner números en orden ascendente desde el 31 al 45 incluidos y sin poder repetirlos, tenemos el número combinatorio 15C4 que da 1365. Por tanto, tenemos una cota superior del número de soluciones al problema dado que podríamos tener cartones/matrices dentro de esas combinaciones con filas, diagonales o antidiagonales en común.

 Lo que pasa es que la solución al problema es exactamente 1365. ¿Por qué la restricción de la tercera columna es la más restrictiva sobre las condiciones de filas, diagonal y antidiagonal? Es decir, ¿por qué podemos asegurar que para cada una de las 1365 combinaciones para la tercera columna podemos rellenar un cartón completo que no comparta alguna fila, diagonal ni antidiagonal con el resto de cartones?

 La justificación no me parece tan fácil como podría pensarse en un principio. ¿Cuál es tu justificación?

jueves, 3 de abril de 2025

Problema de Matemática Recreativa: cartones de bingo especiales

En entradas anteriores (1, 2) he resuelto problemas contenidos en el libro Ludopatía Matemática, de Mariano Mataix Lorda.

Imagen extraída de goodreads.com

El problema 19 se titula Cartones de bingo.

"La figura representa un cartón de bingo algo diferente de los acostumbrados, pero para el caso es igual. Los números de la primera columna se toman de los que existen entre el 1 y el 15, cambos comprendidos; los de la 2.ª, entre el 16 y el 30; los de la 3.ª, entre el 31 y el 45; los de la 4.ª, entre el 46 y el 60; y los de la 5.ª, entre el 61 y el 75.

¿Cuál es el máximo número de tarjetas que pueden hacerse sin que se repita ninguna fila, columna o diagonal?

NOTA: Hay que tener en cuenta que en cada columna el orden de los números va de acuerdo con su magnitud. Es decir, que si, por ejemplo, los números elegidos para la primera columna fuesen 1, 2, 3, 4 y 5, irían en este orden de arriba a abajo, no pudiendo ir nunca un número antes de otro menor que él."

En la próxima entrada pondré mi solución utilitzando Pyhton para continuar con la serie de problemas de matemática recreativa resueltos con programación informática.


jueves, 30 de enero de 2025

Un problema de criptoaritmética diferente

 En la anterior entrada hablé sobre los criptoaritmos y cité el libro Ludopatía Matemática, de Mariano Mataix Lorda.

 En dicho libro aparece un criptoaritmo diferente a los habituales. Se trata del siguiente problema:

 Resulta que entre TEN y TWENTY hay ONE cuadrados perfectos. Por otra parte, TWO, TEN, TWELVE y TWENTY son pares, con la particularidad de que tanto el último como el primer dígito de TWENTY son pares. Por último, TEN no es divisible por 3. ¿Cuánto vale NOW?

Números cuadrados. Serie matemática para niños
Imagen extraída de www.conmishijos.com

  Aquí está mi propuesta de resolución con Python, utilizando la estrategia del método exhaustivo (comprobar todas las posibles soluciones).

 

 

lunes, 13 de enero de 2025

Criptoaritmos con Python

El otro día abrí el libro Ludopatía Matemática, de Mariano Mataix Lorda. En este libro se pueden encontrar diferentes pasatiempos, juegos y curiosidades relacionadas con las matemáticas.

Ludopatía matemática by Mariano Mataix Lorda | Goodreads
Imagen extraída de goodreads.com

 

Algunos de ellos son los conocidos criptoaritmos. Podemos decir, de manera simple, que un criptoaritmo (o criptograma aritmético) es una operación aritmética en la que han cambiado los dígitos de los números implicados en la operación por letras y debemos averiguar qué dígito le corresponde a cada letra para que la operación se cumpla.

Por ejemplo (número 1 del citado libro y que aparece en la portada):

Las cifras han sido cambiadas en la siguiente suma
    YZRM
    BRCP
    TPRM
    BTCP
    XLXX

 Las condiciones más habituales en este juego son las siguientes:

- Cada letra corresponde a un dígito diferente.
- Por tanto, no puede haber más de 10 letras diferentes.
- El primer carácter de cada número no puede equivaler a 0.

Hay diferentes maneras de plantear matemáticamente estos problemas, pero hoy no quiero entrar en esa parte sino en que es un problema que puede resolverse "fácilmente" probando posibilidades hasta que una cuadre.

https://hips.hearstapps.com/hmg-prod/images/una-mente-maravillosa-1552554342.jpeg?crop=1.00xw:0.893xh;0,0.0406xh&resize=1200:*
Imagen extraída de fotogramas.es

¿Cómo vamos a probar posibilidades hasta encontrar la correcta? ¿Eso no es demasiado trabajo? Bueno, tampoco hace falta que las probemos manualmente. Es decir, podemos desarrollar un programa que compruebe todas las soluciones por nosotros. Y, además, que nos sirva no sólo para resolver el criptoaritmo del ejemplo sino cualquier criptoaritmo que cumpla las condiciones dadas.

Y ya que estamos, que no sólo resuelva criptoaritmos de sumas sino también criptoaritmos que utilicen suma, resta, multiplicación y/o división.

La Mar de Mates: Signos de las operaciones matemáticas básicas
Imagen extraída de lamardemates.blogspot.com


Para ello he desarrollado en Python dos versiones de un "resolutor de criptoaritmos", siguiendo dos estrategias:

- Una que he llamado probabilística: consiste en ir asignando al azar valores a las letras hasta encontrar la que hace que se cumpla la operación. Código aquí.

- Otra que he llamado exhaustiva: consiste en ir comprobando todas las posibles asignaciones de valores a las letras hasta encontrar la que hace que se cumpla la operación. Código aquí.

Nota: a nivel de programación, en los códigos se pueden mejorar varias cosas, pero para el objetivo que persigo en esta entrada lo doy por bueno.

¿Qué ventajas puede tener la estrategia probabilística?

- No es necesario programar una estrategia de combinatoria.

- Puede haber suerte y que al azar encuentre la solución más rápido.

¿Qué desventajas tiene?

- Si no tiene solución entramos en un bucle infinito (se puede poner un número máximo de bucles y decir que es probable que no tenga solución).

- Puede repetir más de una vez la misma combinación, por lo que estaría haciendo cálculos repetidos innecesarios.

- Igual que puede haber suerte y encontrar rápido la solución, también puede tardar más en encontrar la solución. Es una cuestión de azar.

Arte Pop Vintage Cruzar Los Dedos Sesión. Gran Ejemplo De Arte Pop Del  Estilo Del Cómic Fingers Crossed Muestra De La Mano Haciendo Un Gesto De  Buena Suerte Y Fortuna. Ilustraciones svg,
Imagen extraída de es.123rf.com

¿Por qué es útil entonces conocer la estrategia probabilística? Porque en ocasiones no conocemos el patrón que rige el problema, no sabemos implementar correctamente la estrategia exhaustiva o no tenemos tiempo/ganas para implementarla. No me refiero sólo a criptoarimética, sino como estrategia general de resolución de problemas mediante computación.