Existe uma maneira eficiente de gerar uma combinação aleatória de N números inteiros, como:
- cada número inteiro está no intervalo [
min
,max
], - os números inteiros têm uma soma de
sum
, - os números inteiros podem aparecer em qualquer ordem (por exemplo, ordem aleatória) e
- a combinação é escolhida uniformemente aleatoriamente dentre todas as combinações que atendem aos outros requisitos?
Existe um algoritmo semelhante para combinações aleatórias em que os números inteiros devem aparecer na ordem classificada por seus valores (e não em qualquer ordem)?
(Escolher uma combinação apropriada com uma média de mean
é um caso especial, se sum = N * mean
. Esse problema é equivalente a gerar uma partição aleatória uniforme de sum
em N partes que estão cada um no intervalo [ min
, max
] e aparecem em qualquer ordem ou em ordem classificada por seus valores, conforme o caso.)
Estou ciente de que esse problema pode ser resolvido da seguinte maneira para combinações que aparecem em ordem aleatória (EDIT [27 de abril]: Algoritmo modificado.):
Se
N * max < sum
ouN * min > sum
, não há solução.Se
N * max == sum
houver apenas uma solução, na qual todos osN
números são iguaismax
. SeN * min == sum
houver apenas uma solução, na qual todos osN
números são iguaismin
.Use o algoritmo fornecido em Smith e Tromble ("Sampling from the Unit Simplex", 2004) para gerar N números inteiros aleatórios não negativos com a soma
sum - N * min
.Adicione
min
a cada número gerado dessa maneira.Se qualquer número for maior que
max
, vá para a etapa 3.
No entanto, esse algoritmo é lento se max
for muito menor que sum
. Por exemplo, de acordo com meus testes (com uma implementação do caso especial acima envolvendo mean
), o algoritmo rejeita, em média,
- cerca de 1,6 amostras se
N = 7, min = 3, max = 10, sum = 42
, mas - cerca de 30,6 amostras se
N = 20, min = 3, max = 10, sum = 120
.
Existe uma maneira de modificar esse algoritmo para ser eficiente para N grande, enquanto ainda atende aos requisitos acima?
EDITAR:
Como alternativa sugerida nos comentários, uma maneira eficiente de produzir uma combinação aleatória válida (que satisfaça todos, exceto o último requisito) é:
- Calcular
X
, o número de combinações válidas possíveissum
, dado,,min
emax
. - Escolha
Y
um número inteiro aleatório uniforme em[0, X)
. - Converter ("unrank")
Y
em uma combinação válida.
No entanto, existe uma fórmula para calcular o número de combinações válidas (ou permutações) e existe uma maneira de converter um número inteiro em uma combinação válida? [EDIT (28 de abril): o mesmo para permutações em vez de combinações].
EDIT (27 de abril):
Depois de ler a Geração de variável aleatória não uniforme de Devroye (1986), posso confirmar que esse é um problema de gerar uma partição aleatória. Além disso, o Exercício 2 (especialmente a parte E) na página 661 é relevante para esta pergunta.
EDIT (28 de abril):
Como se viu, o algoritmo que dei é uniforme, onde os números inteiros envolvidos são dados em ordem aleatória , em oposição à ordem classificada por seus valores . Como ambos os problemas são de interesse geral, modifiquei esta questão para buscar uma resposta canônica para ambos.
O código Ruby a seguir pode ser usado para verificar possíveis soluções de uniformidade (onde algorithm(...)
está o 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
EDIT (29 de abril): Re-adicionado código Ruby da implementação atual.
O exemplo de código a seguir é dado em Ruby, mas minha pergunta é independente da linguagem de programação:
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
}
fonte
sum
eN
são efetivamente ilimitados (dentro da razão). Estou procurando uma resposta canônica porque o problema subjacente aparece em muitas perguntas feitas no Stack Overflow, incluindo esta e esta . @ גלעדברקןRespostas:
Aqui está a minha solução em Java. É totalmente funcional e contém dois geradores:
PermutationPartitionGenerator
para partições não classificadas eCombinationPartitionGenerator
para partições classificadas. Seu gerador também foi implementado na classeSmithTromblePartitionGenerator
para comparação. A classeSequentialEnumerator
enumera todas as partições possíveis (não classificadas ou classificadas, dependendo do parâmetro) em ordem seqüencial. Adicionei testes completos (incluindo seus casos de teste) para todos esses geradores. A implementação é auto-explicável na maior parte. Se você tiver alguma dúvida, eu responderei em alguns dias.Você pode tentar isso no Ideone .
fonte
Aqui está o algoritmo do PermutationPartitionGenerator de John McClane, em outra resposta nesta página. Possui duas fases, a saber, uma fase de configuração e uma fase de amostragem, e gera
n
números aleatórios em [min
,max
] com a somasum
, onde os números são listados em ordem aleatória.Fase de configuração: Primeiro, uma tabela de solução é criada usando as seguintes fórmulas (
t(y, x)
ondey
está em [0,n
] ex
em [0,sum - n * min
]):Aqui, t (y, x) armazena a probabilidade relativa de que a soma dos
y
números (no intervalo apropriado) será igualx
. Essa probabilidade é relativa a todos os t (y, x) iguaisy
.Fase de amostragem: Aqui geramos uma amostra de
n
números. Definas
parasum - n * min
, em seguida, para cada posiçãoi
, começando comn - 1
e trabalhando para trás em 0:v
como um número inteiro aleatório em [0, t (i + 1, s)).r
comomin
.v
.v
permanecer 0 ou maior, subtraia t (i, s-1) dev
, adicione 1 ar
e subtraia 1 des
.i
na amostra está definido comor
.EDITAR:
Parece que, com alterações triviais no algoritmo acima, é possível que cada número aleatório use um intervalo separado, em vez de usar o mesmo intervalo para todos eles:
Cada número aleatório nas posições
i
∈ [0,n
) tem um valor mínimo min (i) e um valor máximo max (i).Seja
adjsum
=sum
- Σmin (i).Fase de configuração: Primeiro, uma tabela de solução é criada usando as seguintes fórmulas (
t(y, x)
ondey
está em [0,n
] ex
em [0,adjsum
]):A fase de amostragem é então exatamente a mesma de antes, exceto que configuramos
s
paraadjsum
(em vez desum - n * min
) e configuramosr
para min (i) (em vez demin
).EDITAR:
Para CombinationPartitionGenerator, de John McClane, as fases de configuração e amostragem são as seguintes.
Fase de configuração: Primeiro, uma tabela de solução é criada usando as seguintes fórmulas (
t(z, y, x)
ondez
está em [0,n
],y
está em [0,max - min
] ex
está em [0,sum - n * min
]):Fase de amostragem: Aqui geramos uma amostra de
n
números. Definas
comosum - n * min
emrange
paramax - min
, em seguida, para cada posiçãoi
, iniciandon - 1
e trabalhando para trás em 0:v
como um número inteiro aleatório em [0, t (i + 1, intervalo, s)).mrange
para min (mrange
,s
)mrange
des
.r
comomin + mrange
.i
,mrange
,s
) a partir dev
.v
restos 0 ou maior, adicionar 1 as
, subtrair 1r
e 1 a partir demrange
, em seguida, subtrair t (i
,mrange
,s
) a partir dev
.i
na amostra está definido comor
.fonte
Eu não testei isso, por isso não é realmente uma resposta, apenas algo para tentar que é muito longo para caber em um comentário. Comece com um array que atenda aos dois primeiros critérios e brinque com ele para que ele ainda atenda aos dois primeiros, mas é muito mais aleatório.
Se a média for um número inteiro, sua matriz inicial pode ser [4, 4, 4, ... 4] ou talvez [3, 4, 5, 3, 4, 5, ... 5, 8, 0] ou algo simples assim. Para uma média de 4,5, tente [4, 5, 4, 5, ... 4, 5].
Em seguida, escolha um par de números
num1
enum2
, na matriz. Provavelmente, o primeiro número deve ser tomado em ordem, como no embaralhamento de Fisher-Yates, o segundo número deve ser escolhido aleatoriamente. A ordem do primeiro número garante que todos os números sejam selecionados pelo menos uma vez.Agora calcule
max-num1
enum2-min
. Essas são as distâncias entre os dois númerosmax
e osmin
limites. Definalimit
para a menor das duas distâncias. Essa é a alteração máxima permitida, que não colocará um ou outro número fora dos limites permitidos. Selimit
for zero, pule este par.Escolha um número inteiro aleatório no intervalo [1,
limit
]: chame-ochange
. Eu omito 0 do intervalo selecionável, pois não tem efeito. Os testes podem mostrar que você obtém melhor aleatoriedade ao incluí-la; Não tenho certeza.Agora defina
num1 <- num1 + change
enum2 <- num2 - change
. Isso não afetará o valor médio e todos os elementos da matriz ainda estão dentro dos limites necessários.Você precisará percorrer toda a matriz pelo menos uma vez. O teste deve mostrar se você precisa executá-lo mais de uma vez para obter algo suficientemente aleatório.
ETA: incluir pseudocódigo
fonte
Como o OP ressalta, a capacidade de desclassificar eficientemente é muito poderosa. Se pudermos fazer isso, a geração de uma distribuição uniforme de partições pode ser feita em três etapas (reafirmando o que o OP estabeleceu na pergunta):
sum
modo que as partes estejam no intervalo [min
,max
].[1, M]
.Abaixo, nos concentramos apenas na geração da n- ésima partição, pois há uma quantidade abundante de informações na geração de uma distribuição uniforme de número inteiro em um determinado intervalo. Aqui está um
C++
algoritmo simples de desagregação que deve ser fácil de traduzir para outros idiomas (NB ainda não descobri como desagregar o caso da composição (por exemplo, a ordem é importante)).A
pCount
função de burro de carga é dada por:Esta função é baseada na excelente resposta para Existe um algoritmo eficiente para particionamento inteiro com número restrito de peças? pelo usuário @ m69_snarky_and_unwelcoming. O dado acima é uma ligeira modificação do algoritmo simples (aquele sem memorização). Isso pode ser facilmente modificado para incorporar a memorização para maior eficiência. Por enquanto, deixaremos isso de lado e focaremos na parte sem classificação.
Explicação de
unRank
Primeiro, observamos que há um mapeamento individual das partições de comprimento N do número, de
sum
modo que as partes estejam no intervalo [min
,max
] até as partições restritas de comprimento N do númerosum - N * (min - 1)
com partes em [1
,max - (min - 1)
].Como um pequeno exemplo, considere as partições
50
de comprimento4
tais que themin = 10
e themax = 15
. Isso terá a mesma estrutura que as partições restritas50 - 4 * (10 - 1) = 14
de comprimento4
com a parte máxima igual a15 - (10 - 1) = 6
.Com isso em mente, para contar facilmente, poderíamos adicionar uma etapa 1a para traduzir o problema para o caso "unit", se desejar.
Agora, simplesmente temos um problema de contagem. Como o @ m69 exibe brilhantemente, a contagem de partições pode ser facilmente obtida dividindo o problema em problemas menores. A função @ m69 fornece nos dá 90% do caminho, só precisamos descobrir o que fazer com a restrição adicional de que existe um limite. É aqui que chegamos:
Também devemos ter em mente que
myMax
isso diminuirá à medida que avançamos. Isso faz sentido se olharmos para a 6 ª partição acima:Para contar o número de partições daqui em diante, devemos continuar aplicando a tradução ao caso "unit". Isso se parece com:
Onde, como no passo anterior, tínhamos um máximo de
6
, agora consideramos apenas um máximo de5
.Com isso em mente, desarranjar a partição não é diferente de desarranjar uma permutação ou combinação padrão. Devemos poder contar o número de partições em uma determinada seção. Por exemplo, para contar o número de partições que começam com
10
acima, tudo o que fazemos é remover10
a primeira coluna:Traduzir para o caso da unidade:
e ligue para
pCount
:Dado um número inteiro aleatório para desagrupar, continuamos calculando o número de partições em seções cada vez menores (como fizemos acima) até preenchermos nosso vetor de índice.
Exemplos
Dada
min = 3
,max = 10
,n = 7
, esum = 42
, aqui está um ideone demo que gera 20 partições aleatórias. A saída está abaixo:O índice lexicográfico está à esquerda e a partição não classificada, à direita.
fonte
Se você gerar 0≤a≤1 dos valores aleatórios no intervalo [l, x-1] uniformemente e 1-a dos valores aleatórios no intervalo [x, h] uniformemente, a média esperada seria:
Então, se você quer um m específico, pode jogar com a e x.
Por exemplo, se você definir x = m: a = (hm) / (h-l + 1).
Para garantir uma probabilidade mais próxima do uniforme para diferentes combinações, escolha a ou x aleatoriamente do conjunto de soluções válidas para a equação acima. (x deve estar no intervalo [l, h] e deve ser (próximo a) um número inteiro; N * a deve ser (próximo a) um número inteiro também.
fonte