Soy un principiante en programación y estoy buscando una buena idea sobre cómo generar tres números enteros que satisfagan una condición.
Ejemplo:
Nos dan n = 30 y nos piden que generemos tres enteros a, b y c, de modo que 7*a + 5*b + 3*c = n . Traté de usar bucles for , pero lleva demasiado tiempo y tengo un tiempo máximo de prueba de 1000 ms.
Estoy usando Python 3.
Mi intento:
x = int(input()) c = [] k = [] w = [] for i in range(x): for j in range(x): for h in range(x): if 7*i + 5*j + 3*h = x: c.append(i) k.append(j) w.append(h) if len(c) == len(k) == len(w) print(-1) else: print(str(k[0]) + ' ' + str(c[0]) + ' ' + str(w[0]))import numpy as np def generate_answer(n: int, low_limit:int, high_limit: int): while True: a = np.random.randint(low_limit, high_limit + 1, 1)[0] b = np.random.randint(low_limit, high_limit + 1, 1)[0] c = (n - 7 * a - 5 * b) / 3.0 if int(c) == c and low_limit <= c <= high_limit: break return a, b, int(c) if __name__ == "__main__": n = 30 ans = generate_answer(low_limit=-5, high_limit=50, n=n) assert ans[0] * 7 + ans[1] * 5 + ans[2] * 3 == n print(ans) Si selecciona dos de los números a, b, c, conoce el tercero. En este caso, aleatorizo enteros para a, b, y encuentro c por c = (n - 7 * a - 5 * b) / 3.0 .
Asegúrese de que c sea un número entero y esté dentro de los límites permitidos, y listo.
Si no es así, vuelve a aleatorizar.
Si quieres generar todas las posibilidades,
def generate_all_answers(n: int, low_limit:int, high_limit: int): results = [] for a in range(low_limit, high_limit + 1): for b in range(low_limit, high_limit + 1): c = (n - 7 * a - 5 * b) / 3.0 if int(c) == c and low_limit <= c <= high_limit: results.append((a, b, int(c))) return resultsSi se permiten bibliotecas de terceros, puede usar el solucionador de ecuaciones diophantine.diop_linear lineales diophantine.diop_linear de SymPy:
from sympy.solvers.diophantine.diophantine import diop_linear from sympy import symbols from numpy.random import randint n = 30 N = 8 # Number of solutions needed # Unknowns a, b, c = symbols('a, b, c', integer=True) # Coefficients x, y, z = 7, 5, 3 # Parameters of parametric equation of solution t_0, t_1 = symbols('t_0, t_1', integer=True) solution = diop_linear(x * a + y * b + z * c - n) if not (None in solution): for s in range(N): # -10000 and 10000 (max and min for t_0 and t_1) t_sub = [(t_0, randint(-10000, 10000)), (t_1, randint(-10000, 10000))] a_val, b_val, c_val = map(lambda t : t.subs(t_sub), solution) print('Solution #%d' % (s + 1)) print('a =', a_val, ', b =', b_val, ', c =', c_val) else: print('no solutions')Salida (aleatoria):
Solution #1 a = -141 , b = -29187 , c = 48984 Solution #2 a = -8532 , b = -68757 , c = 134513 Solution #3 a = 5034 , b = 30729 , c = -62951 Solution #4 a = 7107 , b = 76638 , c = -144303 Solution #5 a = 4587 , b = 23721 , c = -50228 Solution #6 a = -9294 , b = -106269 , c = 198811 Solution #7 a = -1572 , b = -43224 , c = 75718 Solution #8 a = 4956 , b = 68097 , c = -125049Por qué su solución no puede hacer frente a grandes valores de n
Puede comprender que todo en un bucle for con un rango de i , se ejecutará i veces. Entonces multiplicará el tiempo que toma por i .
Por ejemplo, supongamos (para mantener las cosas simples) que esto se ejecuta en 4 milisegundos:
if 7*a + 5*b + 3*c = n: c.append(a) k.append(b) w.append(c)entonces esto se ejecutará en 4 × n milisegundos:
for c in range(n): if 7*a + 5*b + 3*c = n: c.append(a) k.append(b) w.append(c)Aproximadamente:
Si coloca eso dentro de un bucle for en un rango de n , todo se repetirá n veces. Es decir
for b in range(n): for c in range(n): if 7*a + 5*b + 3*c = n: c.append(a) k.append(b) w.append(c)tomará 4n² milisegundos.
Ponerlo en un tercer ciclo for tomará 4n³ milisegundos.
Ahora, ¿qué pasa si reduce a la mitad el original if a 2 milisegundos? n podría aumentar en 15000 en el primer caso... y 23 en el último caso. La lección aquí es que un menor número de bucles for suele ser mucho más importante que acelerar lo que hay dentro de ellos. Como puede ver en la respuesta de Gulzar, parte 2, solo hay dos bucles for, lo que marca una gran diferencia. (Esto solo se aplica si los bucles están uno dentro del otro; si están uno tras otro, no tienes el problema de la multiplicación).
Primero, permítanme señalar que su tarea está subespecificada en al menos dos aspectos:
Normalmente, si no se especifica, se podría suponer que se esperaba una distribución uniforme en el conjunto de posibles soluciones de la ecuación (ya que es, en cierto sentido , la distribución más aleatoria posible en un conjunto dado). Pero una distribución uniforme (discreta) solo es posible si el conjunto de soluciones es finito, lo que no será posible si el rango de resultados no está restringido. (En particular, si ( a , b , c ) es una solución, entonces también lo es ( a , b + 3 k , c − 5 k ) para cualquier entero k ). Entonces, si interpretamos la tarea como si pidiéramos una distribución uniforme con un rango ilimitado, ¡es realmente imposible!
Por otro lado, si se nos permite elegir cualquier distribución y rango, la tarea se vuelve trivial: simplemente hacer que el generador siempre devuelva a = − n , b = n , c = n . Claramente, esta es una solución a la ecuación (ya que −7 n + 5 n + 3 n = (−7 + 5 + 3) n = 1 n ), y una distribución degenerada que asigna toda la masa de probabilidad a un solo punto sigue siendo válida ¡Distribución de probabilidad!
Si quisiera una solución un poco menos degenerada, podría elegir un número entero aleatorio k (usando cualquier distribución de su elección) y devolver a = − n , b = n + 3 k , c = n − 5 k . Como se señaló anteriormente, esta también es una solución a la ecuación para cualquier k . Por supuesto, esta distribución es todavía algo degenerada, ya que el valor de a es fijo.
Si desea que todos los valores devueltos sean al menos algo aleatorios, también puede elegir una h aleatoria y devolver a = − n + h , b = n − 2 h + 3 k y c = n + h − 5 k . Nuevamente, se garantiza que esta es una solución válida para cualquier h y k , ya que claramente satisface la ecuación para h = k = 0, y también es fácil ver que aumentar o disminuir h o k dejará el valor de la izquierda -Lado de la mano de la ecuación sin cambios.
De hecho, se puede probar que este método puede generar todas las soluciones posibles a la ecuación, y que cada solución corresponderá a un único par ( h , k )! (Una forma bastante intuitiva de ver esto es graficar las soluciones en el espacio 3D y observar que forman una red regular de puntos en un plano 2D, y que los vectores (+1, −2, +1) y (0, + 3, −5) abarcan esta red.) Si elegimos h y k de alguna distribución que (al menos en teoría) asigna una probabilidad distinta de cero a cada número entero, entonces tendremos una probabilidad distinta de cero de devolver cualquier número válido solución. Entonces, al menos para una interpretación algo razonable de la tarea (rango ilimitado, cualquier distribución con soporte completo), el siguiente código debería resolver la tarea de manera eficiente :
from random import gauss def random_solution(n): h = int(gauss(0, 1000)) # any distribution with full support on the integers will do k = int(gauss(0, 1000)) return (-n + h, n - 2*h + 3*k, n + h - 5*k)Si se restringe el rango de valores posibles, el problema se vuelve un poco más complicado. En el lado positivo, si todos los valores están acotados por debajo (o por encima), entonces el conjunto de posibles soluciones es finito, por lo que existe una distribución uniforme en él. Por otro lado, muestrear eficientemente esta distribución uniforme no es trivial.
Un enfoque posible, que ha utilizado usted mismo, es generar primero todas las soluciones posibles (suponiendo que haya un número finito de ellas) y luego tomar muestras de la lista de soluciones. Podemos hacer la generación de soluciones de manera bastante eficiente de esta manera:
La parte complicada es el paso 2, donde queremos calcular el rango de posibles valores de b . Para esto, podemos hacer uso de la observación de que, para un a dado, establecer c en su valor más pequeño permitido y resolver la ecuación da un límite superior para b (y viceversa).
En particular, resolviendo la ecuación para a , b y c respectivamente, obtenemos:
Dados los límites inferiores de algunos de los valores, podemos usar estas soluciones para calcular los límites superiores correspondientes de los demás. Por ejemplo, el siguiente código generará todas las soluciones no negativas de manera eficiente (y se puede modificar fácilmente para usar un límite inferior distinto de 0, si es necesario):
def all_nonnegative_solutions(n): a_min = b_min = c_min = 0 a_max = (n - 5*b_min - 3*c_min) // 7 for a in range(a_min, a_max + 1): b_max = (n - 7*a - 3*c_min) // 5 for b in range(b_min, b_max + 1): if (n - 7*a - 5*b) % 3 == 0: c = (n - 7*a - 5*b) // 3 yield (a, b, c)Luego podemos almacenar las soluciones en una lista o una tupla y tomar muestras de esa lista :
from random import choice solutions = tuple(all_nonnegative_solutions(30)) a, b, c = choice(solutions) PD. Aparentemente, la random.choice de Python no es lo suficientemente inteligente como para usar el muestreo de reservorio para tomar muestras de un iterable arbitrario, por lo que necesitamos almacenar la lista completa de soluciones incluso si solo queremos tomar muestras una vez. O, por supuesto, siempre podríamos implementar nuestro propio sampler :
def reservoir_choice(iterable): r = None n = 0 for x in iterable: n += 1 if randrange(n) == 0: r = x return r a, b, c = reservoir_choice(all_nonnegative_solutions(30)) Por cierto, podríamos hacer que la función all_nonnegative_solutions de arriba sea un poco más eficiente al observar que la condición (n - 7*a - 5*b) % 3 == 0 (que verifica si c = ( n − 7 a − 5 b ) / 3 es un número entero y, por lo tanto, una solución válida) es cierto para cada tercer valor de b . Por lo tanto, si primero calculamos el valor más pequeño de b que satisface la condición para un a dado (lo que se puede hacer con un poco de aritmética modular ), podríamos iterar sobre b con un tamaño de paso de 3 a partir de ese valor mínimo y omitir la comprobación de divisibilidad por completo. Dejaré la implementación de esa optimización como un ejercicio.
desde mi perspectiva, el último número de los tres nunca es un número aleatorio. digamos que primero genera a y b , luego c nunca es aleatorio porque debe calcularse a partir de la ecuación
n = 7*a + 5*b + 3*c c = (7*a + 5*b - n) / -3 esto significa que necesitamos generar dos valores aleatorios (a,b) que 7*a + 5*b - n es divisible por 3
import random n = 30; max = 1000000; min = -1000000; while True: a = random.randint(min , max); b = random.randint(min , max); t = (7*a) + (5*b) - n; if (t % 3 == 0) : break; c = (t/-3); print("A = " + str(a)); print("B = " + str(b)); print("C = " + str(c)); print("7A + 5B + 3C =>") print("(7 * " + str(a) + ") + (5 * " + str(b) + ") + (3 * " + str(c) + ") = ") print((7*a) + (5*b) + (3*c));