¿Hay una manera eficiente de generar una combinación aleatoria de N enteros de tal manera que:
- cada entero está en el intervalo [
min
,max
], - los enteros tienen una suma de
sum
, - los enteros pueden aparecer en cualquier orden (p. ej., orden aleatorio) y
- la combinación se elige de manera uniforme al azar entre todas las combinaciones que cumplen con los otros requisitos?
¿Existe un algoritmo similar para combinaciones aleatorias en el que los enteros deben aparecer en orden ordenado por sus valores (en lugar de en cualquier orden)?
(Elegir una combinación apropiada con una media de mean
es un caso especial, si sum = N * mean
. Este problema es equivalente a generar una partición aleatoria uniforme de sum
N partes que están en el intervalo [ min
, max
] y aparecen en cualquier orden o en orden ordenado por su valores, según sea el caso.)
Soy consciente de que este problema se puede resolver de la siguiente manera para combinaciones que aparecen en orden aleatorio (EDIT [27 de abril]: Algoritmo modificado):
Si
N * max < sum
oN * min > sum
, no hay solución.Si
N * max == sum
, solo hay una solución, en la que todos losN
números son igualesmax
. SiN * min == sum
, solo hay una solución, en la que todos losN
números son igualesmin
.Utilice el algoritmo proporcionado en Smith y Tromble ("Muestreo de la unidad simple", 2004) para generar N enteros no negativos aleatorios con la suma
sum - N * min
.Agregue
min
a cada número generado de esta manera.Si cualquier número es mayor que
max
, vaya al paso 3.
Sin embargo, este algoritmo es lento si max
es mucho menor que sum
. Por ejemplo, de acuerdo con mis pruebas (con una implementación del caso especial mencionado anteriormente mean
), el algoritmo rechaza, en promedio:
- alrededor de 1.6 muestras si
N = 7, min = 3, max = 10, sum = 42
, pero - aproximadamente 30,6 muestras si
N = 20, min = 3, max = 10, sum = 120
.
¿Hay alguna forma de modificar este algoritmo para que sea eficiente para N grande mientras se cumplen los requisitos anteriores?
EDITAR:
Como alternativa sugerida en los comentarios, una forma eficiente de producir una combinación aleatoria válida (que satisfaga todos los requisitos excepto el último) es:
- Calcular
X
, el número de combinaciones válidas posible dadasum
,min
ymax
. - Elija
Y
, un entero aleatorio uniforme en[0, X)
. - Convierta ("sin carga")
Y
en una combinación válida.
Sin embargo, ¿existe una fórmula para calcular el número de combinaciones válidas (o permutaciones), y hay alguna forma de convertir un número entero en una combinación válida? [EDITAR (28 de abril): lo mismo para las permutaciones que para las combinaciones].
EDITAR (27 de abril):
Después de leer la generación de varianza aleatoria no uniforme de Devroye (1986), puedo confirmar que este es un problema de generar una partición aleatoria. Además, el ejercicio 2 (especialmente la parte E) en la página 661 es relevante para esta pregunta.
EDITAR (28 de abril):
Al final resultó que el algoritmo que di es uniforme, donde los enteros involucrados se dan en orden aleatorio , en lugar de ordenarlos por sus valores . Como ambos problemas son de interés general, he modificado esta pregunta para buscar una respuesta canónica para ambos problemas.
El siguiente código Ruby se puede usar para verificar posibles soluciones para la uniformidad (donde algorithm(...)
está el algoritmo candidato):
combos={}
permus={}
mn=0
mx=6
sum=12
for x in mn..mx
for y in mn..mx
for z in mn..mx
if x+y+z==sum
permus[[x,y,z]]=0
end
if x+y+z==sum and x<=y and y<=z
combos[[x,y,z]]=0
end
end
end
end
3000.times {|x|
f=algorithm(3,sum,mn,mx)
combos[f.sort]+=1
permus[f]+=1
}
p combos
p permus
EDITAR (29 de abril): se volvió a agregar el código Ruby de la implementación actual.
El siguiente ejemplo de código se da en Ruby, pero mi pregunta es independiente del lenguaje de programación:
def posintwithsum(n, total)
raise if n <= 0 or total <=0
ls = [0]
ret = []
while ls.length < n
c = 1+rand(total-1)
found = false
for j in 1...ls.length
if ls[j] == c
found = true
break
end
end
if found == false;ls.push(c);end
end
ls.sort!
ls.push(total)
for i in 1...ls.length
ret.push(ls[i] - ls[i - 1])
end
return ret
end
def integersWithSum(n, total)
raise if n <= 0 or total <=0
ret = posintwithsum(n, total + n)
for i in 0...ret.length
ret[i] = ret[i] - 1
end
return ret
end
# Generate 100 valid samples
mn=3
mx=10
sum=42
n=7
100.times {
while true
pp=integersWithSum(n,sum-n*mn).map{|x| x+mn }
if !pp.find{|x| x>mx }
p pp; break # Output the sample and break
end
end
}
fuente
sum
yN
son prácticamente ilimitada (dentro de lo razonable). Estoy buscando una respuesta canónica porque el problema subyacente aparece en muchas preguntas formuladas en Stack Overflow, incluidas esta y esta . @ גלעדברקןRespuestas:
Aquí está mi solución en Java. Es completamente funcional y contiene dos generadores:
PermutationPartitionGenerator
para particiones sin clasificar yCombinationPartitionGenerator
para particiones ordenadas. Su generador también se implementó en la claseSmithTromblePartitionGenerator
para comparación. La claseSequentialEnumerator
enumera todas las particiones posibles (sin clasificar u ordenadas, según el parámetro) en orden secuencial. He agregado pruebas exhaustivas (incluidos sus casos de prueba) para todos estos generadores. La implementación es autoexplicable en su mayor parte. Si tiene alguna pregunta, las responderé en un par de días.Puedes probar esto en Ideone .
fuente
Aquí está el algoritmo de PermutationPartitionGenerator de John McClane, en otra respuesta en esta página. Tiene dos fases, a saber, una fase de configuración y una fase de muestreo, y genera
n
números aleatorios en [min
,max
] con la sumasum
, donde los números se enumeran en orden aleatorio.Fase de configuración: Primero, se crea una tabla de solución utilizando las siguientes fórmulas (
t(y, x)
dondey
está en [0,n
] yx
está en [0,sum - n * min
]):Aquí, t (y, x) almacena la probabilidad relativa de que la suma de
y
números (en el rango apropiado) sea igualx
. Esta probabilidad es relativa a todos los t (y, x) con el mismoy
.Fase de muestreo: aquí generamos una muestra de
n
números. Ajustes
asum - n * min
, luego para cada posicióni
, comenzandon - 1
y trabajando hacia atrás a 0:v
un entero aleatorio en [0, t (i + 1, s)).r
amin
.v
.v
permanezca 0 o mayor, reste t (i, s-1) dev
, sume 1r
y reste 1 des
.i
de la muestra se establece enr
.EDITAR:
Parece que con cambios triviales en el algoritmo anterior, es posible que cada número aleatorio use un rango separado en lugar de usar el mismo rango para todos ellos:
Cada número aleatorio en las posiciones
i
∈ [0,n
) tiene un valor mínimo min (i) y un valor máximo max (i).Let
adjsum
=sum
- Σmin (i).Fase de configuración: Primero, se crea una tabla de solución utilizando las siguientes fórmulas (
t(y, x)
dondey
está en [0,n
] yx
está en [0,adjsum
]):La fase de muestreo es exactamente la misma que antes, excepto que establecemos
s
enadjsum
(en lugar desum - n * min
) y establecemosr
en min (i) (en lugar demin
).EDITAR:
Para CombinationPartitionGenerator de John McClane, las fases de configuración y muestreo son las siguientes.
Fase de configuración: Primero, se construye una tabla de solución usando las siguientes fórmulas (
t(z, y, x)
dondez
está en [0,n
],y
está en [0,max - min
] yx
está en [0,sum - n * min
]):Fase de muestreo: aquí generamos una muestra de
n
números. Establezcas
tosum - n * min
ymrange
tomax - min
, luego para cada posicióni
, comenzandon - 1
y trabajando hacia atrás a 0:v
en un entero aleatorio en [0, t (i + 1, rango, s)).mrange
en min (mrange
,s
)mrange
des
.r
amin + mrange
.i
,mrange
,s
) a partir dev
.v
restos 0 o mayor, añadir 1 as
, restar 1 ar
y 1 demrange
, a continuación, t restar (i
,mrange
,s
) a partir dev
.i
de la muestra se establece enr
.fuente
No he probado esto, por lo que no es realmente una respuesta, solo algo para probar que es demasiado largo para caber en un comentario. Comience con una matriz que cumpla los dos primeros criterios y juegue con ella para que cumpla con los dos primeros, pero es mucho más aleatorio.
Si la media es un número entero, entonces su matriz inicial puede ser [4, 4, 4, ... 4] o tal vez [3, 4, 5, 3, 4, 5, ... 5, 8, 0] o Algo simple como eso. Para una media de 4.5, intente [4, 5, 4, 5, ... 4, 5].
A continuación, elija un par de números
num1
ynum2
, en la matriz. Probablemente, el primer número debe tomarse en orden, ya que con el shuffle de Fisher-Yates, el segundo número debe elegirse al azar. Tomar el primer número en orden asegura que cada número se elija al menos una vez.Ahora calcule
max-num1
ynum2-min
. Esas son las distancias de los dos números a los límitesmax
ymin
. Establecerlimit
en la menor de las dos distancias. Ese es el cambio máximo permitido que no pondrá uno u otro número fuera de los límites permitidos. Silimit
es cero, omita este par.Elija un entero aleatorio en el rango [1,
limit
]: llámelochange
. Omito 0 del rango seleccionable ya que no tiene ningún efecto. Las pruebas pueden mostrar que obtienes una mejor aleatoriedad al incluirla; No estoy seguro.Ahora listo
num1 <- num1 + change
ynum2 <- num2 - change
. Eso no afectará el valor medio y todos los elementos de la matriz todavía están dentro de los límites requeridos.Deberá ejecutar la matriz completa al menos una vez. Las pruebas deben mostrar si necesita ejecutarlo más de una vez para obtener algo lo suficientemente aleatorio.
ETA: incluye pseudocódigo
fuente
Como señala el OP, la capacidad de descargar eficientemente es muy poderosa. Si podemos hacerlo, se puede generar una distribución uniforme de particiones en tres pasos (reafirmando lo que el OP ha establecido en la pregunta):
sum
modo que las partes estén en el rango [min
,max
].[1, M]
.A continuación, sólo se centran en la generación de la n º partición ya que hay una copiosa cantidad de información sobre la generación de una distribución uniforme de número entero en un rango determinado. Aquí hay un
C++
algoritmo de clasificación simple que debería ser fácil de traducir a otros idiomas (Nota: todavía no he descubierto cómo eliminar el caso de composición (es decir, el orden importa)).La
pCount
función de caballo de batalla viene dada por:Esta función se basa en la excelente respuesta a ¿Existe un algoritmo eficiente para la partición de enteros con un número restringido de partes? por el usuario @ m69_snarky_and_unwelcoming. El que se da arriba es una ligera modificación del algoritmo simple (el que no tiene memoria). Esto se puede modificar fácilmente para incorporar la memorización para una mayor eficiencia. Dejaremos esto por ahora y nos centraremos en la parte de clasificación.
Explicación de
unRank
Primero notamos que hay un mapeo uno a uno desde las particiones de longitud N del número de
sum
manera que las partes están en el rango [min
,max
] a las particiones restringidas de longitud N del númerosum - N * (min - 1)
con partes en [1
,max - (min - 1)
].Como un pequeño ejemplo, considere las particiones
50
de longitud4
tal que elmin = 10
y elmax = 15
. Esto tendrá la misma estructura que las particiones restringidas50 - 4 * (10 - 1) = 14
de longitud4
con la parte máxima igual a15 - (10 - 1) = 6
.Con esto en mente, para contar fácilmente, podríamos agregar un paso 1a para traducir el problema al caso de "unidad" si lo desea.
Ahora, simplemente tenemos un problema de conteo. Como se muestra brillantemente en @ m69, el recuento de particiones se puede lograr fácilmente dividiendo el problema en problemas más pequeños. La función que proporciona @ m69 nos proporciona el 90% del camino, solo tenemos que averiguar qué hacer con la restricción adicional de que hay un límite. Aquí es donde obtenemos:
También tenemos que tener en cuenta que
myMax
disminuirá a medida que avancemos. Esto tiene sentido si nos fijamos en la 6 ª partición anterior:Para contar el número de particiones de aquí en adelante, debemos seguir aplicando la traducción al caso de "unidad". Esto se ve así:
Donde como el paso anterior, teníamos un máximo de
6
, ahora solo consideramos un máximo de5
.Con esto en mente, desentrañar la partición no es diferente a descalificar una permutación o combinación estándar. Debemos poder contar el número de particiones en una sección determinada. Por ejemplo, para contar el número de particiones que comienzan con lo
10
anterior, todo lo que hacemos es eliminar el10
en la primera columna:Traducir a la caja de la unidad:
y llama
pCount
:Dado un entero aleatorio para descargar, continuamos calculando el número de particiones en secciones cada vez más pequeñas (como hicimos anteriormente) hasta que hayamos llenado nuestro vector índice.
Ejemplos
Teniendo en cuenta
min = 3
,max = 10
,n = 7
, ysum = 42
, aquí es una Ideone demo que genera 20 particiones aleatorias. La salida está abajo:El índice lexicográfico está a la izquierda y la partición sin clasificar a la derecha.
fuente
Si genera 0≤a≤1 de los valores aleatorios en el rango [l, x-1] de manera uniforme, y 1-a de los valores aleatorios en el rango [x, h] de manera uniforme, la media esperada sería:
Entonces, si quieres una m específica, puedes jugar con a y x.
Por ejemplo, si establece x = m: a = (hm) / (h-l + 1).
Para garantizar una probabilidad más cercana a la uniforme para diferentes combinaciones, elija a o x al azar del conjunto de soluciones válidas para la ecuación anterior. (x debe estar en el rango [l, h] y debe estar (cerca de) un número entero; N * a también debe estar (cerca de) un número entero.
fuente