C++11 난수(Random)
[The C++ Programming Language에서 발췌]
난수는 테스트, 게임, 시뮬레이션, 보안 등의 많은 상황에서 유용하다. <random>에서 표준 라이브러리가 제공하는 폭넓은 난수 생성기의 선택 폭은 이러한 응용 영역의 다양성을 대변하는 것이다. 난수 생성기는 두 부분으로 구성된다.
[1] 난수나 의사 난수 값의 시퀸스를 생성하는 엔진
[2] 이러한 값들을 일정 범위의 수학적 분포로 매피아는 분포
분포 예로는 uniform_int_distribution(생성되는 모든 정수가 동일한 확률을 갖는다.), normal_distribution('종형 곡선'), exponential_distribution(지수 성장 곡선)이 있는데, 각각은 어떤 특정한 범위를 나타낸다. 다음의 예를 살펴보자.
1 2 3 4 5 6 7 8 | using my_egine = default_random_engine; // 엔진의 타입 using my_distirbution = uniform_int_distribution<>; // 분포의 타입 my_engine re {}; my_distribution one_to_six {1, 6}; auto die = bind(one_to_six, re); // one_to_six(re)와 동일함 int x = die(); | cs |
표준 라이브러리 함수 bind()는 두 번째 인자를 인자로 해서 첫 번째 인자를 호출할 것이다. 따라서 die() 호출은 one_to_six(re)를 호출하는 것과 동일하다.
범용성과 성능에 대한 철저한 집중 덕택에 한 전문가는 표준 라이브러리의 난수 구성 요소를 '모든 난수 라이브러리가 궁극적으로 원하는 목표'라는 의견을 밝힌 바 있다. 하지만 표준 라이브러리의 난수 구성 요소가 '초심자에게 친숙'하다고 보기는 어려울 수 있다. using문은 기존의 방식을 좀 더 명확하게 표현해준다. 그 방법 대신에 단순히 다음과 같이 작성할 수도 있다
1 | auto die = bind(uniform_int_distribution<>{1, 6}, default_random_engine{}); | cs |
어떤 버전이 좀 더 이해하기 쉬운가는 전적으로 상황과 독자에 따라 다르다.
초심자에게는 난수 라이브러리에 대해 완벽하게 범용적인 인터페이스는 상당히 난관이 될 수 있다. 많은 경우에는 간단한 균등 난수 발생기로 시자개도 충분하다. 예를 들면 다음과 같다.
1 2 | Rand_int rnd {1, 10}; // [1:10]에 대한 난수 발생기를 만든다. int x = rnd(); // x에 [1:10]에 속하는 숫자다. | cs |
그러면 이것들을 어디에서 얻는단 말인가? die() 같은 건 Rand_int 클래스에서 얻어야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 | class Rand_int { private : default_random_engine re; uniform_int_distribution<> dist; public : Rand_int(int low, int high) : dist {low, high} {} const int operator()() { return dist(re); } } | cs |
이러한 정의는 여전히 '전문가 수준'이지만, Rand_int()는 C++ 초보자 코스에 첫 번째주에서도 활용할 수 있다. 다음의 예를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main() { Rand_int rnd {0, 4}; // 균등 난수 발생기를 만든다. vector<int> histogram(5); // 크기 5의 벡터를 만든다. for (int i = 0; i != histogram.size(); i++) ++histogram[rnd()]; // 히스토그램을 [0:4] 범위 숫자의 등장 빈도로 채운다. for (int i = 0; i != histogram.size(); i++) // 막대 그래프를 출력한다. { cout << i << '\t'; for (int j =0; j != histogram[i]; j++) cout << endl; } } | cs |
출력은 균등 분포다.
0 ******************************
1 ***************************
2 *********************
3 ***************************
4**************************
─────────────────────────────────
[예제 코드]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <iostream> #include <functional> #include <random> using std::cout; using std::endl; using std::bind; auto main(void) -> int { // 랜덤 디바이스 생성 std::random_device randDeivce; // 예측 불가능 난수 엔진 생성 및 랜덤 디바이스 세팅 std::default_random_engine randEngine{ randDeivce() }; // -3부터 3까지의 랜덤 정수 출력 std::uniform_int_distribution<int> dist1{ -3, 3 }; cout << dist1(randEngine) << endl; // 1.5부터 3.5까지의 랜점 실수 출력 std::uniform_real_distribution<double> dist2{ 1.5, 3.5 }; cout << dist2(randEngine) << endl; // 정규 분포 (평균, 표준 편차) std::normal_distribution<double> dist3{ 2.0, 1.0 }; cout << dist3(randEngine) << endl; // 베르누이 분포, (확률(0.0 ~ 1.0)), 성공하면 true를 실패하면 false를 반환함. std::bernoulli_distribution dist4{ 0.7 }; cout << dist4(randEngine) << endl; // 베르누이 분포, (시도 횟수, 확률(0.0 ~ 1.0)), 성공한 횟수를 반환 std::binomial_distribution<int> dist5{ 100, 0.5 }; cout << dist5(randEngine) << endl; // bind함수로 난수 클래스와 엔진을 하나로 묶는다. auto bindDist = bind(std::uniform_int_distribution<int> { -1, 1 }, randEngine); cout << bindDist() << endl; } | cs |