Apakah ada cara efisien untuk menghasilkan kombinasi acak bilangan bulat N sehingga—
- setiap bilangan bulat berada dalam interval [
min
,max
], - bilangan bulat memiliki jumlah
sum
, - bilangan bulat dapat muncul dalam urutan apa pun (mis., urutan acak), dan
- kombinasi dipilih secara seragam secara acak dari semua kombinasi yang memenuhi persyaratan lain?
Apakah ada algoritma yang sama untuk kombinasi acak di mana bilangan bulat harus muncul dalam urutan diurutkan berdasarkan nilainya (daripada dalam urutan apa pun)?
(Memilih kombinasi yang sesuai dengan rata-rata mean
adalah kasus khusus, jika sum = N * mean
. Masalah ini setara dengan menghasilkan partisi acak yang seragam sum
ke dalam N bagian yang masing-masing dalam interval [ min
, max
] dan muncul dalam urutan apa pun atau dalam urutan diurutkan menurut urutannya. nilai-nilai, sesuai kasusnya.)
Saya menyadari bahwa masalah ini dapat diselesaikan dengan cara berikut untuk kombinasi yang muncul dalam urutan acak (EDIT [27 Apr]: Algoritma dimodifikasi.):
Jika
N * max < sum
atauN * min > sum
, tidak ada solusi.Jika
N * max == sum
, hanya ada satu solusi, di mana semuaN
angka sama denganmax
. JikaN * min == sum
, hanya ada satu solusi, di mana semuaN
angka sama denganmin
.Gunakan algoritma yang diberikan dalam Smith dan Tromble ("Pengambilan Sampel dari Unit Simplex", 2004) untuk menghasilkan bilangan bulat acak non-negatif N dengan penjumlahan
sum - N * min
.Tambahkan
min
ke setiap nomor yang dihasilkan dengan cara ini.Jika ada nomor yang lebih besar dari
max
, lanjutkan ke langkah 3.
Namun, algoritma ini lambat jika max
jauh lebih kecil sum
. Sebagai contoh, menurut pengujian saya (dengan implementasi kasus khusus yang melibatkan di atas mean
), algoritma menolak, rata-rata—
- sekitar 1,6 sampel jika
N = 7, min = 3, max = 10, sum = 42
, tetapi - sekitar 30,6 sampel jika
N = 20, min = 3, max = 10, sum = 120
.
Apakah ada cara untuk memodifikasi algoritma ini agar efisien untuk N besar sambil tetap memenuhi persyaratan di atas?
EDIT:
Sebagai alternatif yang disarankan dalam komentar, cara efisien untuk menghasilkan kombinasi acak yang valid (yang memenuhi semua kecuali persyaratan terakhir) adalah:
- Hitung
X
, jumlah kombinasi yang valid mungkin diberikansum
,min
danmax
. - Pilih
Y
, bilangan bulat acak seragam[0, X)
. - Ubah ("unrank")
Y
menjadi kombinasi yang valid.
Namun, apakah ada rumus untuk menghitung jumlah kombinasi yang valid (atau permutasi), dan adakah cara untuk mengkonversi integer ke kombinasi yang valid? [EDIT (28 April): Sama untuk permutasi daripada kombinasi].
EDIT (27 April):
Setelah membaca Devroye's Non-Uniform Random Variate Generation (1986), saya dapat mengkonfirmasi bahwa ini adalah masalah menghasilkan partisi acak. Juga, Latihan 2 (terutama bagian E) pada halaman 661 relevan dengan pertanyaan ini.
EDIT (28 April):
Ternyata algoritma yang saya berikan seragam di mana bilangan bulat yang terlibat diberikan dalam urutan acak , sebagai lawan urutan diurutkan berdasarkan nilai-nilai mereka . Karena kedua masalah tersebut merupakan kepentingan umum, saya telah memodifikasi pertanyaan ini untuk mencari jawaban kanonik untuk kedua masalah tersebut.
Kode Ruby berikut dapat digunakan untuk memverifikasi solusi potensial untuk keseragaman (di mana algorithm(...)
kandidat algoritma):
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 April): Menambahkan kembali kode Ruby dari implementasi saat ini.
Contoh kode berikut diberikan dalam Ruby, tetapi pertanyaan saya tidak tergantung pada bahasa pemrograman:
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
}
sumber
sum
danN
secara efektif tidak terbatas (sesuai alasan). Saya mencari jawaban kanonik karena masalah mendasar muncul dalam banyak pertanyaan yang diajukan pada Stack Overflow, termasuk yang ini dan yang ini . @ גלעדברקןJawaban:
Inilah solusi saya di Jawa. Ini berfungsi penuh dan berisi dua generator:
PermutationPartitionGenerator
untuk partisi yang tidak disortir danCombinationPartitionGenerator
untuk partisi yang diurutkan. Generator Anda juga diterapkan di kelasSmithTromblePartitionGenerator
untuk perbandingan. KelasSequentialEnumerator
menghitung semua kemungkinan partisi (tidak disortir atau disortir, tergantung pada parameter) dalam urutan berurutan. Saya telah menambahkan tes menyeluruh (termasuk kasus pengujian Anda) untuk semua generator ini. Implementasinya dapat dijelaskan sendiri untuk sebagian besar. Jika Anda memiliki pertanyaan, saya akan menjawabnya dalam beberapa hari.Anda dapat mencoba ini di Ideone .
sumber
Berikut adalah algoritme dari PermutationPartitionGenerator John McClane, dalam jawaban lain di halaman ini. Ini memiliki dua fase, yaitu fase pengaturan dan fase pengambilan sampel, dan menghasilkan
n
angka acak dalam [min
,max
] dengan jumlahsum
, di mana angka-angka tersebut tercantum dalam urutan acak.Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di
t(y, x)
manay
berada di [0,n
] danx
di [0,sum - n * min
]):Di sini, t (y, x) menyimpan probabilitas relatif bahwa jumlah
y
angka (dalam kisaran yang sesuai) akan samax
. Probabilitas ini relatif terhadap semua t (y, x) dengan yang samay
.Fase pengambilan sampel: Di sini kami menghasilkan sampel
n
angka. Setels
kesum - n * min
, lalu untuk setiap posisii
, mulai dengann - 1
dan mundur ke 0:v
ke integer acak dalam [0, t (i + 1, s)).r
kemin
.v
.v
tetap 0 atau lebih besar, kurangi t (i, s-1) dariv
, tambahkan 1 ker
, dan kurangi 1 daris
.i
dalam sampel diatur ker
.EDIT:
Tampaknya dengan perubahan sepele untuk algoritme di atas, dimungkinkan untuk membuat setiap nomor acak menggunakan rentang yang terpisah daripada menggunakan rentang yang sama untuk semuanya:
Setiap angka acak pada posisi
i
∈ [0,n
) memiliki nilai minimum min (i) dan nilai maksimum maks (i).Biarkan
adjsum
=sum
- Σmin (i).Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di
t(y, x)
manay
berada di [0,n
] danx
di [0,adjsum
]):Fase pengambilan sampel sama persis seperti sebelumnya, kecuali kita mengatur
s
keadjsum
(daripadasum - n * min
) dan diaturr
ke min (i) (bukanmin
).EDIT:
Untuk CombinationPartitionGenerator John McClane, fase pengaturan dan pengambilan sampel adalah sebagai berikut.
Fase pengaturan: Pertama, tabel solusi dibangun menggunakan rumus berikut (di
t(z, y, x)
manaz
berada di [0,n
],y
di [0,max - min
], danx
di [0,sum - n * min
]):Fase pengambilan sampel: Di sini kami menghasilkan sampel
n
angka. Aturs
kesum - n * min
danmrange
kemax - min
, lalu untuk setiap posisii
, mulai dengann - 1
dan mundur ke 0:v
ke integer acak dalam [0, t (i + 1, susun, s)).mrange
ke min (mrange
,s
)mrange
daris
.r
kemin + mrange
.i
,mrange
,s
) dariv
.v
sisa-sisa 0 atau lebih besar, tambahkan 1 kes
, kurangi 1 darir
dan 1 darimrange
, maka t kurangi (i
,mrange
,s
) dariv
.i
dalam sampel diatur ker
.sumber
Saya belum menguji ini, jadi itu bukan jawaban, hanya sesuatu untuk dicoba yang terlalu panjang untuk dimasukkan ke dalam komentar. Mulailah dengan sebuah array yang memenuhi dua kriteria pertama dan bermain dengannya sehingga masih memenuhi dua kriteria pertama, tetapi jauh lebih acak.
Jika mean adalah bilangan bulat, maka array awal Anda dapat [4, 4, 4, ... 4] atau mungkin [3, 4, 5, 3, 4, 5, ... 5, 8, 0] atau sesuatu yang sederhana seperti itu. Untuk rata-rata 4,5, coba [4, 5, 4, 5, ... 4, 5].
Selanjutnya pilih sepasang angka,
num1
dannum2
, dalam array. Mungkin nomor pertama harus diambil secara berurutan, seperti halnya kocokan Fisher-Yates, angka kedua harus dipilih secara acak. Mengambil nomor pertama secara berurutan memastikan bahwa setiap nomor dipetik setidaknya satu kali.Sekarang hitung
max-num1
dannum2-min
. Itu adalah jarak dari dua angka ke batasmax
danmin
. Aturlimit
ke yang lebih kecil dari dua jarak. Itu adalah perubahan maksimum yang diizinkan yang tidak akan menempatkan salah satu dari angka-angka di luar batas yang diizinkan. Jikalimit
nol maka lewati pasangan ini.Pilih bilangan bulat acak dalam rentang [1,
limit
]: panggil sajachange
. Saya menghilangkan 0 dari rentang yang dapat dipilih karena tidak memiliki efek. Pengujian mungkin menunjukkan bahwa Anda mendapatkan keacakan yang lebih baik dengan memasukkannya; Saya tidak yakin.Sekarang atur
num1 <- num1 + change
dannum2 <- num2 - change
. Itu tidak akan mempengaruhi nilai rata-rata dan semua elemen array masih dalam batas yang diperlukan.Anda harus menjalankan seluruh array setidaknya satu kali. Pengujian harus menunjukkan jika Anda perlu menjalankannya lebih dari satu kali untuk mendapatkan sesuatu yang cukup acak.
ETA: sertakan pseudocode
sumber
Seperti yang ditunjukkan OP, kemampuan unrank efisien sangat kuat. Jika kita dapat melakukannya, menghasilkan distribusi partisi yang seragam dapat dilakukan dalam tiga langkah (menyatakan kembali apa yang telah OP sebutkan dalam pertanyaan):
sum
sedemikian rupa sehingga bagian-bagiannya berada dalam kisaran [min
,max
].[1, M]
.Di bawah ini, kami hanya fokus pada menghasilkan partisi ke- n karena ada sejumlah besar informasi tentang menghasilkan distribusi integer yang seragam dalam rentang yang diberikan. Berikut ini adalah
C++
algoritma unranking sederhana yang seharusnya mudah diterjemahkan ke bahasa lain (NB saya belum menemukan cara untuk membuka tutup case komposisi (mis. Ketertiban)).Fungsi pekerja keras
pCount
diberikan oleh:Fungsi ini didasarkan pada jawaban yang sangat baik untuk Apakah ada algoritma yang efisien untuk partisi integer dengan jumlah bagian yang terbatas? oleh pengguna @ m69_snarky_and_unwelcoming. Yang diberikan di atas adalah sedikit modifikasi dari algoritma sederhana (yang tanpa memoisasi). Ini dapat dengan mudah dimodifikasi untuk memasukkan memoisasi untuk efisiensi yang lebih besar. Kami akan mengabaikan ini untuk saat ini dan fokus pada bagian unranking.
Penjelasan dari
unRank
Kami pertama-tama mencatat ada pemetaan satu-ke-satu dari partisi panjang N dari nomor
sum
sedemikian sehingga bagian-bagian berada dalam kisaran [min
,max
] ke partisi terbatas panjang N dari angkasum - N * (min - 1)
dengan bagian-bagian dalam [1
,max - (min - 1)
].Sebagai contoh kecil, pertimbangkan partisi dari
50
panjang4
sehinggamin = 10
danmax = 15
. Ini akan memiliki struktur yang sama dengan partisi dengan50 - 4 * (10 - 1) = 14
panjang terbatas4
dengan bagian maksimum sama dengan15 - (10 - 1) = 6
.Dengan mengingat hal ini, agar mudah dihitung, kita dapat menambahkan langkah 1a untuk menerjemahkan masalah ke kasing "unit" jika Anda mau.
Sekarang, kami hanya memiliki masalah penghitungan. Seperti @ m69 ditampilkan dengan cemerlang, menghitung partisi dapat dengan mudah dicapai dengan memecah masalah menjadi masalah yang lebih kecil. Fungsi @ m69 memberi Anda 90% dari cara, kita hanya harus mencari tahu apa yang harus dilakukan dengan pembatasan tambahan yang ada batasnya. Di sinilah kita mendapatkan:
Kita juga harus ingat bahwa hal itu
myMax
akan berkurang ketika kita terus berjalan. Ini masuk akal jika kita melihat partisi ke- 6 di atas:Untuk menghitung jumlah partisi dari sini, kita harus terus menerapkan terjemahan ke kasus "unit". Ini terlihat seperti:
Dimana sebagai langkah sebelumnya, kami memiliki maks
6
, sekarang kami hanya mempertimbangkan maks5
.Dengan mengingat hal ini, unranking partisi tidak ada bedanya dengan unranking permutasi atau kombinasi standar. Kita harus dapat menghitung jumlah partisi di bagian yang diberikan. Misalnya, untuk menghitung jumlah partisi yang dimulai dengan di
10
atas, semua yang kami lakukan adalah menghapus10
di kolom pertama:Menerjemahkan ke unit kasus:
dan telepon
pCount
:Diberikan bilangan bulat acak untuk dibuka, kami terus menghitung jumlah partisi di bagian yang lebih kecil dan lebih kecil (seperti yang kami lakukan di atas) sampai kami telah mengisi vektor indeks kami.
Contohnya
Mengingat
min = 3
,max = 10
,n = 7
, dansum = 42
, di sini adalah ideone demo yang menghasilkan 20 partisi acak. Outputnya di bawah ini:Indeks leksikografis ada di sebelah kiri dan partisi yang tidak dirank di sebelah kanan.
sumber
Jika Anda menghasilkan 0≤a≤1 dari nilai acak dalam rentang [l, x-1] secara seragam, dan 1-dari nilai acak dalam rentang [x, h] secara seragam, rata-rata yang diharapkan adalah:
Jadi, jika Anda menginginkan m tertentu, Anda bisa bermain dengan a dan x.
Misalnya, jika Anda mengatur x = m: a = (hm) / (h-l + 1).
Untuk memastikan probabilitas lebih dekat ke seragam untuk kombinasi yang berbeda, pilih a atau x secara acak dari serangkaian solusi yang valid untuk persamaan di atas. (x harus dalam kisaran [l, h] dan harus (dekat dengan) bilangan bulat; N * a juga harus (dekat dengan) bilangan bulat.
sumber