Yukarıdaki yorumumda belirttiğim gibi, kodunuzu aşırı karmaşıklaştırmadan önce bunu profillemenizi öneririz. Hızlı for
döngü özetleme zar karmaşık matematik formülleri ve tablo oluşturma / arama daha anlamak ve değiştirmek çok daha kolaydır. Önemli sorunları çözdüğünüzden emin olmak için her zaman önce profil oluşturun. ;)
Bununla birlikte, bir düşme baskısında karmaşık olasılık dağılımlarını örneklemenin iki ana yolu vardır:
1. Kümülatif Olasılık Dağılımları
Sadece tek bir üniform rasgele girdi kullanarak sürekli olasılık dağılımlarından numune almak için düzgün bir hile vardır . O ile bir ilgisi yoktur kümülatif dağılım fonksiyonu olduğu "bir değerini alma olasılığı nedir cevaplar büyük olmayan x değerinden?"
Bu işlev azalmaz, 0'dan başlar ve etki alanı üzerinde 1'e yükselir. İki altı yüzlü zarın toplamı için bir örnek aşağıda gösterilmiştir:
Kümülatif dağıtım işlevinizin hesaplaması uygun bir tersi varsa (veya Bézier eğrileri gibi parçalı işlevlerle yaklaşık olarak tahmin edebiliyorsanız), bunu orijinal olasılık işlevinden örneklemek için kullanabilirsiniz.
Ters fonksiyon, alanın 0 ile 1 arasında parsellemesini, orijinal rastgele işlemin her bir çıktısına eşlenmiş aralıklarla işler ve her birinin yakalama alanı orijinal olasılıkla eşleşir. (Bu sürekli dağılımlar için son derece doğrudur. Zar atışları gibi ayrık dağılımlar için dikkatli yuvarlama yapmamız gerekir)
İşte bunu 2d6'ya benzetmek için kullanma örneği:
int SimRoll2d6()
{
// Get a random input in the half-open interval [0, 1).
float t = Random.Range(0f, 1f);
float v;
// Piecewise inverse calculated by hand. ;)
if(t <= 0.5f)
{
v = (1f + sqrt(1f + 288f * t)) * 0.5f;
}
else
{
v = (25f - sqrt(289f - 288f * t)) * 0.5f;
}
return floor(v + 1);
}
Bunu şununla karşılaştır:
int NaiveRollNd6(int n)
{
int sum = 0;
for(int i = 0; i < n; i++)
sum += Random.Range(1, 7); // I'm used to Range never returning its max
return sum;
}
Kod netliği ve esnekliğindeki fark hakkında ne demek istediğimi anlıyor musunuz? Saf yol döngüleri ile naif olabilir, ancak kısa ve basittir, ne yaptığı hakkında hemen açıktır ve farklı kalıp boyutlarına ve sayılarına ölçeklendirmek kolaydır. Kümülatif dağıtım kodunda değişiklik yapmak önemsiz olmayan bir matematik gerektirir ve açık bir hata olmadan kırılması ve beklenmedik sonuçlara neden olması kolay olacaktır. (Umarım yukarıda yapmadım)
Bu nedenle, net bir döngüden ayrılmadan önce, bunun bu tür fedakarlığa değer bir performans sorunu olduğundan kesinlikle emin olun.
2. Takma Ad Yöntemi
Kümülatif dağılım yöntemi, kümülatif dağılım işlevinin tersini basit bir matematik ifadesi olarak ifade edebildiğinizde iyi çalışır, ancak bu her zaman kolay ve hatta mümkün değildir. Ayrık dağıtımlar için güvenilir bir alternatif , Takma Ad Yöntemi olarak adlandırılan bir şeydir .
Bu, yalnızca iki bağımsız, tekdüze dağılmış rasgele giriş kullanarak rastgele ayrık olasılık dağılımından örneklemenizi sağlar.
Soldaki aşağıdaki gibi bir dağıtım alarak (alanların / ağırlıkların 1'e eşit olmadığından endişe etmeyin, göreli ağırlığı önemsediğimiz Takma Ad Yöntemi için ) ve bunu aşağıdaki gibi bir tabloya dönüştürerek çalışır. doğru yer:
- Her sonuç için bir sütun vardır.
- Her sütun, her biri orijinal sonuçlardan biriyle ilişkili en fazla iki bölüme ayrılmıştır.
- Her sonucun nispi alanı / ağırlığı korunur.
( Örnekleme yöntemleri hakkındaki bu mükemmel makaledeki resimlere dayanan diyagram )
Kod olarak, bunu, her sütundan alternatif sonucu seçme olasılığını ve bu alternatif sonucun kimliğini (veya "takma adını") temsil eden iki tablo (veya iki özelliğe sahip bir nesne tablosu) ile temsil ederiz. O zaman dağıtımdan örnek alabiliriz:
int SampleFromTables(float[] probabiltyTable, int[] aliasTable)
{
int column = Random.Range(0, probabilityTable.Length);
float p = Random.Range(0f, 1f);
if(p < probabilityTable[column])
{
return column;
}
else
{
return aliasTable[column];
}
}
Bu biraz kurulum gerektirir:
Olası her sonucun göreceli olasılıklarını hesaplayın (1000d6'yı yuvarlıyorsanız, her toplamı 1000'den 6000'e kadar elde etmenin yol sayısını hesaplamamız gerekir)
Her sonuç için bir giriş içeren bir çift tablo oluşturun. Tam yöntem bu cevabın kapsamı dışına çıkar, bu yüzden Takma Ad Yöntemi algoritmasının bu açıklamasına başvurmanızı şiddetle tavsiye ederim .
Bu tabloları saklayın ve bu dağıtımdan yeni bir rasgele kalıp rulosuna ihtiyacınız olduğunda onlara geri dönün.
Bu bir uzay-zaman dengesidir . Ön hesaplama adımı biraz ayrıntılıdır ve sahip olduğumuz sonuçlarla orantılı bir bellek ayırmamız gerekir (1000d6 için bile, tek haneli kilobaytlardan bahsediyoruz, bu yüzden uykumuzu kaybedecek bir şey yok), ancak örneklemimizi değiştirelim dağıtımımız ne kadar karmaşık olursa olsun sabit sürelidir.
Umarım bu yöntemlerden biri ya da diğeri bazı yararlı olabilir (ya da naif yöntemin sadeliğinin döngü için harcanan zamana değer olduğuna ikna oldum);)