Genera múltiples números aleatorios para igualar un valor en Python

Así que aquí está el trato: quiero (por ejemplo) generar 4 números pseudoaleatorios, que cuando se sumn equivaldrían a 40. ¿Cómo podría ser esta cúpula en python? Podría generar un número aleatorio 1-40, luego generar otro número entre 1 y el rest, etc., pero entonces el primer número tendría una mayor probabilidad de “agarrar” más.

b = random.randint(2, 38) a = random.randint(1, b - 1) c = random.randint(b + 1, 39) return [a, b - a, c - b, 40 - c] 

(Supongo que querías enteros ya que dijiste “1-40”, pero esto podría generalizarse fácilmente para los flotadores).

Así es como funciona:

  • cortar el rango total en dos al azar, eso es b. El rango impar es porque habrá al menos 2 debajo del punto medio y al menos 2 arriba. (Esto viene de su mínimo de 1 en cada valor).
  • Cortar cada uno de esos rangos en dos al azar. Una vez más, los límites deben tener en cuenta el mínimo de 1.
  • Devuelve el tamaño de cada rebanada. Van a sumr hasta 40.

Aquí está la solución estándar. Es similar a la respuesta de Laurence Gonsalves, pero tiene dos ventajas sobre esa respuesta.

  1. Es uniforme: cada combinación de 4 enteros positivos que sumen 40 es igualmente probable que proponga este esquema.

y

  1. es fácil de adaptar a otros totales (7 números que sumn 100, etc.)
 import random def constrained_sum_sample_pos(n, total): """Return a randomly chosen list of n positive integers summing to total. Each such list is equally likely to occur.""" dividers = sorted(random.sample(xrange(1, total), n - 1)) return [a - b for a, b in zip(dividers + [total], [0] + dividers)] 

Salidas de muestra:

 >>> constrained_sum_sample_pos(4, 40) [4, 4, 25, 7] >>> constrained_sum_sample_pos(4, 40) [9, 6, 5, 20] >>> constrained_sum_sample_pos(4, 40) [11, 2, 15, 12] >>> constrained_sum_sample_pos(4, 40) [24, 8, 3, 5] 

Explicación: hay una correspondencia de uno a uno entre (1) 4 tuplas (a, b, c, d) de enteros positivos de manera que a + b + c + d == 40 , y (2) triples de enteros (e, f, g) con 0 < e < f < g < 40 , y es fácil producir este último utilizando random.sample . La correspondencia está dada por (e, f, g) = (a, a + b, a + b + c) en una dirección, y (a, b, c, d) = (e, f - e, g - f, 40 - g) en sentido inverso.

Si desea enteros no negativos (es decir, permitir 0 ) en lugar de positivos, entonces hay una transformación fácil: si (a, b, c, d) son enteros no negativos que sumn 40 entonces (a+1, b+1, c+1, d+1) son enteros positivos que sumn 44 , y viceversa. Usando esta idea, tenemos:

 def constrained_sum_sample_nonneg(n, total): """Return a randomly chosen list of n nonnegative integers summing to total. Each such list is equally likely to occur.""" return [x - 1 for x in constrained_sum_sample_pos(n, total + n)] 

Ilustración gráfica de constrained_sum_sample_pos(4, 10) , gracias a @FM. (Editado un poco.)

 0 1 2 3 4 5 6 7 8 9 10 # The universe. | | # Place fixed dividers at 0, 10. | | | | | # Add 4 - 1 randomly chosen dividers in [1, 9] abcd # Compute the 4 differences: 2 3 4 1 

Genera 4 números aleatorios, calcula su sum, divide cada uno por la sum y multiplica por 40.

Si quieres enteros, esto requerirá un poco de no aleatoriedad.

 from numpy.random import multinomial multinomial(40, [1/4.] * 4) 

Solo hay 37 ^ 4 = 1,874,161 arreglos de cuatro enteros en el rango [1,37] (con repeticiones permitidas). Enumérelos, guardando y contando las permutaciones que sumn 40. (Este será un número mucho más pequeño, N).

Dibuje números enteros aleatorios distribuidos uniformemente K en el intervalo [0, N-1] y devuelva la permutación K-th. Esto puede verse fácilmente para garantizar una distribución uniforme en el espacio de posibles resultados, con cada posición de la secuencia distribuida de manera idéntica. (¡Muchas de las respuestas que estoy viendo tendrán la opción final sesgada más baja que las primeras tres!)

Suponiendo que desea que se distribuyan uniformemente, y suponiendo que no desea repeticiones

 addends = [] picks = range(1, 34) while sum(addends) != 40: addends = random.sample(picks, 3) if sum(addends) > 39: continue addends.append(40 - sum(addends))