Existe-t-il un moyen efficace de générer une combinaison aléatoire de N entiers telle que ...
- chaque entier est dans l'intervalle [
min
,max
], - les entiers ont une somme de
sum
, - les entiers peuvent apparaître dans n'importe quel ordre (par exemple, un ordre aléatoire), et
- la combinaison est choisie uniformément au hasard parmi toutes les combinaisons qui répondent aux autres exigences?
Existe-t-il un algorithme similaire pour les combinaisons aléatoires dans lequel les entiers doivent apparaître dans l'ordre trié par leurs valeurs (plutôt que dans n'importe quel ordre)?
(Choisir une combinaison appropriée avec une moyenne de mean
est un cas spécial, si sum = N * mean
. Ce problème équivaut à générer une partition aléatoire uniforme de sum
en N parties qui sont chacune dans l'intervalle [ min
, max
] et apparaissent dans n'importe quel ordre ou dans l'ordre trié par leur selon le cas.)
Je suis conscient que ce problème peut être résolu de la manière suivante pour les combinaisons qui apparaissent dans un ordre aléatoire (EDIT [27 avr.]: Algorithme modifié.):
Si
N * max < sum
ouN * min > sum
, il n'y a pas de solution.Si
N * max == sum
, il n'y a qu'une seule solution, dans laquelle tous lesN
nombres sont égaux àmax
. SiN * min == sum
, il n'y a qu'une seule solution, dans laquelle tous lesN
nombres sont égaux àmin
.Utilisez l'algorithme donné dans Smith et Tromble («Sampling from the Unit Simplex», 2004) pour générer N nombres entiers non négatifs aléatoires avec la somme
sum - N * min
.Ajoutez
min
à chaque numéro généré de cette façon.Si un nombre est supérieur à
max
, passez à l'étape 3.
Cependant, cet algorithme est lent s'il max
est bien inférieur à sum
. Par exemple, selon mes tests (avec une implémentation du cas particulier ci-dessus impliquant mean
), l'algorithme rejette, en moyenne—
- environ 1,6 échantillon si
N = 7, min = 3, max = 10, sum = 42
, mais - environ 30,6 échantillons si
N = 20, min = 3, max = 10, sum = 120
.
Existe-t-il un moyen de modifier cet algorithme pour qu'il soit efficace pour un grand N tout en répondant aux exigences ci-dessus?
ÉDITER:
Comme alternative suggérée dans les commentaires, un moyen efficace de produire une combinaison aléatoire valide (qui satisfait toutes les exigences sauf la dernière) est:
- Calculer
X
, le nombre de combinaisons valides de GIVEN possiblesum
,min
etmax
. - Choisissez
Y
un entier aléatoire uniforme dans[0, X)
. - Convertissez ("non classé")
Y
en une combinaison valide.
Cependant, existe-t-il une formule pour calculer le nombre de combinaisons (ou permutations) valides, et existe-t-il un moyen de convertir un entier en une combinaison valide? [EDIT (28 avril): Idem pour les permutations plutôt que pour les combinaisons].
EDIT (27 avril):
Après avoir lu la génération aléatoire non uniforme de Devroye (1986), je peux confirmer qu'il s'agit d'un problème de génération d'une partition aléatoire. De plus, l'exercice 2 (en particulier la partie E) à la page 661 est pertinent pour cette question.
EDIT (28 avril):
Il s'est avéré que l'algorithme que j'ai donné est uniforme où les entiers impliqués sont donnés dans un ordre aléatoire , par opposition à un ordre trié par leurs valeurs . Étant donné que les deux problèmes sont d'intérêt général, j'ai modifié cette question pour rechercher une réponse canonique à ces deux problèmes.
Le code Ruby suivant peut être utilisé pour vérifier les solutions potentielles d'uniformité (où se algorithm(...)
trouve l'algorithme candidat):
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 avril): Re-ajouté le code Ruby de l'implémentation actuelle.
L'exemple de code suivant est donné en Ruby, mais ma question est indépendante du langage de programmation:
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
}
la source
sum
etN
sont effectivement illimités (dans des limites raisonnables). Je cherche une réponse canonique parce que le problème sous-jacent apparaît dans de nombreuses questions posées sur Stack Overflow, y compris celle-ci et celle-ci . @ גלעדברקןRéponses:
Voici ma solution en Java. Il est entièrement fonctionnel et contient deux générateurs:
PermutationPartitionGenerator
pour les partitions non triées etCombinationPartitionGenerator
pour les partitions triées. Votre générateur a également été implémenté dans la classeSmithTromblePartitionGenerator
pour comparaison. La classeSequentialEnumerator
énumère toutes les partitions possibles (non triées ou triées, selon le paramètre) dans un ordre séquentiel. J'ai ajouté des tests approfondis (y compris vos cas de test) pour tous ces générateurs. La mise en œuvre s'explique en grande partie d'elle-même. Si vous avez des questions, je vais y répondre dans quelques jours.Vous pouvez essayer ceci sur Ideone .
la source
Voici l'algorithme de PermutationPartitionGenerator de John McClane, dans une autre réponse sur cette page. Il comporte deux phases, à savoir une phase de configuration et une phase d'échantillonnage, et génère
n
des nombres aléatoires en [min
,max
] avec la sommesum
, où les nombres sont répertoriés dans un ordre aléatoire.Phase d'installation: Tout d'abord, une table de solutions est créée à l'aide des formules suivantes (
t(y, x)
oùy
est dans [0,n
] etx
est dans [0,sum - n * min
]):Ici, t (y, x) stocke la probabilité relative que la somme des
y
nombres (dans la plage appropriée) soit égalex
. Cette probabilité est relative à tous les t (y, x) de mêmey
.Phase d'échantillonnage: Ici, nous générons un échantillon de
n
nombres. Réglezs
sursum - n * min
, puis pour chaque positioni
, en commençant parn - 1
et en revenant à 0:v
sur un entier aléatoire dans [0, t (i + 1, s)).r
surmin
.v
.v
reste 0 ou plus, soustrayez t (i, s-1) dev
, ajoutez 1 àr
et soustrayez 1 des
.i
dans l'échantillon est défini surr
.ÉDITER:
Il semble qu'avec des changements triviaux à l'algorithme ci-dessus, il est possible que chaque nombre aléatoire utilise une plage distincte plutôt que d'utiliser la même plage pour chacun d'eux:
Chaque nombre aléatoire aux positions
i
∈ [0,n
) a une valeur minimale min (i) et une valeur maximale max (i).Soit
adjsum
=sum
- Σmin (i).Phase d'installation: Tout d'abord, une table de solutions est créée à l'aide des formules suivantes (
t(y, x)
oùy
est dans [0,n
] etx
est dans [0,adjsum
]):La phase d'échantillonnage est alors exactement la même que précédemment, sauf que nous avons réglé
s
suradjsum
(plutôt quesum - n * min
) et réglér
sur min (i) (plutôt quemin
).ÉDITER:
Pour CombinationPartitionGenerator de John McClane, les phases de configuration et d'échantillonnage sont les suivantes.
Phase d'installation: Tout d'abord, une table de solutions est créée à l'aide des formules suivantes (
t(z, y, x)
oùz
est dans [0,n
],y
est dans [0,max - min
] etx
est dans [0,sum - n * min
]):Phase d'échantillonnage: Ici, nous générons un échantillon de
n
nombres. Réglezs
sursum - n * min
etmrange
surmax - min
, puis pour chaque positioni
, en commençant parn - 1
et en revenant à 0:v
sur un entier aléatoire dans [0, t (i + 1, mrange, s)).mrange
sur min (mrange
,s
)mrange
des
.r
surmin + mrange
.i
,mrange
,s
) à partir dev
.v
reste 0 ou plus, ajouter 1 às
, soustraire 1r
et 1 à partirmrange
, puis soustrayez t (i
,mrange
,s
) à partirv
.i
dans l'échantillon est défini surr
.la source
Je n'ai pas testé cela, donc ce n'est pas vraiment une réponse, juste quelque chose à essayer qui est trop long pour entrer dans un commentaire. Commencez avec un tableau qui répond aux deux premiers critères et jouez avec lui afin qu'il réponde toujours aux deux premiers, mais est beaucoup plus aléatoire.
Si la moyenne est un entier, votre tableau initial peut être [4, 4, 4, ... 4] ou peut-être [3, 4, 5, 3, 4, 5, ... 5, 8, 0] ou quelque chose de simple comme ça. Pour une moyenne de 4,5, essayez [4, 5, 4, 5, ... 4, 5].
Ensuite, choisissez une paire de nombres
num1
etnum2
, dans le tableau. Probablement, le premier nombre doit être pris dans l'ordre, comme avec le shuffle de Fisher-Yates, le deuxième nombre doit être choisi au hasard. Prendre le premier numéro dans l'ordre garantit que chaque numéro est choisi au moins une fois.Maintenant, calculez
max-num1
etnum2-min
. Ce sont les distances entre les deux nombresmax
et lesmin
frontières. Réglezlimit
sur la plus petite des deux distances. C'est le changement maximum autorisé qui ne mettra pas l'un ou l'autre des nombres en dehors des limites autorisées. Silimit
est nul, sautez cette paire.Choisissez un entier aléatoire dans la plage [1,
limit
]: appelez-lechange
. J'omets 0 de la plage sélectionnable car cela n'a aucun effet. Les tests peuvent montrer que vous obtenez un meilleur caractère aléatoire en l'incluant; Je ne suis pas sûr.Maintenant, réglez
num1 <- num1 + change
etnum2 <- num2 - change
. Cela n'affectera pas la valeur moyenne et tous les éléments du tableau sont toujours dans les limites requises.Vous devrez parcourir l'ensemble du tableau au moins une fois. Le test devrait montrer si vous devez le parcourir plusieurs fois pour obtenir quelque chose de suffisamment aléatoire.
ETA: inclure le pseudocode
la source
Comme le souligne l'OP, la capacité de se défaire efficacement est très puissante. Si nous sommes en mesure de le faire, la génération d'une distribution uniforme des partitions peut se faire en trois étapes (en reformulant ce que l'OP a exposé dans la question):
sum
tel que les pièces soient dans la plage [min
,max
].[1, M]
.Ci-dessous, nous nous concentrons uniquement sur la génération de la n ième partition car il existe une quantité abondante d'informations sur la génération d'une distribution uniforme d'entiers dans une plage donnée. Voici un
C++
algorithme de classement simple qui devrait être facile à traduire dans d'autres langues (NB je n'ai pas encore compris comment défaire le cas de composition (c'est-à-dire que l'ordre est important)).La
pCount
fonction cheval de bataille est donnée par:Cette fonction est basée sur l'excellente réponse à Existe-t-il un algorithme efficace pour le partitionnement entier avec un nombre restreint de parties? par l'utilisateur @ m69_snarky_and_unwelcoming. Celui donné ci-dessus est une légère modification de l'algorithme simple (celui sans mémorisation). Cela peut facilement être modifié pour incorporer la mémorisation pour une plus grande efficacité. Nous allons laisser cela de côté pour l'instant et nous concentrer sur la partie non classée.
Explication de
unRank
Nous notons d'abord qu'il existe un mappage un à un des partitions de longueur N du nombre de
sum
sorte que les parties sont dans la plage [min
,max
] aux partitions restreintes de longueur N du nombresum - N * (min - 1)
avec des parties en [1
,max - (min - 1)
].À titre d'exemple, considérons les partitions
50
de longueur4
telles que lemin = 10
et lemax = 15
. Cela aura la même structure que les partitions restreintes50 - 4 * (10 - 1) = 14
de longueur4
avec la partie maximale égale à15 - (10 - 1) = 6
.Dans cet esprit, afin de pouvoir compter facilement, nous pourrions ajouter une étape 1a pour traduire le problème dans le cas "unité" si vous le souhaitez.
Maintenant, nous avons simplement un problème de comptage. Comme le montre brillamment @ m69, le comptage des partitions peut être facilement réalisé en divisant le problème en problèmes plus petits. La fonction fournie par @ m69 nous permet d'obtenir 90% du chemin, il nous suffit de comprendre ce qu'il faut faire avec la restriction supplémentaire qu'il y a un plafond. C'est là que nous obtenons:
Nous devons également garder à l'esprit que
myMax
cela diminuera à mesure que nous avancerons. Cela est logique si l' on considère la 6 e partition ci - dessus:Afin de compter le nombre de partitions à partir de maintenant, nous devons continuer d'appliquer la traduction au cas "unité". Cela ressemble à ceci:
Alors que l'étape précédente, nous avions un max de
6
, maintenant nous ne considérons qu'un max de5
.Dans cette optique, le classement de la partition n'est pas différent du classement d'une permutation ou combinaison standard. Il faut pouvoir compter le nombre de partitions dans une section donnée. Par exemple, pour compter le nombre de partitions commençant par
10
ci-dessus, tout ce que nous faisons est de supprimer le10
dans la première colonne:Traduire dans le cas de l'unité:
et appelez
pCount
:Étant donné un entier aléatoire à non classé, nous continuons de calculer le nombre de partitions dans des sections de plus en plus petites (comme nous l'avons fait ci-dessus) jusqu'à ce que nous ayons rempli notre vecteur d'index.
Exemples
Compte tenu
min = 3
,max = 10
,n = 7
etsum = 42
, voici une ideone démo qui génère 20 partitions aléatoires. La sortie est ci-dessous:L'index lexicographique est à gauche et la partition non classée à droite.
la source
Si vous générez uniformément 0≤a≤1 des valeurs aléatoires dans la plage [l, x-1] et 1-a des valeurs aléatoires dans la plage [x, h] uniformément, la moyenne attendue serait:
Donc, si vous voulez un m spécifique, vous pouvez jouer avec a et x.
Par exemple, si vous définissez x = m: a = (hm) / (h-l + 1).
Pour garantir une probabilité plus proche de l'uniformité pour différentes combinaisons, choisissez a ou x au hasard dans l'ensemble des solutions valides pour l'équation ci-dessus. (x doit être compris entre [l, h] et doit être (proche) d'un entier; N * a doit également être (proche) d'un entier.
la source