Döngü olmadan çok sayıda zar rulo simüle?


14

Tamam, eğer oyununuz çok fazla zar atarsa, sadece bir döngüde rastgele bir sayı üreteci çağırabilirsiniz. Ancak yeterince sık yuvarlanan herhangi bir zar seti için bir dağıtım eğrisi / histogram alırsınız. Yani sorum, dağıtıma uyan bir sayı verecek güzel bir basit hesaplama var mı?

Örn 2D6 - Puan -% Olasılık

% 2 - 2.77

% 3 - 5.55

% 4 - 8.33

% 5 - 11.11

% 6 - 13,88

% 7 - 16,66

% 8 - 13.88

% 9 - 11.11

% 10 - 8.33

% 11 - 5.55

% 12 - 2.77

Yani yukarıdakileri bilmek tek bir d100 yuvarlayabilir ve doğru bir 2D6 değeri çalıştırabilirsiniz. Ancak 10D6, 50D6, 100D6, 1000D6 ile başladıktan sonra çok fazla işlem süresi kazandırabilir. Bu yüzden bunu hızlı bir şekilde yapabilen bir öğretici / yöntem / algoritma olmalı? Büyük olasılıkla borsalar, casinolar, strateji oyunları, cüce kale vb.


5
1000 d6'da bile, döngü fark etmeyeceğiniz modern bir bilgisayarda yeterince hızlı olacaktır, bu nedenle bu erken optimizasyon olabilir. Şeffaf bir halkayı opak bir formülle değiştirmeden önce daima profil oluşturmayı deneyin. Bununla birlikte, algoritmik seçenekler var. Örneklerinizde zar gibi ayrık olasılıklarla ilgileniyor musunuz, yoksa bunları sürekli olasılık dağılımı olarak modellemek kabul edilebilir mi (2.5 gibi kesirli bir sonuç mümkün olabilir)?
DMGregory

DMGregory doğru, 1000d6 hesaplamak bir işlemci domuzu olmayacak. Ancak, adı verilen bir şey yoktur Binom Dağılımı (bazı akıllı çalışma ile) sonucu sen ilgilenen geçecektir. Ayrıca, hiç, denemek keyfi bir rulo kural seti için olasılıkları bulmak istiyorsanız Troll mütevazı bir dili vardır bir zar kümesinin nasıl yuvarlanacağını belirtmek için ayarlanır ve olası her sonuç için tüm olasılıkları hesaplar.
Draco18s artık SE

Poisson dağılımı kullanın: s.
Luis Masuelli

1
Herhangi bir zar seti yeterince sık yuvarlanırsa, muhtemelen bir dağılım eğrisi / histogram alırsınız. Bu önemli bir ayrım. Bir zar arka arkaya bir milyon 6s yuvarlanabilir, bu olası değil, ama olabilir
Richard Tingle

@RichardTingle Ayrıntılı olabilir misiniz? Bir dağıtım eğrisi / histogramında “arka arkaya milyon 6s” durumu da bulunur.
amitp

Yanıtlar:


16

Yukarıdaki yorumumda belirttiğim gibi, kodunuzu aşırı karmaşıklaştırmadan önce bunu profillemenizi öneririz. Hızlı fordö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:

2d6 için olasılık, kümülatif dağılım ve ters grafikler

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.

Dağıtımı arama tablosuna dönüştürme Takma Ad Yöntemi örneği

( Ö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:

  1. 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)

  2. 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 .

  3. 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);)


1
Müthiş cevap. Yine de naif yaklaşımı seviyorum. Hatalar için çok daha az yer ve anlaşılması kolay.
bummzack

Bu soru reddit üzerinde rastgele bir sorudan kopyala-yapıştır.
Vaillancourt

Ccompleteness için, bu @AlexandreVaillancourt hakkında konuşuyor reddit iplik olduğunu düşünüyorum. Buradaki cevaplar esas olarak ilmekli versiyonun korunmasını (zaman maliyetinin makul olabileceğine dair bazı kanıtlarla) veya normal / Gauss dağılımı kullanarak çok sayıda zarın yaklaştırılmasını önermektedir.
DMGregory

Takma ad yöntemi için +1, bu kadar az insan bunu biliyor gibi görünüyor ve bu tür olasılık seçim durumlarının çoğuna gerçekten ideal bir çözüm ve muhtemelen "daha iyi" olan Gauss çözümünden bahsetmek için +1 yalnızca performans ve yerden tasarruf sağlamayı önemsiyorsak cevap verin.
whn

0

Maalesef cevap, bu yöntemin performansta bir artışla sonuçlanmayacağıdır.

Rastgele bir sayının nasıl üretildiği sorusunda bazı yanlış anlaşılmalar olabileceğine inanıyorum. Aşağıdaki örneği alın [Java]:

Random r = new Random();
int n = 20;
int min = 1; //arbitrary
int max = 6; //arbitrary
for(int i = 0; i < n; i++){
    int randomNumber = (r.nextInt(max - min + 1) + min)); //silly maths
    System.out.println("Here's a random number: " + randomNumber);
}

Bu kod, 1 ile 6 (dahil) arasında rasgele sayılar yazdırmak için 20 kez döngü yapacaktır. Bu kodun performansı hakkında konuştuğumuzda, Random nesnesini oluşturmak için biraz zaman var (bu, oluşturulduğu sırada bilgisayarın dahili saatine bağlı olarak bir dizi rasgele rastgele tamsayı oluşturmayı içerir) ve ardından 20 sabit zaman her nextInt () çağrısında arama yapar. Her 'rulo' sabit bir zaman işlemi olduğundan, bu haddeleme işlemini çok ucuz hale getirir. Ayrıca min - max aralığının da önemli olmadığını unutmayın (başka bir deyişle, bir bilgisayarın bir d6'yı yuvarlaması, bir d10000'ü yuvarlaması kadar kolaydır). Zaman karmaşıklığı açısından konuşursak, çözeltinin performansı basitçe O (n) 'dir, burada n zar sayısıdır.

Alternatif olarak, tek bir d100 (veya bu konu için d10000) rulosu ile istediğiniz sayıda d6 rulosuna yaklaşabiliriz. Bu yöntemi kullanarak, yuvarlanmadan önce s [zardaki yüz sayısı] * n [zar sayısı] yüzdelerini hesaplamalıyız (teknik olarak s * n - n + 1 yüzdelerini ve kabaca bölebiliriz) simetrik olduğu için ikiye katlanır; 2d6 rulo simüle etme örneğinizde 11 yüzde ve 6'nın benzersiz olduğunu hesaplamışsınızdır). Yuvarladıktan sonra, rulonuzun hangi aralığa düştüğünü anlamak için bir ikili arama kullanabiliriz. Zaman karmaşıklığı açısından, bu çözüm O (s * n) çözümünü değerlendirir, burada s kenar sayısı ve n zar sayısıdır. Gördüğümüz gibi, bu bir önceki paragrafta önerilen O (n) çözeltisinden daha yavaştır.

Oradan tahmin ederek, 1000d20'lik bir ruloyu simüle etmek için bu programların her ikisini de oluşturduğunuzu varsayalım. Birincisi sadece 1000 kez yuvarlanacaktı. İkinci program, başka bir şey yapmadan önce 19.001 yüzdelerini (1.000 ila 20.000 potansiyel aralığı için) belirlemelidir. Bu nedenle, bellek aramalarının kayan nokta işlemlerinden çok daha pahalı olduğu garip bir sistemde değilseniz, her rulo için bir nextInt () çağrısı kullanmak yol gibi görünüyor.


2
Yukarıdaki analiz doğru değildir. Takma Ad Yöntemine göre olasılık ve takma ad tabloları oluşturmak için bir süre ön kenara koyarsak , sabit bir zamanda (2 rasgele sayı ve tablo araması) keyfi bir ayrı olasılık dağılımından örnek alabiliriz. Yani 5 zar veya 500 zarlık bir rulo simüle etmek, masalar hazırlandıktan sonra aynı miktarda işi gerektirir. Bu, her örnek için çok sayıda zarın ilmek yapmasından asimptotik olarak daha hızlıdır, ancak bu mutlaka soruna daha iyi bir çözüm oluşturmaz. ;)
DMGregory

0

Zar kombinasyonlarını saklamak istiyorsanız, iyi haber şu ki, bir çözüm var, kötü olan, bilgisayarlarımızın bu tür problemlerle ilgili olarak bir şekilde sınırlı olmasıdır.

Güzel haberler:

Bu sorunun kararlı bir yaklaşımı vardır:

1 / Zar grubunuzun tüm kombinasyonlarını hesaplayın

2 / Her kombinasyon için olasılığı belirleyin

3 / Bu listede zar atmak yerine bir sonuç arayın

Kötü haber:

Tekrarlama ile kombinasyon sayısı aşağıdaki formüllerle verilir

Γnk=(n+k-1k)=(n+k-1)!k! (n-1)!

( fransız wikipedia'dan ):

Tekrarlarla kombinasyon

Bu, örneğin, 150 dicesle ​​698'526'906 kombinasyonunuz olduğu anlamına gelir. Olasılığı 32 bit kayan nokta olarak sakladığınızı, 2,6 GB belleğe ihtiyacınız olduğunu ve yine de dizinler için bellek gereksinimi eklemeniz gerektiğini varsayalım ...

Hesaplama terimlerinde, kombinasyon numarası, kullanışlı ancak bellek kısıtlamalarını çözmeyen kıvrımlarla hesaplanabilir.

Sonuç olarak, çok sayıda zar için, her bir kombinasyonla ilişkili olasılıkları önceden hesaplamak yerine, zarları atmanızı ve sonucu gözlemlemenizi tavsiye ederim.

Düzenle

Bununla birlikte, sadece zarların toplamıyla ilgilendiğiniz için olasılıkları çok daha az kaynakla saklayabilirsiniz.

Evrişim kullanarak her zar toplamı için kesin olasılıkları hesaplayabilirsiniz.

Genel formül Fben(m)=ΣnF1(n)Fben-1(m-n)

Daha sonra her sonucun 1 / 6'sından 1 zarla başlayarak, istediğiniz sayıda zar için doğru olasılıkları oluşturabilirsiniz.

İşte gösterim için yazdım kaba bir java kodu (gerçekten optimize değil):

public class DiceProba {

private float[][] probas;
private int currentCalc;

public int getCurrentCalc() {
    return currentCalc;
}

public float[][] getProbas() {
    return probas;
}

public void calcProb(int faces, int diceNr) {

    if (diceNr < 0) {
        currentCalc = 0;
        return;
    }

    // Initialize
    float baseProba = 1.0f / ((float) faces);
    probas = new float[diceNr][];
    probas[0] = new float[faces + 1];
    probas[0][0] = 0.0f;
    for (int i = 1; i <= faces; ++i)
        probas[0][i] = baseProba;

    for (int i = 1; i < diceNr; ++i) {

        int maxValue = (i + 1) * faces + 1;
        probas[i] = new float[maxValue];

        for (int j = 0; j < maxValue; ++j) {

            probas[i][j] = 0;
            for (int k = 0; k <= j; ++k) {
                probas[i][j] += probability(faces, k, 0) * probability(faces, j - k, i - 1);
            }

        }

    }

    currentCalc = diceNr;

}

private float probability(int faces, int number, int diceNr) {

    if (number < 0 || number > ((diceNr + 1) * faces))
        return 0.0f;

    return probas[diceNr][number];

}

}

İstediğiniz parametrelerle calcProb () öğesini çağırın ve ardından sonuçlar için proba tablosuna erişin (ilk dizin: 1 zar için 0, iki zar için 1 ...).

Dizüstü bilgisayarımda 1'000D6 ile kontrol ettim, 1 ila 1'000 zar arasındaki tüm olasılıkları ve tüm olası zar sayısını hesaplamak 10 saniye sürdü.

Önceden hesaplama ve verimli depolama ile, çok sayıda zar için hızlı cevaplar alabilirsiniz.

Umarım yardımcı olur.


3
OP sadece zarların toplamının değerini aradığından , bu kombinatoryal matematik geçerli değildir ve olasılık tablosu girişlerinin sayısı zar büyüklüğü ve zar sayısı ile doğrusal olarak büyür.
DMGregory

Haklısın ! Cevabımı düzenledim. Biz her zaman çok zeki olduğumuzu;)
elenfoiro78

Böl ve fethet yaklaşımını kullanarak verimliliği biraz artırabileceğinizi düşünüyorum. 20d6 için olasılık tablosunu, tabloyu 10d6 için kendisiyle birleştirerek hesaplayabiliriz. 10d6 5d6 tablosunu kendisiyle birleştirerek bulabiliriz. 5d6, 2d6 ve 3d6 tablolarını kıvrık olarak bulabiliriz. Bu şekilde yarıya kadar ilerlemek, 1-20'den büyük masa boyutlarını oluşturmayı atlamamızı ve çabalarımızı ilginç olanlara odaklamamızı sağlar.
DMGregory

1
Ve simetri kullanın!
elenfoiro78
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.