programing

java.util 입니다.정말 그렇게 무작위로요?52! (팩토리얼) 가능한 시퀀스를 생성하려면 어떻게 해야 합니까?

newsource 2022. 8. 11. 22:58

java.util 입니다.정말 그렇게 무작위로요?52! (팩토리얼) 가능한 시퀀스를 생성하려면 어떻게 해야 합니까?

쓰고 있어요.Random (java.util.Random).52장 52! (8.0658175e 가능성이 있다.52! (8.0658175e+67)지지 for 의 씨앗이 것을 .java.util.Random는 입니다.long2^64(1.8446744e+19)입니다.

나는 기서, from는, from from from from from from from from from from from from from from from from from from from 가 의심스럽다.java.util.Random 52!의 모든 가능성을 실제로 만들어 낼 수 있을까요?

그렇지 않다면 52!의 가능성을 모두 생성할 수 있는 더 나은 랜덤 시퀀스를 어떻게 확실하게 생성할 수 있을까요?

랜덤 순열을 선택하려면 질문이 암시하는 것보다 더 많은 랜덤성과 더 적은 랜덤성이 동시에 요구됩니다.제가 설명해 드릴게요.

나쁜 소식은 더 많은 무작위성이 필요하다는 것입니다.

접근법의 근본적인 결점은 엔트로피 64비트(랜덤 시드)를 사용하여226 최대 2개의 가능성 중 하나를 선택하려고 한다는 것입니다.~2가지226 가능성 중에서 공평하게 선택하기 위해서는 64비트가 아닌 226비트의 엔트로피를 생성하는 방법을 찾아야 합니다.

랜덤 비트를 생성하는 방법에는 전용 하드웨어, CPU 명령, OS 인터페이스, 온라인 서비스 등 여러 가지가 있습니다.질문에는 이미 64비트를 생성할 수 있다는 암묵적인 가정이 있습니다.그러니까 할 일을 4번만 하고 나머지 비트를 자선단체에 기부하세요.:)

좋은 소식은 무작위성이 적다는 것입니다.

이들 226개의 랜덤비트를 취득하면 나머지는 결정적으로 실행할 수 있기 때문에의 속성은 무관할 수 있습니다.방법은 이렇다.

예를 들어 52개의 순열을 모두 생성하고 사전 편찬으로 정렬합니다.

중 은 단 입니다.0 ★★★★★★★★★★★★★★★★★」52!-1이 정수는 엔트로피의 226비트입니다정렬된 순열 목록의 인덱스로 사용할 것입니다.랜덤 지수가 균일하게 분포되어 있으면 모든 순열을 선택할 수 있을 뿐만 아니라 적합하게 선택될 것입니다(이것은 질문보다 더 강력한 보장임).

이 모든 순열을 생성할 필요는 없습니다.가상 정렬 목록에서 임의로 선택한 위치에 따라 직접 제작할 수 있습니다.[1] 작업은 Lehmer 코드를 사용하여 O2(n) 시간 내에 수행할 수 있습니다(번호순열인자 번호 체계 참조).여기서 n은 갑판 크기, 즉 52입니다.

Stack Overflow 응답에는 C 구현이 있습니다.n=52에 대해 오버플로하는 정수 변수가 몇 개 있지만 Java에서는 다행히 사용할 수 있습니다.java.math.BigInteger나머지 계산은 거의 그대로 전사할 수 있습니다.

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] 레러와 혼동하지 말 것.:)

분석 결과는 정확합니다. 특정 시드를 포함하는 의사 난수 생성기를 시드하면 셔플 후 동일한 시퀀스를 생성해야 하므로 얻을 수 있는 순열 수를 2로 제한할64 수 있습니다.이 주장은 콜링에 의해 실험적으로 검증되기 쉽다.Collection.shuffle, , 패스Random동일한 시드로 초기화되고 두 개의 랜덤 셔플이 동일한지 관찰합니다.

이에 대한 해결책은 더 큰 시드를 허용하는 난수 생성기를 사용하는 것입니다.Java는 다음과 같이 초기화할 수 있는 클래스를 제공합니다.byte[]크기가 거의 무제한인 어레이. '보다 낫다'의 예를 들 수 .SecureRandom로로 합니다.Collections.shuffle업을완 완료: :

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

일반적으로 의사난수생성기(PRNG)는 최대 사이클 길이가 226비트 미만인 경우 52개 항목 목록의 모든 순열 중에서 선택할 수 없습니다.

java.util.Random는 계수가 2이고48 최대 사이클 길이가 48비트인 알고리즘을 실장하고 있습니다.이는 제가 참조한 226비트보다 훨씬 적은 수치입니다.사이클 길이가 큰 다른 PRNG, 특히 최대 사이클 길이가 52 요인 이상인 PRNG를 사용해야 합니다.

난수 생성기에 대한 기사의 "Shuffling"도 참조하십시오.

이 고려사항은 PRNG의 성격과는 무관하며 암호화 PRNG와 비암호화 PRNG에도 동일하게 적용됩니다(물론 비암호화 PRNG는 정보보안이 관여할 때마다 부적절합니다).


일일 ~일도 although although although although 。java.security.SecureRandom에서는, 」, 「시드」, 「시드」)를 건네줄 수 .SecureRandomPRNG(SHA1PRNG)는 DRBG입니다.또한 PRNG의 최대 주기 길이에 따라 52개의 요인 배열 중에서 선택할 수 있는지 여부가 결정됩니다.

미리 사과드립니다.이거는 조금 이해하기 어렵기 때문에...

여러분은 , 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아시다.java.util.Random전혀 무작위적이지 않습니다.씨앗에서 완벽하게 예측 가능한 방식으로 시퀀스를 생성합니다.시드의 길이는 64비트이므로 2^64개의 시퀀스만 생성할 수 있습니다.64개의 실제 랜덤 비트를 생성하여 시드 선택에 사용한다면 그 시드를 사용하여 52개의 가능한 모든 시퀀스 에서 동일한 확률로 랜덤으로 선택할 수 없습니다.

그러나 실제로 2^64 시퀀스 이상을 생성하지 않는 한 이 사실은 중요하지 않습니다. 단, 2^64 시퀀스에 대해 '특별한' 또는 '특기할 만한' 것이 없는 한 말입니다.

1000비트 시드를 사용한 PRNG가 훨씬 우수하다고 가정해 보겠습니다.초기화하는 방법은 두 가지가 있습니다.한 가지 방법은 시드 전체를 사용하여 초기화하는 것이고, 한 가지 방법은 시드를 초기화하기 전에 64비트로 해시하는 것입니다.

어떤 이니셜라이저가 어떤 것인지 모르는 경우, 어떤 테스트라도 작성해 주실 수 있습니까?같은 64비트의 불량 비트를 2회 초기화할 수 있는 (불행한) 경우를 제외하고, 대답은 "아니오"입니다.특정 PRNG 구현의 약점에 대한 자세한 지식이 없으면 2개의 이니셜라이저를 구별할 수 없습니다.

'아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아,아.Random클래스는 완전히 랜덤으로 선택된 2^64 시퀀스의 배열을 가지고 있으며, 시드는 이 배열의 인덱스일 뿐이었다.

그 은...Random는 같은 시드를 2회 사용할 가능성이 크지 않은 한 시드에 64비트만 사용하는 것은 실제로는 통계적으로 반드시 문제가 되지 않습니다.

물론 암호화 목적에서는 64비트 시드는 충분하지 않습니다. 왜냐하면 시스템에서 동일한 시드를 두 번 사용하는 것은 계산적으로 가능하기 때문입니다.

편집:

상기 내용이 모두 맞더라도 실제 구현은 다음과 같습니다.java.util.Random멋있지 않아요. 게임을 게임을 할 수 .MessageDigestSHA-256 API의 "MyGameName"+System.currentTimeMillis()이 비트를 사용하여 덱을 섞습니다.논거에 도박을 하고 , 가 도박을 하고 있지 않다면, 도박을 하고 있다는 은 하지 currentTimeMillislong을 반환합니다.사용자가 정말로 도박을 하고 있다면SecureRandom씨도 없이.

나는 이것에 대해 조금 다른 방침을 취할 것이다.당신의 추측은 옳습니다.당신의 PRNG는 52!의 가능성을 모두 충족시킬 수 없습니다.

문제는 카드 게임의 규모가 얼마나 되느냐는 것입니다.

간단한 클론다이크 스타일의 게임을 만들고 있다면?그럼 52!의 가능성을 모두 가질 필요는 없겠네요.대신, 이렇게 보세요: 한 선수는 1,800조 개의 다른 게임을 가질 것입니다.심지어 '생일 문제'를 설명하더라도, 그들은 첫 번째 복제 게임에 뛰어들기 전에 수십억 명의 손놀림을 해야 할 것이다.

몬테카를로 시뮬레이션을 한다면?그럼 괜찮을 거야PRNG의 'P'로 인해 아티팩트를 처리해야 할 수도 있지만 단순히 시드 공간이 부족하다고 해서 문제가 발생하는 것은 아닐 것입니다(또한 5천 가지의 고유한 가능성을 찾고 있습니다).반대로, 반복 횟수가 많은 경우 시드 공간이 적으면 거래가 깨질 수 있습니다.

멀티플레이어 카드 게임을 만들고 있다면, 특히 돈이 걸려 있다면?그런 다음 온라인 포커 사이트들이 당신이 질문한 것과 같은 문제를 어떻게 처리했는지에 대해 구글을 검색해야 합니다.평균적인 플레이어에게는 시드 공간 부족 문제가 에 띄지 않지만, 시간을 투자할 가치가 있다면 악용할 수 있기 때문이다(포커 사이트들은 모두 PRNG가 해킹되는 단계를 거쳤으며, 단순히 노출된 카드에서 시드를 추론함으로써 다른 모든 플레이어의 홀 카드를 볼 수 있도록 했다).이 경우 단순히 더 나은 PRNG를 찾는 것이 아니라 암호 문제만큼 심각하게 취급해야 합니다.

기본적으로 Dasblinkenlight와 동일한 짧은 솔루션:

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

Collections.shuffle(deck, random);

내부 상태는 걱정하지 않으셔도 됩니다.자세한 이유:

를 작성하는 SecureRandom이 방법으로 OS 고유의 진정한 난수 생성기에 액세스합니다.이는 랜덤 비트(예를 들어 나노초 타이머의 경우 나노초 정밀도는 기본적으로 랜덤)를 포함하는 값에 액세스하는 엔트로피 풀 또는 내부 하드웨어 번호 생성기입니다.

이 입력(!)은 여전히 스플리어스 트레이스를 포함할 수 있으며, 이러한 트레이스를 삭제하는 암호화적으로 강력한 해시에 공급됩니다.그렇기 때문에 CSPRNG가 사용되는 것이지, 그 번호 자체를 작성하기 위한 것이 아닙니다.SecureRandom(「」 「」 「」 「」 「」getBytes(),getLong()기타) 및 필요에 따라 엔트로피 비트로 재충전합니다.

는 잊고 '하다'를 하세요.SecureRandom진정한 난수 생성기로 사용할 수 있습니다.

숫자를 비트(또는 바이트)의 배열로만 간주할 경우 (Secure)를 사용할 수 있습니다.Random.nextBytes스택 오버플로우 질문에 제시된 솔루션을 사용하여 어레이를 매핑합니다.new BigInteger(byte[]).

매우 간단한 알고리즘은 SHA-256을 0에서 위로 증가하는 정수 시퀀스에 적용하는 것입니다.(다른 시퀀스를 얻으려면 소금을 추가할 수 있습니다.)SHA-256의 출력이 0 ~2256 - 1 사이의 균등하게 분포된 정수만큼 좋다고 가정하면 작업에 충분한 엔트로피를 얻을 수 있습니다.

SHA256의 출력(정수로 표현되는 경우)에서 치환을 얻으려면 모듈로 52, 51, 50을 줄이면 됩니다.다음 의사 코드에서와 같습니다.

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]

나의 경험적 연구 결과는 자바이다.랜덤은 완전히 랜덤이 아닙니다.랜덤 클래스 "nextGaush()" 방법을 사용하여 -1과 1 사이의 숫자에 대해 충분히 큰 샘플 모집단을 생성하는 경우 그래프는 가우스 모델이라고 하는 정규 분산 필드입니다.

핀란드 정부 소유의 도박 북마커는 1년 내내 매일 추첨을 통해 하루에 한 번꼴로 복권 게임을 합니다. 당첨표는 북마커가 정상적으로 분산된 방식으로 당첨금을 주는 것을 보여줍니다.500만 추첨을 사용한 Java 시뮬레이션에서는 nextInt() -method에서 사용된 숫자 추첨을 사용하면 당첨이 보통 내 북마크에서 당첨금을 받는 것처럼 분배된다는 것을 알 수 있습니다.

제가 뽑은 최고의 선택은 각각 엔딩에서 숫자 3과 숫자 7을 피하는 것입니다. 그리고 그것은 그들이 거의 승리하지 못하는 것이 사실입니다.1에서 70 사이의 정수(Keno)에서 한 열에 3과 7개의 숫자를 피함으로써 5개의 선택 중 5개를 획득했습니다.

핀란드 복권은 일주일에 한 번 토요일 저녁에 추첨된다 39개의 숫자 중 12개의 숫자를 가지고 시스템 게임을 한다면, 아마도 3과 7의 값을 피함으로써 쿠폰에서 5, 6개의 올바른 선택을 할 수 있을 것이다.

핀란드 복권은 1~40번까지 선택할 수 있으며, 12번 체계로 모든 번호를 커버하려면 쿠폰이 4장 필요합니다.총 비용은 240유로이고 장기적으로는 레귤러 도박사가 파산하지 않고 게임을 하기에는 너무 비싸다.다른 고객에게 쿠폰을 나눠서 사더라도 수익을 내고 싶다면 운이 좋아야 한다.

언급URL : https://stackoverflow.com/questions/51771206/is-java-util-random-really-that-random-how-can-i-generate-52-factorial-possi