DEV Community

Ternas pitagóricas

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²
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

¿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))
Enter fullscreen mode Exit fullscreen mode

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)]))
Enter fullscreen mode Exit fullscreen mode

¡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}%")
Enter fullscreen mode Exit fullscreen mode

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%
Enter fullscreen mode Exit fullscreen mode

¡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}")
Enter fullscreen mode Exit fullscreen mode

Algunas salidas fueron las siguientes:

# Pythagorean triads in random list
[(3, 4, 5), (24, 7, 25)]
Percentage: 01.98%
Repetitions: 13
Enter fullscreen mode Exit fullscreen mode
# Pythagorean triads in random list
[(3, 4, 5), (13, 84, 85)]
Percentage: 01.98%
Repetitions: 129
Enter fullscreen mode Exit fullscreen mode
# Pythagorean triads in random list
[(3, 4, 5), (48, 55, 73)]
Percentage: 01.98%
Repetitions: 47
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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²
Enter fullscreen mode Exit fullscreen mode

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]))
    ...
...
Enter fullscreen mode Exit fullscreen mode

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%
Enter fullscreen mode Exit fullscreen mode

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)