Me gusta jugar y experimentar con conceptos matemáticos. Es decir, con conceptos matemáticos simples. Lo mío, al fin y al cabo, son los aparatos con teclado. Los que se pueden aporrear, vamos.
Todos conocemos el teorema de Pitágoras, según el cual la suma del cuadrado de los catetos a y b, es igual a la hipotenusa c al cuadrado.
c² = a² + b²
Podemos coleccionar triángulos de este estilo mediante las ternas pitagóricas. Una terna pitagórica es un vector v de tres elementos en el que se cumple la ecuación anterior. Si lo trasladamos a código Python (esta es la nuestra), lo que tendríamos es lo siguiente:
ternas = []
a = #...
b = #...
c = #...
ternas += (a, b, c)
¿Qué ponemos donde los comentarios con puntos suspensivos? ¿Cómo podemos crear una terna, sabiendo de antemano que los valores formarán una auténtica terna pitagórica?
Es fácil comprobar que una terna es una terna pitagórica. Por ejemplo, dada una lista de ternas como la anterior, podemos obtener cuáles de ellas son pitagóricas con una función como la siguiente:
def filter_real_triads(l):
return list(filter(lambda t: ((t[0] ** 2) + (t[1] ** 2) == (t[2] ** 2)), l))
Lo que no es tan fácil es lo que nos preguntábamos más arriba. ¿Cómo crear una terna pitagórica? Algo que se puede intentar es crearlas al azar. Al fin y al cabo, solo son tres números.
def crea_random_triads(l, max):
for _ in range(max):
l.append(tuple([rnd(1, max), rnd(1, max), rnd(1, max)]))
¡Esto es emocionante! ¿Cuántas ternas obtendremos de esta manera?
from random import randint as rnd
import random
random.seed()
l1 = [(3, 4, 5)]
max = 100
print("\n# Random triads:")
crea_random_triads(l1, max)
print(format(l1))
print("\n# Pythagorean triads in l1")
pl1 = filter_real_triads(l1)
print(f"{pl1}\nPercentage: {(len(pl1)/len(l1)) * 100:05.2f}%")
Si ejecutamos el programa, la salida del mismo será la siguiente:
# Random triads:
[(3, 4, 5), (48, 64, 100), (72, 98, 69), (70, 19, 78), (98, 54, 42), (77, 47, 18), (99, 80, 9), (69, 55, 12), (61, 30, 8), (23, 60, 25), (29, 37, 40), (98, 1, 44), (13, 2, 97), (15, 54, 65), (80, 85, 28), (8, 100, 62), (4, 29, 66), (72, 21, 64), (1, 78, 39), (25, 16, 64), (17, 82, 24), (72, 7, 94), (35, 84, 68), (4, 52, 59), (74, 40, 100), (55, 30, 55), (82, 46, 96), (49, 42, 50), (30, 8, 4), (39, 36, 52), (80, 16, 34), (63, 82, 98), (30, 61, 11), (48, 63, 94), (36, 71, 7), (60, 52, 76), (34, 82, 40), (82, 12, 31), (51, 82, 35), (73, 59, 89), (44, 76, 47), (25, 4, 58), (80, 46, 63), (96, 8, 45), (42, 39, 35), (24, 36, 82), (37, 93, 42), (69, 85, 51), (69, 88, 61), (60, 98, 100), (17, 41, 90), (56, 36, 95), (58, 40, 29), (62, 67, 85), (58, 11, 98), (89, 86, 31), (30, 89, 35), (45, 8, 66), (9, 47, 40), (90, 83, 82), (19, 88, 14), (35, 19, 95), (2, 63, 22), (7, 99, 83), (15, 2, 10), (93, 19, 69), (6, 81, 98), (55, 88, 78), (87, 89, 40), (35, 74, 35), (33, 99, 38), (58, 43, 87), (86, 54, 88), (89, 78, 54), (19, 32, 32), (81, 92, 22), (100, 53, 5), (62, 48, 98), (51, 76, 75), (98, 42, 46), (92, 74, 21), (15, 48, 90), (11, 85, 59), (82, 52, 16), (48, 58, 52), (26, 37, 12), (75, 52, 50), (35, 77, 13), (10, 67, 96), (90, 60, 43), (98, 75, 24), (16, 11, 81), (39, 5, 47), (94, 35, 52), (70, 56, 97), (66, 62, 26), (17, 61, 22), (41, 47, 92), (41, 4, 28), (68, 4, 20), (89, 98, 38)]
# Pythagorean triads in l1
[(3, 4, 5)]
Percentage: 00.99%
¡Madre mía, qué desastre! Solo la de prueba, la de base, es una terna pitagórica. He probado a repetir varias veces la ejecución, sin mayores resultados.
Decidí cambiar un poco el algoritmo. ¿Qué pasa si repetimos hasta tener al menos una generada? ¿Se bloqueará el ordenador buscando la relación que nunca existirá?
def crea_random_triad(max):
return tuple([rnd(1, max), rnd(1, max), rnd(1, max)])
...
if __name__ == "__main__":
max = 100
random.seed()
print("\n# Random triads:")
l1 = []
pl1 = []
repetitions = 0
while len(pl1) < 2:
l1 = [(3, 4, 5)]
crea_random_triads(l1, max)
pl1 = filter_real_triads(l1)
repetitions += 1
...
print(format(l1))
print("\n# Pythagorean triads in random list")
print(f"{pl1}\nPercentage: {(len(pl1)/len(l1)) * 100:05.2f}%")
print(f"Repetitions: {repetitions}")
Algunas salidas fueron las siguientes:
# Pythagorean triads in random list
[(3, 4, 5), (24, 7, 25)]
Percentage: 01.98%
Repetitions: 13
# Pythagorean triads in random list
[(3, 4, 5), (13, 84, 85)]
Percentage: 01.98%
Repetitions: 129
# Pythagorean triads in random list
[(3, 4, 5), (48, 55, 73)]
Percentage: 01.98%
Repetitions: 47
Se pueden obtener algunos resultados. El algoritmo termina en cuanto encuentra al menos una, en algunas ocasiones tras iterar cientos de veces. Vamos a cambiarlo de nuevo:
def crea_random_triad(max):
return tuple([rnd(1, max), rnd(1, max), rnd(1, max)])
...
if __name__ == "__main__":
max = 100
random.seed()
print("\n# Random triads:")
l1 = [(3, 4, 5)]
pl1 = list(l1)
repetitions = 0
while repetitions < max**2:
t = crea_random_triad(max)
l1.append(t)
if len(filter_real_triads([t])) > 0:
pl1.append(t)
...
repetitions += 1
...
print(l1)
print("\n# Pythagorean triads in random list")
print(f"{pl1}\nPercentage: {(len(pl1)/len(l1)) * 100:05.2f}%")
print(f"Repetitions: {repetitions}")
La salida ahora es:
# Random triads:
[(3,4,5), (92, 94, 23), (90, 95, 9)...]
# Pythagorean triads in random list
[(3, 4, 5), (39, 80, 89), (15, 20, 25), (24, 32, 40)]
Percentage: 00.04%
Repetitions: 10000
Dado que en cada repetición se crea una nueva terna, de 10,000 ternas acabamos con 4, un 0.04% del total. Y eso que una, la dábamos de partida. He ejecutado el programa varias veces, y en ocasiones solo encuentra una, dos, o incluso ninguna (aparte de (3, 4, 5), que ya viene dada). Es decir, abordándolo por fuerza bruta, podemos obtener resultados, pero con un coste computacional elevado.
Resulta que este problema ya lo trató Euclides. Como él no tenía ordenadores que son básicamente tontos, y no se preguntan nada cuando les pides que repitan 10,000 veces la creación de unos números aleatorios, con el objetivo de crear una terna pitagórica, y además él era demasiado inteligente como para confiar en la fuerza bruta, acabó creando un sencillo algoritmo para la creación de ternas pitagóricas:
Si m > n son enteros positivos, entonces:
a = m² − n²
b = 2mn
c = m² + n²
Podemos traducir este pequeño algoritmo a Python muy facilmente:
def crea_semi_random_triads(l, max):
for _ in range(max):
a = -1
b = 0
while a < b:
a = rnd(1, max)
b = rnd(1, max)
...
x = (a ** 2) - (b ** 2)
y = 2 * a * b
z = (a ** 2) + (b ** 2)
l.append(tuple([x, y, z]))
...
...
La salida ahora es la siguiente:
# Pythagorean triads in semi-random list
[(3, 4, 5), (2656, 13650, 13906), (6400, 6942, 9442), (3744, 11880, 12456), (896, 1440, 1696), (2697, 3304, 4265), (111, 680, 689), (2793, 424, 2825), (1313, 5016, 5185), (5029, 4620, 6829), (1496, 390, 1546), (91, 4140, 4141), (1512, 5734, 5930), (5568, 2926, 6290), (2295, 8968, 9257), (5513, 10416, 11785), (2873, 14136, 14425), (533, 756, 925), (8715, 2068, 8957), (2511, 3960, 4689), (640, 312, 712), (264, 950, 986), (4620, 11408, 12308), (6909, 8740, 11141), (0, 10658, 10658), (1920, 7072, 7328), (4288, 8466, 9490), (3135, 112, 3137), (160, 168, 232), (0, 15488, 15488), (3355, 348, 3373), (639, 2480, 2561), (2289, 5720, 6161), (1995, 1012, 2237), (3213, 11484, 11925), (2576, 510, 2626), (4672, 10146, 11170), (936, 12150, 12186), (2793, 10624, 10985), (7544, 870, 7594), (420, 352, 548), (6912, 3784, 7880), (696, 15130, 15146), (1652, 6864, 7060), (1083, 1444, 1805), (357, 7076, 7085), (3432, 8374, 9050), (6497, 1296, 6625), (1140, 272, 1172), (2024, 3990, 4474), (688, 3666, 3730), (4635, 4292, 6317), (936, 1190, 1514), (245, 1188, 1213), (1368, 74, 1370), (315, 572, 653), (2816, 3360, 4384), (255, 3608, 3617), (4560, 6478, 7922), (1360, 222, 1378), (3161, 5520, 6361), (6901, 3060, 7549), (4747, 3996, 6205), (576, 4590, 4626), (309, 5300, 5309), (1175, 792, 1417), (5900, 5712, 8212), (4495, 11592, 12433), (3128, 3654, 4810), (255, 1288, 1313), (48, 64, 80), (1127, 936, 1465), (4320, 5032, 6632), (3344, 3150, 4594), (21, 20, 29), (5699, 8820, 10501), (5655, 1672, 5897), (0, 10082, 10082), (0, 5618, 5618), (201, 2240, 2249), (572, 96, 580), (161, 12960, 12961), (5777, 4536, 7345), (255, 3608, 3617), (768, 224, 800), (2835, 8892, 9333), (1792, 1656, 2440), (632, 12474, 12490), (256, 8190, 8194), (1800, 1350, 2250), (2117, 2244, 3085), (7056, 2210, 7394), (4144, 10560, 11344), (5559, 4640, 7241), (2397, 9796, 10085), (2079, 4680, 5121), (2709, 8100, 8541), (4636, 6720, 8164), (2700, 3600, 4500), (1449, 2160, 2601), (6716, 9600, 11716)]
Percentage: 100.00%
Efectivamente, ahora obtenemos un 100% de eficiencia. Euclides era un fenómeno.
Puedes encontrar el código fuente de este algoritmo de ternas pitagóricas en IDEOne.
¿Qué sentido tiene esto? No lo sé. ¿Importa?
Top comments (0)