java.util.Random이 실제로 무작위입니까? 52를 어떻게 생성 할 수 있습니까! (계통적) 가능한 순서?


202

Random (java.util.Random)52 장의 카드 덱을 섞는 데 사용 하고 있습니다. 52가 있습니다! (8.0658175e + 67) 가능성. 그러나, 나는 씨앗 java.util.Randomlong2 ^ 64 (1.8446744e + 19)에서 훨씬 작은 것을 발견했습니다 .

여기에서 나는 java.util.Random 정말 그 랜덤 인지 의심 스럽다 . 실제로 모든 52 개를 생성 할 수 있습니까? 가능성?

그렇지 않은 경우 52 개를 모두 생성 할 수있는 더 나은 무작위 시퀀스를 어떻게 안정적으로 생성 할 수 있습니까? 가능성?


21
" 52 개 이상의 실수를 어떻게 확실히 생성 할 수 있을까요 ?" 의 숫자 Random는 절대 실수 가 아닙니다 . PNG는 "의사"를 나타내는 PRNG입니다. 들어 실제 임의의 숫자, 당신은 (예 : random.org 등) 난수의 발생원을 필요로한다.
TJ Crowder

7
@JimGarrison 그것은 OP의 후가 아닙니다. 그는 10 ^ 68 개의 가능한 시퀀스에 대해 이야기하고 있습니다. 각 의사 랜덤 시퀀스는 시드로 식별되므로 OP는 최대 2 ^ 64 개의 서로 다른 시퀀스가있을 수 있다고 말합니다.
dasblinkenlight

6
나는 그것이 흥미로운 질문이라고 생각하고 생각할 가치가 있습니다. 그러나 나는 당신의 문제 상황에 대해 궁금해 할 수 없습니다 : 정확히 52 개의 것을 모두 생성 할 수있게하는 것이 무엇입니까! 순열? 예를 들어, 실제 브리지에서는 덱을 섞어서 한 번에 한 장의 카드를 처리 할 수 ​​있지만, 다른 순열이 같은 핸드를 가지므로 ~ 6e11 개의 다른 핸드 만 있습니다. 다른 방향으로 생각할 때 52에 대한 솔루션이 필요합니까, 아니면 두 개의 데크가 서로 섞여 있음 (104! / (2 ** 52) 가능성 또는 ~ 2e150)을 일반화하는 솔루션이 필요합니까?
NPE

9
@NPE-예를 들어 52 인 Solitaire (Klondike)를 사용하십시오! 가능한 손의 수입니다 ..
Serj Ardovic

3
나는 이것이 흥미있는 글이라고 생각한다 : superuser.com/a/712583
Dennis_E

답변:


153

랜덤 순열을 선택하려면 질문에서 의미하는 것보다 더 많은 랜덤이 필요합니다. 설명하겠습니다.

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

귀하의 접근 방식의 근본적인 결함은 64 비트의 엔트로피 (임의의 시드)를 사용하여 ~ 2 226 가능성 중에서 선택하려고한다는 것 입니다. ~ 2 226 개의 가능성 을 공정하게 선택하려면 64 대신 226 비트의 엔트로피를 생성하는 방법을 찾아야합니다.

랜덤 비트를 생성하는 방법에는 전용 하드웨어 , CPU 명령어 , OS 인터페이스 , 온라인 서비스 등 여러 가지가 있습니다 . 당신의 질문에는 이미 64 비트를 생성 할 수 있다는 암시 적 가정이 이미 있습니다. 따라서 4 번만 수행하고 초과 비트를 자선 단체에 기부하십시오. :)

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

226 개의 임의 비트가 있으면 나머지를 결정 론적으로 수행 할 수 있으므로 속성을 java.util.Random무의미하게 만들 수 있습니다 . 방법은 다음과 같습니다.

52 개를 모두 생성한다고 가정 해 봅시다! 순열 (나를 지니고)을 사전 식으로 정렬합니다.

순열 중 하나를 선택 모든 우리의 필요 사이에 단일 무작위 정수 052!-1. 그 정수는 우리의 226 비트 엔트로피입니다. 정렬 된 순열 목록에 대한 색인으로 사용합니다. 랜덤 인덱스가 균일하게 분포 된 경우 모든 순열을 선택할 수 있다고 보장 할뿐만 아니라 동등 하게 선택됩니다 (질문이 요구하는 것보다 더 강력한 보증 임).

이제 실제로 이러한 순열을 모두 생성 할 필요는 없습니다. 가상 정렬 목록에서 임의로 선택된 위치를 고려하여 직접 하나를 생성 할 수 있습니다. 이는 Lehmer [1] 코드를 사용하여 O (n 2 ) 시간 내에 수행 할 수 있습니다 ( 번호 매기기 순열계승 수 시스템 참조 ). 여기서 n은 갑판의 크기, 즉 52입니다.

StackOverflow 답변 에는 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] Lehrer 와 혼동하지 마십시오 . :)


7
Heh, 그리고 마지막 링크는 New Math 일 것이라고 확신했습니다 . :-)
TJ Crowder

5
@ TJCrowder : 그것은 거의했다! 그것을 흔들었던 것은 무한히 차별화되는 리만 매니 폴드였습니다. :-)
NPE

2
사람들이 고전을 고마워하는 것을 보게되어 기쁩니다. :-)
TJ Crowder

3
Java 에서 임의의 226 비트를 어디서 얻 습니까? 죄송합니다, 귀하의 코드는 그에 대한 답변을하지 않습니다.
Thorsten S.

5
Java Random ()은 64 비트의 엔트로피를 제공하지 않습니다. OP는 PRNG를 시드하기 위해 64 비트를 생성 할 수있는 지정되지 않은 소스를 의미합니다. 동일한 소스에 226 비트를 요청할 수 있다고 가정하는 것이 좋습니다.
Monica의

60

분석이 정확합니다. 특정 시드가있는 의사 난수 생성기를 시드하면 셔플 후 동일한 시퀀스를 생성해야하므로 얻을 수있는 순열의 수는 2 64로 제한 됩니다. 이 어설 션은 두 번 호출 하고 동일한 시드로 초기화 된 객체를 전달 하고 두 개의 임의 셔플이 동일한 지 관찰하여 실험적 으로 쉽게 확인할 수 있습니다.Collection.shuffleRandom

따라서 이에 대한 해결책은 더 큰 시드를 허용하는 난수 생성기를 사용하는 것입니다. Java는 사실상 무제한 크기의 배열 SecureRandom로 초기화 될 수있는 클래스를 제공합니다 byte[]. 당신은 인스턴스를 전달할 수 SecureRandom에 대한 Collections.shuffle작업을 완료하려면 :

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

8
분명히, 큰 씨앗이 모든 52 개를 보장한다고는 할 수 없습니다! 가능성이 생길까요 (이 질문이 구체적으로 무엇입니까)? 생각 실험으로, 임의로 큰 종자를 취하고 무한히 긴 일련의 0을 생성하는 병리학 적 PRNG를 고려하십시오. PRNG는 단지 충분히 큰 종자를 섭취하는 것보다 더 많은 요구 사항을 충족시켜야한다는 것이 분명해 보입니다.
NPE

2
@SerjArdovic 예. Java 문서에 따라 SecureRandom 오브젝트에 전달 된 시드 자료는 예측할 수 없어야합니다.
dasblinkenlight

10
@NPE 당신은 그렇습니다. 너무 작은 씨앗은 상한을 보장하지만, 충분히 큰 씨앗은 하한에서 보장되지 않습니다. 이 모든 것은 이론적 인 상한을 제거하여 RNG가 52 개를 모두 생성 할 수있게합니다! 조합.
dasblinkenlight

5
@SerjArdovic 필요한 최소 바이트 수는 29입니다 (52. 가능한 바이트 조합 인 28.25 바이트이므로 226 비트가 필요하므로 반올림해야합니다). 29 바이트의 시드 재료를 사용하면 하한을 설정하지 않고도 얻을 수있는 셔플 수에 대한 이론적 상한을 제거 할 수 있습니다 (매우 큰 시드를 취하고 모든 0의 시퀀스를 생성하는 크 래피 RNG에 대한 NPE의 의견 참조).
dasblinkenlight

8
SecureRandom구현은 거의 확실하게 기초 PRNG를 사용합니다. 그리고 그것은 PRNG의 기간 (그리고 그보다 작은 경우, 상태 길이)에 따라 52 개의 요인 순열 중에서 선택할 수 있는지에 달려 있습니다. (문서에 따르면 SecureRandom구현은 특정 통계 테스트를 "최소하게 준수"하고 "암호 적으로 강력해야하는"출력을 생성하지만 기본 PRNG의 상태 길이 또는 기간에 대한 명시적인 하한은 없습니다.
Peter O.

26

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

java.util.Random모듈러스가 2 48 인 알고리즘을 구현하고 ; 따라서 상태 길이는 48 비트이므로 내가 언급 한 226 비트보다 훨씬 적습니다. 상태 길이가 더 큰 다른 PRNG를 사용해야합니다. 특히주기가 52 배 이상인 PRNG를 사용해야합니다.

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

이 고려 사항은 PRNG의 특성과 무관합니다. 이는 암호화 및 비 암호화 PRNG에 동일하게 적용됩니다 (물론 정보 암호화가 관련 될 때마다 비 암호화 PRNG는 부적절합니다).


java.security.SecureRandom길이가 무제한 인 시드를 전달할 수 있지만 SecureRandom구현시 기본 PRNG (예 : "SHA1PRNG"또는 "DRBG")를 사용할 수 있습니다. 그리고 그것은 PRNG의 기간 (그리고 그보다 작은 경우, 상태 길이)에 따라 52 개의 요인 순열 중에서 선택할 수 있는지에 달려 있습니다. ( "상태 길이" 를 "PRNG가 해당 시드를 줄이거 나 압축하지 않고 상태를 초기화하기 위해 취할 수있는 시드의 최대 크기 "정의 합니다.)


18

이해하기 조금 어렵 기 때문에 미리 사과 드리겠습니다 ...

우선, 당신은 이미 java.util.Random완전히 무작위가 아님을 알고 있습니다. 종자에서 완벽하게 예측 가능한 방식으로 서열을 생성합니다. 시드의 길이는 64 비트이므로 2 ^ 64 개의 서로 다른 시퀀스 만 생성 할 수 있습니다. 어떻게 든 64 개의 실제 랜덤 비트를 생성하고이를 사용하여 시드를 선택했다면 해당 시드를 사용 하여 52 개 모두 를 임의로 선택할 수 없었습니다 ! 동일한 확률로 가능한 서열.

그러나이 사실은 중요하지의 당신이 실제로 한 것도 '특별한'또는 존재하는 한, 2 개 이상의 ^ 64 개 시퀀스를 생성하지 않을거야만큼 '눈에 띄게 특별한'2 ^ 64 시퀀스에 대해이 있다고 할 수 생성 .

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

어떤 이니셜 라이저가 어떤 것인지 모르는 경우 구별 할 수있는 테스트를 작성할 수 있습니까? 같은 64 비트로 나쁜 것을 초기화 할 정도로 운이 좋지 않으면 대답은 '아니요'입니다. 특정 PRNG 구현의 약점에 대한 자세한 지식이 없으면 두 이니셜 라이저를 구별 할 수 없습니다.

또는 Random클래스에 2 ^ 64 시퀀스의 배열이 있으며 먼 과거에 어느 시점에서 완전히 무작위로 선택되었으며 시드 가이 배열에 대한 색인 일뿐이라고 상상해보십시오 .

따라서 동일한 시드를 두 번 사용할 가능성이 크지 않다면Random 시드에 64 비트 만 사용 한다는 사실이 통계적으로 문제가되지는 않습니다.

물론, 암호화 목적 상, 시스템이 동일한 시드를 두 번 사용하도록하는 것은 계산 상 가능하기 때문에 64 비트 시드로는 충분하지 않습니다.

편집하다:

위의 모든 내용이 정확하더라도 실제 구현이 java.util.Random훌륭하지 않다는 것을 추가해야합니다 . 카드 게임을 작성하는 경우 MessageDigestAPI를 사용하여 의 SHA-256 해시를 생성하고 "MyGameName"+System.currentTimeMillis()해당 비트를 사용하여 덱을 섞습니다. 위의 주장에 따르면, 사용자가 실제로 도박을하지 않는 currentTimeMillis한 오래 반환 한다고 걱정할 필요가 없습니다 . 사용자 실제로 도박을하는 SecureRandom경우 시드없이 사용하십시오 .


6
@ThorstenS, 결코 올 수없는 카드 조합이 있는지 확인할 수있는 모든 종류의 테스트를 어떻게 작성할 수 있습니까?
Matt Timmermans

2
George Marsaglia의 Diehard 또는 Pierre L' Ecuyer / Richard Simard의 TestU01과 같은 난수 테스트 스위트는 랜덤 출력에서 ​​통계적 이상을 쉽게 찾을 수 있습니다. 카드 확인을 위해 두 개의 사각형을 사용할 수 있습니다. 카드 주문을 결정합니다. 첫 번째 사각형은 처음 두 카드의 위치를 ​​xy 쌍으로 표시합니다. 첫 번째 카드는 x이고 두 번째 카드의 차이 (!) 위치 (-26-25)는 y입니다. 두 번째 사각형은 두 번째 / 3에 상대적인 (-25-25)의 세 번째와 네 번째 카드를 보여줍니다. 일정 기간 동안 배포 를 실행하면 배포판의 간격과 클러스터가 즉시 표시 됩니다.
Thorsten S.

4
글쎄, 그것은 당신이 쓸 수 있다고 말한 테스트는 아니지만 적용되지 않습니다. 분포에서 그러한 테스트로 밝혀 질 격차와 클러스터가 있다고 가정하는 이유는 무엇입니까? 그것은 내가 언급 한 바와 같이 "PRNG 구현의 특정 약점"을 암시하며 가능한 시드 수와는 전혀 관련이 없습니다. 이러한 테스트는 발전기를 다시 시드 할 필요조차 없습니다. 처음에는 이것이 이해하기 어렵다고 경고했습니다.
Matt Timmermans

3
@ThorstenS. 이러한 테스트 스위트는 소스가 64 비트 시드 암호화 보안 PRNG인지 실제 RNG인지를 절대 판단 하지 않습니다 . (PRNG 테스트는 그 제품군을위한 것입니다.) 사용중인 알고리즘을 알고 있더라도 PRNG가 양호하면 상태 공간을 무차별 적으로 검색하지 않고도 상태를 확인할 수 없습니다.
Sneftel

1
@ThorstenS .: 실제 카드 덱에서는 대부분의 조합이 나타나지 않습니다. 당신은 그것들이 무엇인지 모릅니다. 절반 정도의 PRNG의 경우 동일합니다. 주어진 출력 시퀀스가 ​​이미지에 긴지 여부를 테스트 할 수 있다면 이는 PRNG의 결함입니다. 엄청나게 큰 주 / 기간 52와 같은! 필요하지 않다; 128 비트이면 충분합니다.
R .. GitHub 중지 지원 얼음

10

나는 이것에 대해 약간 다른 압정을 취할 것입니다. 당신은 당신의 가정에 맞습니다-당신의 PRNG는 52를 모두 칠 수 없을 것입니다! 가능성.

문제는 카드 게임의 규모는 무엇입니까?

간단한 클론 다이크 스타일 게임을 만들고 있다면? 그럼 당신은 확실히 모든 52 필요 하지 않습니다 ! 가능성. 대신, 다음과 같이보십시오 : 플레이어는 18 개의 quintillion 별개의 게임을 갖게됩니다 . 심지어 '생일 문제'를 설명하더라도, 그들은 첫 번째 복제 게임을 시작하기 전에 수십억의 손을 쳐야했습니다.

몬테카를로 시뮬레이션을하고 있다면? 그렇다면 당신은 아마 괜찮을 것입니다. PRNG의 'P'로 인해 인공물을 처리해야 할 수도 있지만 낮은 시드 공간으로 인해 문제가 발생하지 않을 수도 있습니다 (다시 말해서 고유 한 가능성의 5 분의 1을보고 있습니다). 반대로, 반복 횟수가 큰 작업을 수행하는 경우 시드 공간이 적을 때 거래가 중단 될 수 있습니다.

멀티 플레이어 카드 게임을하고 있다면, 특히 돈이 많이 있다면? 그런 다음 온라인 포커 사이트가 요청한 것과 동일한 문제를 어떻게 처리했는지에 대해 인터넷 검색을해야합니다. 낮은 시드 공간 문제는 일반적인 플레이어 에게는 눈에 띄지 않지만 시간 투자 가치가 있다면 악용 될 수 있습니다. (포커 사이트는 모두 PRNG가 '해킹'된 단계를 거치며 노출 된 카드에서 시드를 추론하여 다른 플레이어의 홀 카드를 볼 수있게합니다.) 이것이 현재 상황이라면, 돈 't는 당신은 암호화 문제로 심각하게 취급해야합니다 - 단순히 더 나은 PRNG를 찾을 수 있습니다.


9

본질적으로 dasblinkenlight와 동일한 짧은 솔루션 :

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

Collections.shuffle(deck, random);

내부 상태에 대해 걱정할 필요가 없습니다. 왜 긴 설명 :

SecureRandom이 방법으로 인스턴스 를 만들면 OS 별 실제 난수 생성기에 액세스합니다. 임의의 비트를 포함하는 값에 액세스하는 엔트로피 풀 (예 : 나노초 타이머의 경우 나노초 정밀도는 본질적으로 임의 임) 또는 내부 하드웨어 번호 생성기입니다.

여전히 가짜 트레이스를 포함 할 수있는이 입력 (!)은 해당 트레이스를 제거하는 강력한 암호화 해시로 공급됩니다. 그것이 바로 CSPRNG가 사용되는 이유입니다. 는 SecureRandom많은 비트가 (사용 된 방법을 추적 카운터 갖고 getBytes(), getLong()등) 및 리필 SecureRandom엔트로피 비트를 필요 .

한마디로 : 이의 제기를 잊고 SecureRandom진정한 난수 생성기로 사용하십시오.


4

숫자를 비트 (또는 바이트)의 배열로 간주하면 Random.nextBytes스택 오버플로 질문 에서 제안 된 (보안) 솔루션 을 사용한 다음 배열을에 매핑 할 수 new BigInteger(byte[])있습니다.


3

매우 간단한 알고리즘은 SHA-256을 0에서 증가하는 정수 시퀀스에 적용하는 것입니다. ( "다른 시퀀스를 찾기 '를 원한다면 염을 추가 할 수있다.) 우리는 SHA-256의 출력이 0에서 균일하게 분포 정수"좋은로서 "2라고 가정하면 256 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]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.