Java.util.Random gerçekten rastgele mi? Nasıl 52 üretebilirim! (faktöriyel) olası diziler?


203

Random (java.util.Random)52 kartlık bir desteyi karıştırmak için kullanıyorum . 52 var! (8.0658175e + 67) olasılıkları. Oysa ben için tohum olduğunu öğrendim java.util.Randombir olduğunu long2 ^ 64 (1.8446744e + 19) de çok daha küçük olan.

Buradan java.util.Random , gerçekten rastgele olup olmadığından şüpheleniyorum ; aslında 52 tane üretebiliyor mu? olasılıkları?

Değilse, 52'nin tümünü üretebilecek daha iyi bir rastgele dizi nasıl güvenilir bir şekilde oluşturabilirim! olasılıkları?


21
diyerek şöyle devam etti: " 52'den fazla gerçek bir rastgele sayıyı nasıl oluşturabilirim !" Den gelen sayılar Randomasla gerçek rastgele sayılar değildir. Bu bir PRNG'dir, burada P "sahte" anlamına gelir. İçin gerçek rasgele sayılar, (örneğin random.org gibi) rastgelelik kaynağı gerekir.
TJ Crowder

7
@JimGarrison OP'nin peşinde olduğu şey bu değil. 10 ^ 68 olası diziden bahsediyor. Her sahte rastgele sekans tohumuyla tanımlandığından OP en fazla 2 ^ 64 farklı sekans olabileceğini söylüyor.
dasblinkenlight

6
Bence bu ilginç bir soru ve düşünmeye değer. Ancak sorun bağlamınızı merak etmeye yardımcı olamıyorum: 52'nin tamamını üretme gereksiniminin tam olarak ne olduğunu! permütasyon? Örneğin, gerçek dünya köprüsünde desteyi karıştırıp bir seferde bir kart dağıtabiliriz, ancak birçok farklı permütasyon aynı el ile sonuçlandığından sadece ~ 6e11 farklı el vardır. Diğer yönde düşünmek gerekirse, 52!
NPE

9
@NPE - Solitaire'i (Klondike) ele alalım örneğin 52! olası el sayısıdır ..
Serj Ardovic

3
Bunun ilginç bir okuma olduğunu düşünüyorum: superuser.com/a/712583
Dennis_E 10:08

Yanıtlar:


153

Rastgele bir permütasyon seçmek, aynı anda sorunuzun ima ettiğinden daha fazla ve daha az rastgele olmalıdır. Açıklamama izin ver.

Kötü haber: daha fazla rastgelelik gerekir.

Yaklaşımınızdaki temel kusur, 64 bit entropi (rastgele tohum) kullanarak ~ 226 olasılık arasında seçim yapmaya çalışmasıdır . ~ 2222 olasılık arasında adil bir seçim yapmak için 64 yerine 226 bit entropi üretmenin bir yolunu bulmanız gerekecektir.

Rastgele bitler üretmenin birkaç yolu vardır: özel donanım , CPU talimatları , işletim sistemi arabirimleri , çevrimiçi hizmetler . Sorunuzda bir şekilde 64 bit oluşturabileceğiniz konusunda örtük bir varsayım var, bu yüzden ne yapacaksanız yapın, sadece dört kez yapın ve fazla bitleri sadaka bağışlayın. :)

İyi haber: daha az rastgelelik gerekir.

Bu 226 rastgele bite sahip olduğunuzda, geri kalanı deterministik olarak yapılabilir ve böylece özellikleri java.util.Randomilgisiz hale getirilebilir . İşte böyle.

Diyelim ki 52 tane oluşturuyoruz! permütasyonlar (benimle birlikte) ve sözlükbilimsel olarak sıralayın.

Permütasyon birini seçmek için hepimiz ihtiyacı arasında tek rasgele tamsayıdır 0ve 52!-1. Bu tam sayı, bizim 226 bit entropi. Sıralı permütasyon listemize bir indeks olarak kullanacağız. Rastgele endeks eşit dağıtılırsa, sadece onlar seçilecektir, tüm permütasyon seçilebilir olduğu garanti edilir equiprobably (soru soran olandan daha güçlü bir garanti olan).

Şimdi, tüm bu permütasyonları oluşturmanıza gerek yok. Varsayımsal sıralama listemizdeki rastgele seçilen konumu göz önüne alındığında doğrudan bir tane üretebilirsiniz. Bu Lehmer [1] kodu kullanılarak O (n 2 ) zamanında yapılabilir ( numaralandırma permütasyonlarına ve factoriadic sayı sistemine de bakınız ). Buradaki n, destenizin boyutu, yani 52'dir.

Bu StackOverflow yanıtında bir C uygulaması var . Orada n = 52 için taşacak birkaç tamsayı değişkeni vardır, ancak neyse ki Java'da kullanabilirsiniz java.math.BigInteger. Hesaplamaların geri kalanı neredeyse olduğu gibi kopyalanabilir:

public static int[] shuffle(int n, BigInteger random_index) {
    int[] perm = new int[n];
    BigInteger[] fact = new BigInteger[n];
    fact[0] = BigInteger.ONE;
    for (int k = 1; k < n; ++k) {
        fact[k] = fact[k - 1].multiply(BigInteger.valueOf(k));
    }

    // compute factorial code
    for (int k = 0; k < n; ++k) {
        BigInteger[] divmod = random_index.divideAndRemainder(fact[n - 1 - k]);
        perm[k] = divmod[0].intValue();
        random_index = divmod[1];
    }

    // readjust values to obtain the permutation
    // start from the end and check if preceding values are lower
    for (int k = n - 1; k > 0; --k) {
        for (int j = k - 1; j >= 0; --j) {
            if (perm[j] <= perm[k]) {
                perm[k]++;
            }
        }
    }

    return perm;
}

public static void main (String[] args) {
    System.out.printf("%s\n", Arrays.toString(
        shuffle(52, new BigInteger(
            "7890123456789012345678901234567890123456789012345678901234567890"))));
}

[1] Lehrer ile karıştırılmamalıdır . :)


7
Heh, ve sonunda bağlantının Yeni Matematik olacağından emindim . :-)
TJ Crowder

5
@TJCrowder: Neredeyse öyleydi! Onu sallayan sonsuz farklılaşabilen Riemann manifoldlarıydı. :-)
NPE

2
Klasikleri takdir eden insanları görmek güzel. :-)
TJ Crowder

3
Java'da rasgele 226 bitleri nereden edinebilirsiniz ? Üzgünüz, kodunuz buna cevap vermiyor.
Thorsten S.

5
Ne demek istediğinizi anlamıyorum, Java Random () 64 bit entropi sağlamaz. OP, PRNG'yi tohumlamak için 64 bit üretebilen belirtilmemiş bir kaynak anlamına gelir. Aynı kaynağı 226 bit için sorabileceğinizi varsaymak mantıklıdır.
Monica

60

Analiziniz doğrudur: herhangi bir belirli tohumla sözde rasgele bir sayı üretecini tohumlamak, bir karıştırmadan sonra aynı sırayı vermeli ve elde edebileceğiniz permütasyon sayısını sınırlamalıdır 2 64 . Bu iddianın, iki kez çağırarak , aynı tohumla başlatılan bir nesneyi geçirerek ve iki rastgele karıştırmanın aynı olduğunu gözlemleyerek deneysel olarak doğrulanması kolaydır .Collection.shuffleRandom

Bunun için bir çözüm, daha büyük bir tohuma izin veren rastgele bir sayı üreteci kullanmaktır. Java, neredeyse sınırsız boyut dizisiyle SecureRandombaşlatılabilecek bir sınıf sağlar byte[]. Arasında Ardından bir örneğini geçebileceği SecureRandomiçin Collections.shufflegörevi tamamlamak için:

byte seed[] = new byte[...];
Random rnd = new SecureRandom(seed);
Collections.shuffle(deck, rnd);

8
Elbette, büyük bir tohum 52'nin hepsinin bir garantisi değildir! olasılıklar üretilecekti (bu soru özellikle bununla ilgilidir)? Bir düşünce deneyi olarak, keyfi olarak büyük bir tohum alan ve sonsuz uzunluğunda sıfır serisi üreten patolojik bir PRNG'yi düşünün. PRNG'nin sadece yeterince büyük bir tohum almaktan daha fazla gereksinimi karşılaması gerektiği oldukça açık görünüyor.
NPE

2
@SerjArdovic Evet, bir SecureRandom nesnesine iletilen tüm tohum materyalleri Java belgelerine göre öngörülemez olmalıdır.
dasblinkenlight

10
@NPE Haklısın, çok küçük bir tohum üst sınırın bir garantisi olmasına rağmen, alt sınırda yeterince büyük bir tohum garanti edilmez. Bütün bunlar teorik bir üst limitin kaldırılması, RNG'nin 52'nin tümünü üretmesini mümkün kılıyor! kombinasyonları.
dasblinkenlight

5
@SerjArdovic Bunun için gereken en az bayt sayısı 29'dur (52! Olası bit kombinasyonunu temsil etmek için 226 bite ihtiyacınız vardır, bu da 28,25 bayttır, bu yüzden yuvarlamalıyız). 29 bayt tohum materyali kullanmanın, alt limit belirlemeden elde edebileceğiniz karıştırma sayısı üzerindeki teorik üst limiti ortadan kaldırdığını unutmayın (NPE'nin çok büyük bir tohum alan ve tüm sıfırların bir dizisini oluşturan berbat RNG hakkındaki yorumuna bakın).
dasblinkenlight

8
SecureRandomUygulama neredeyse kesinlikle altta yatan PRNG kullanacaktır. Ve 52 faktöriyel permütasyon arasından seçim yapıp yapamayacağı PRNG dönemine (ve daha az ölçüde devlet uzunluğuna) bağlıdır. (Belgelerin, SecureRandomuygulamanın belirli istatistiksel testlere "minimum düzeyde uyduğunu ve" kriptografik olarak güçlü olması gereken "çıktılar oluşturduğunu, ancak alttaki PRNG'nin durum uzunluğu veya süresi üzerinde açık bir alt sınır koymadığını söylediğini unutmayın .)
Peter O.

26

Genel olarak, bir sözde sayı üreteci (PRNG), eğer durum uzunluğu 226 bitten azsa, 52 maddelik bir listenin tüm permütasyonları arasından seçim yapamaz.

java.util.Random2 48 modülü ile bir algoritma uygular ; dolayısıyla durum uzunluğu sadece 48 bittir, bahsettiğim 226 bitten çok daha azdır. Daha büyük bir devlet uzunluğuna sahip başka bir PRNG kullanmanız gerekecektir - özellikle de 52 faktöriyel veya daha büyük bir periyotla.

Ayrıca rastgele sayı üreteçleri ile ilgili makalemdeki "Karıştırma" bölümüne bakın .

Bu düşünce PRNG'nin doğasından bağımsızdır; kriptografik ve kriptografik olmayan PRNG'ler için de aynı şekilde geçerlidir (elbette, kriptografik olmayan PRNG'ler bilgi güvenliği söz konusu olduğunda uygun değildir).


java.security.SecureRandomSınırsız uzunlukta tohumların geçmesine izin vermesine rağmen , SecureRandomuygulama altta yatan bir PRNG (örneğin, "SHA1PRNG" veya "DRBG") kullanabilir. Ve 52 faktöriyel permütasyon arasından seçim yapıp yapamayacağı PRNG dönemine (ve daha az ölçüde devlet uzunluğuna) bağlıdır. ( "Durum uzunluğu" nu "PRNG'nin o tohumu kısaltmadan veya sıkıştırmadan durumunu başlatmak için alabileceği maksimum tohum boyutu " olarak tanımladığımı unutmayın ).


18

Önceden özür dileyeyim, çünkü bunu anlamak biraz zor ...

Her şeyden önce java.util.Random, bunun tamamen rastgele olmadığını zaten biliyorsunuz . Tohumdan mükemmel tahmin edilebilir bir şekilde sekanslar üretir. Tohumun sadece 64 bit uzunluğunda olduğundan, sadece 2 ^ 64 farklı sekans üretebildiğinden tamamen haklısınız. Bir şekilde 64 gerçek rastgele bit üretecek ve bunları bir tohum seçmek için kullanacak olsaydınız, bu tohumu 52'nin tümü arasından rastgele seçim yapmak için kullanamazsınız ! eşit olasılıkla olası diziler.

Ancak, bu gerçektir hiçbir sonuç aslında sürece hiçbir şey 'özel' ya da orada olduğu gibi fazla 2 ^ 64 dizileri üretmek için gitmiyoruz olduğu sürece 'belirgin özel' 2 ^ 64 dizileri hakkında o olabilir oluşturmak .

1000 bit tohum kullanan çok daha iyi bir PRNG'ye sahip olduğunuzu varsayalım. Başlatırken iki yolun olduğunu düşünün - bir yol tüm tohumu kullanarak onu başlatacak ve bir yol, onu başlatmadan önce tohumu 64 bite kadar azaltacaktır.

Hangi başlatıcının hangisi olduğunu bilmiyorsanız, ayırt etmek için herhangi bir test yazabilir misiniz? Aynı 64 bit ile kötü olanı iki kez başlatmak için yeterince şanslı olmadığınız sürece , cevap hayırdır. Belirli PRNG uygulamasındaki bazı zayıflıklar hakkında ayrıntılı bilgi sahibi olmadan iki başlatıcıyı ayırt edemezsiniz.

Alternatif olarak, Randomsınıfın uzak geçmişte bir zamanda tamamen ve rastgele seçilmiş 2 ^ 64 sekanslı bir diziye sahip olduğunu ve tohumun sadece bu dizinin bir indeksi olduğunu düşünün .

Bu nedenle, Randomtohum için sadece 64 bit kullanan gerçeği , aynı tohumu iki kez kullanmanız için önemli bir şans olmadığı sürece, istatistiksel olarak bir sorun olmak zorunda değildir .

Tabii ki, kriptografik amaçlar için, 64 bitlik bir tohum yeterli değildir, çünkü aynı tohumu iki kez kullanmak için bir sistem elde etmek hesaplamalı olarak mümkündür.

DÜZENLE:

Yukarıdakilerin hepsi doğru olsa da, gerçek uygulamasının java.util.Randomharika olmadığını eklemeliyim . Bir kart oyunu yazıyorsanız, belki MessageDigestSHA-256 karmasını oluşturmak için API'yı "MyGameName"+System.currentTimeMillis()kullanın ve desteyi karıştırmak için bu bitleri kullanın. Yukarıdaki argümanla, kullanıcılarınız gerçekten kumar oynamadıkları sürece, bunun uzun süre currentTimeMillisdöndüğünden endişelenmenize gerek yoktur . Kullanıcıların Eğer edilir gerçekten kumar, sonra da kullanmak SecureRandomhiçbir tohum ile.


6
@ThorstenS, asla gelemeyen kart kombinasyonları olduğunu belirleyebilecek her türlü testi nasıl yazabilirsiniz?
Matt Timmermans

2
George Marsaglia'dan Diehard veya Pierre L'Ecuyer / Richard Simard'dan TestU01 gibi rastgele çıktılarda istatistiksel anomalileri kolayca bulan birkaç rasgele sayı test paketi vardır. Kart kontrolü için iki kare kullanabilirsiniz. Kart sırasını siz belirlersiniz. İlk kare ilk iki kartın konumunu xy çifti olarak gösterir: İlk kart x olarak ve ikinci kartın fark (!) Konumu (-26-25) y olarak gösterilir. İkinci kare 3. ve 4. kartları 2. / 3. sayılara göre (-25-25) gösterir. Bu, bir süre çalıştırırsanız dağıtımınızdaki hemen boşlukları ve kümeleri gösterecektir .
Thorsten S.

4
Bu, yazabileceğinizi söylediğin test değil, aynı zamanda geçerli değil. Neden dağıtımda bu tür testlerin ortaya çıkacağı boşluklar ve kümeler olduğunu varsayıyorsunuz? Bu, bahsettiğim gibi "PRNG uygulamasında spesifik bir zayıflık" anlamına gelir ve olası tohumların sayısıyla hiçbir ilgisi yoktur. Bu testler jeneratörü yeniden beslemenizi bile gerektirmez. Başlangıçta bunun anlaşılması zor olduğu konusunda uyardım.
Matt Timmermans

3
@ThorstenS. Bunlar deney düzenekleri kesinlikle olacak değil kaynak 64 bit tohumlanmamış kriptografik PRNG veya gerçek bir RNG olup olmadığını belirlemek. (Sonuçta PRNG'leri test etmek, bu süitlerin ne için olduğunu.) Kullanımdaki algoritmayı bilseniz bile, iyi bir PRNG, durum alanını kaba kuvvet araması olmadan durumun belirlenmesini olanaksız hale getirir.
Sneftel

1
@ThorstenS .: Gerçek bir kart destesinde, kombinasyonların büyük çoğunluğu asla ortaya çıkmayacak. Bunların hangileri olduğunu bilmiyorsun. Yarı iyi bir PRNG için aynıdır - belirli bir çıkış dizisinin görüntüsünde uzun olup olmadığını test edebiliyorsanız, bu PRNG'deki bir kusurdur. 52 gibi gülünç büyük devlet / dönem! Gerek yok; 128-bit yeterli olmalıdır.
R .. GitHub BUZA YARDIMCI DURDUR

10

Bu konuda biraz farklı bir yol izleyeceğim. Varsayımlarınız konusunda haklısınız - PRNG'niz 52'nin tümünü vuramayacak! olasılık.

Soru şu: Kart oyununuzun ölçeği nedir?

Basit bir klondike tarzı oyun yapıyorsanız? O zaman kesinlikle 52'ye ihtiyacınız yok ! olasılık. Bunun yerine şuna şöyle bakın: Bir oyuncunun 18 quintillion farklı oyunu olacak. 'Doğum Günü Sorunu'nu hesaba katsa bile, ilk kopya oyuna girmeden önce milyarlarca eli oynamak zorunda kalacaklardı.

Monte-carlo simülasyonu yapıyorsanız? O zaman muhtemelen iyisin . PRNG'deki 'P' nedeniyle eserler ile uğraşmak zorunda kalabilirsiniz, ancak muhtemelen sadece düşük tohum alanı nedeniyle sorunlara maruz kalmayacaksınız (yine, benzersiz olasılıkların beşte birlik kısmına bakıyorsunuz.) büyük bir iterasyon sayımı ile çalışıyorsanız, o zaman evet, düşük tohum alanınız bir anlaşma kırıcı olabilir.

Çok oyunculu bir kart oyunu yapıyorsanız, özellikle hatta para varsa? Ardından, çevrimiçi poker sitelerinin sorduğunuz aynı sorunu nasıl ele aldığını öğrenmek için biraz googling yapmanız gerekecek. Çünkü düşük tohum alanı sorunu ortalama bir oyuncu tarafından fark edilemese de , zaman yatırımına değerse sömürülebilir . (Poker siteleri hepsi, sadece açıkta kartlarından tohum deducing tarafından birisi tüm diğer oyuncuların delik kartları görmek icar, kendi PRNGs vardı 'hack' bir dönemden geçti.) Bu içinde bulunduğumuz durum ise don 't bir Kripto problemi kadar ciddiye tedavi etmek gerekir - sadece daha iyi bir PRNG bulabilirsiniz.


9

Dasblinkenlight ile aynı olan kısa çözüm:

// Java 7
SecureRandom random = new SecureRandom();
// Java 8
SecureRandom random = SecureRandom.getInstanceStrong();

Collections.shuffle(deck, random);

İç durum hakkında endişelenmenize gerek yok. Neden uzun açıklama:

SecureRandomBu şekilde bir örnek oluşturduğunuzda , bir işletim sistemine özgü gerçek rasgele sayı üretecine erişir. Bu, ya rasgele bitler içeren değerlere erişilen bir entropi havuzu (örneğin bir nanosaniye zamanlayıcı için nanosaniye hassasiyeti esasen rastgele) veya bir dahili donanım numarası üretecidir.

Hala sahte izler içerebilen bu girdi (!), Bu izleri kaldıran kriptografik olarak güçlü bir karmaya beslenir. Bu CSPRNG'lerin kullanılmasının nedeni budur, bu sayıları kendileri oluşturmak için değil! SecureRandomBir çok bitleri (kullanılan nasıl izleri sayaç vardır getBytes(), getLong()vs.) ve doldurma SecureRandomentropi bit gereklidir .

Kısacası: İtirazları unutun ve SecureRandomgerçek rastgele sayı üreteci olarak kullanın .


4

Sayıyı yalnızca bir bit (veya bayt) dizisi olarak görürseniz Random.nextBytes, bu Yığın Taşması sorusunda önerilen (Güvenli) çözümleri kullanabilir ve diziyi a ile eşleştirebilirsiniz new BigInteger(byte[]).


3

Çok basit bir algoritma SHA-256'yı 0'dan yukarı doğru artan bir tamsayı dizisine uygulamaktır. ( "Farklı bir dizi almak için" arzu edildiği takdirde bir tuz eklenebilir.) Biz SHA-256 çıktısı 0 ile eşit dağıtılmış tamsayılar "olarak da iyi olarak" 2 olduğunu varsayarsak 256 - 1 ise biz için yeterli entropisi görev.

SHA256'nın çıktısından (tamsayı olarak ifade edildiğinde) bir permütasyon elde etmek için sadece bu sahte kodda olduğu gibi modulo 52, 51, 50 ... azaltması gerekir:

deck = [0..52]
shuffled = []
r = SHA256(i)

while deck.size > 0:
    pick = r % deck.size
    r = floor(r / deck.size)

    shuffled.append(deck[pick])
    delete deck[pick]
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.