위의 의견에서 언급했듯이 코드를 복잡하게 만들기 전에 이것을 프로파일 링하는 것이 좋습니다. 빠른 for
루프 합산 주사위는 복잡한 수학 공식 및 테이블 작성 / 검색보다 이해하고 수정하기가 훨씬 쉽습니다. 중요한 문제를 해결하려면 항상 먼저 프로파일 링하십시오. ;)
즉, 한 번에 복잡한 확률 분포를 샘플링하는 두 가지 주요 방법이 있습니다.
1. 누적 확률 분포
하나의 균일 한 무작위 입력 만 사용하여 연속 확률 분포에서 샘플링하는 깔끔한 트릭 이 있습니다 . 그것은 함께 할 수있다 누적 분포 함수를 그 "값을 얻기없는 확률 무엇 답변 더 큰 X보다가?"
이 함수는 감소하지 않고 0에서 시작하여 도메인에서 1로 증가합니다. 6 면체 주사위 2 개를 합한 예는 다음과 같습니다.
누적 분포 함수에 계산하기 편리한 역수가있는 경우 (또는 베 지어 곡선과 같은 부분 함수로 근사 할 수있는 경우)이를 사용하여 원래 확률 함수에서 샘플링 할 수 있습니다.
역함수는 0에서 1 사이의 도메인을 원래 랜덤 프로세스의 각 출력에 매핑 된 간격으로 파 셀링하고 각 영역의 원래의 확률과 일치하는 포획 영역을 처리합니다. (이것은 연속 분포의 경우 무한히 사실입니다. 주사위 롤과 같은 이산 분포의 경우 신중한 반올림을 적용해야합니다)
이것을 사용하여 2d6을 에뮬레이트하는 예는 다음과 같습니다.
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);
}
이것을 다음과 비교하십시오.
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;
}
코드 선명도와 유연성의 차이에 대해 무엇을 의미합니까? 순진한 방법은 루프로 순진하지만 짧고 간단하며 작동 방식을 즉시 알 수 있으며 다양한 다이 크기와 수로 쉽게 확장 할 수 있습니다. 누적 배포 코드를 변경하려면 사소한 수학이 필요하며 명확한 실수없이 쉽게 깨져서 예기치 않은 결과가 발생할 수 있습니다. (내가 위에서 만들지 않았 으면 좋겠다)
따라서 명확한 루프를 없애기 전에 실제로 이런 종류의 희생에 가치가있는 성능 문제인지 반드시 확인하십시오.
2. 별칭 방법
누적 분포 함수의 역수를 간단한 수학 식으로 표현할 수있을 때 누적 분포 방법이 효과적이지만 항상 쉬운 것은 아닙니다. 불연속 분포의 신뢰할 수있는 대안 은 Alias Method 입니다.
이를 통해 두 개의 독립적으로 균일하게 분포 된 임의의 입력을 사용하여 임의의 이산 확률 분포에서 샘플링 할 수 있습니다.
왼쪽 아래의 것과 같은 분포를 취하고 ( 상대 가중치에 관심이있는 Alias Method의 경우 면적 / 무게가 1로 합산되지 않을까 걱정하지 마십시오 ) 다음과 같은 표로 변환하여 작동합니다 오른쪽 위치 :
- 각 결과에 대해 하나의 열이 있습니다.
- 각 열은 최대 두 부분으로 나뉘며 각 부분은 원래 결과 중 하나와 연결됩니다.
- 각 결과의 상대적 면적 / 무게가 유지됩니다.
( 샘플링 방법에 대한이 훌륭한 기사의 이미지를 기반으로 한 다이어그램 )
코드에서 우리는 각 열에서 대체 결과를 선택할 확률을 나타내는 두 개의 테이블 (또는 두 개의 속성을 가진 객체 테이블)과 대체 결과의 동일성 (또는 "별칭")으로이를 나타냅니다. 그런 다음 분포에서 표본 추출 할 수 있습니다.
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];
}
}
여기에는 약간의 설정이 포함됩니다.
가능한 모든 결과의 상대 확률을 계산하십시오 (따라서 1000d6을 굴릴 경우 1000에서 6000까지 모든 합계를 얻는 방법의 수를 계산해야합니다)
각 결과에 대한 항목으로 테이블 쌍을 작성하십시오. 전체 방법은이 답변의 범위를 벗어나므로 Alias Method 알고리즘에 대한이 설명을 참조하는 것이 좋습니다 .
이 분포에서 새로운 랜덤 다이 롤이 필요할 때마다 해당 테이블을 저장하고 다시 참조하십시오.
이것은 시공간 상충 관계 입니다. 사전 계산 단계는 다소 철저하며, 결과 수에 비례하여 메모리를 따로 보관해야합니다 (1000d6의 경우에도 한 자리 킬로바이트를 사용하므로 수면을 잃을 것은 없습니다). 분포가 아무리 복잡하더라도 상수 시간입니다.
나는 그 방법들 중 하나 또는 다른 방법이 다소 유용 할 수 있기를 바랍니다 (또는 순진한 방법의 단순성이 반복하는 데 가치가 있다고 확신합니다);)