java.util 입니다.정말 그렇게 무작위로요?52! (팩토리얼) 가능한 시퀀스를 생성하려면 어떻게 해야 합니까?
쓰고 있어요.Random (java.util.Random)
.52장 52! (8.0658175e의 가능성이 있다.52! (8.0658175e+67)지지 for 의 씨앗이 것을 .java.util.Random
는 입니다.long
2^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
에서는, 」, 「시드」, 「시드」)를 건네줄 수 .SecureRandom
PRNG(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
멋있지 않아요. 게임을 게임을 할 수 .MessageDigest
SHA-256 API의 "MyGameName"+System.currentTimeMillis()
이 비트를 사용하여 덱을 섞습니다.논거에 도박을 하고 , 가 도박을 하고 있지 않다면, 도박을 하고 있다는 은 하지 currentTimeMillis
long을 반환합니다.사용자가 정말로 도박을 하고 있다면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
'programing' 카테고리의 다른 글
오류: 'optionalChaining' 실험 구문은 현재 지원되지 않지만 다음과 같습니다. (0) | 2022.08.11 |
---|---|
Eclipse .classpath / .프로젝트 파일에는 무엇이 있습니까? (0) | 2022.08.11 |
npm 빌드 실행 후 vuejs 빈 페이지 (0) | 2022.08.11 |
소켓 라이브러리에서 recv를 호출할 때 사용하는 recv 버퍼 크기 (0) | 2022.08.11 |
Vue.js에 중첩된 구성 요소: 구성 요소를 마운트하지 못했습니다. 템플릿 또는 렌더 함수가 정의되지 않았습니다. (0) | 2022.08.11 |