c++식 랜덤값 얻기의 개선안
Project/Win32API로 던그리드 모작

c++식 랜덤값 얻기의 개선안

Woon2World :: Programming & Art Life

 

 

std::default_random_engine을 선언해놓고

std::uniform_int_distribution 객체를 생성해 operator()의 인자로 주는 기존 c++스타일 랜덤은 몇 가지 한계가 있다

  • 가장 중요한 이유로, std::default_random_engine은 용량이 커 전역 객체인 편이 나은데, 이 전역 객체는 코드를 유지보수하기 어렵게 만든다. 전역 객체는 어디에서나 접근 가능하기 때문에 오류나 수정 사항이 발생하면 모든 코드를 검토해야 하기 때문이다
    • 심지어는, 여러 개의 헤더 파일로 이루어진 코드에서 extern 키워드를 통해 전역 객체에 접근하는 경우, 링크 순서가 꼬이면 도무지 이해 할 수 없는 디버그 메시지를 내뱉는 링크 에러의 지옥에 빠지게 된다
  • 사용자가 객체의 이름을 마음대로 정할 수 있기 때문에 객체의 이름을 항상 외우고 있어야 하며, 코드의 통일성이 부족해진다
  • 한 번에 여러 개의 랜덤값이 필요한 경우, 반복문의 사용이 필수적이다

 

 

 

개선된 랜덤 함수를 통해 조금 더 코드를 작성하기 쉬워진다

 

  • const int random_value(const std::uniform_int_distribution<>& uid)
    설명 : std::uniform_int_distribution 분포의 랜덤값을 한 개 생성하여 반환한다
    예시
    •  0, 100 사이의 난수 생성
      std::uniform_int_distribution uid{ 0, 100 };
      int n = random_value(uid);

    • 50, 200 사이의 난수 생성
      std::uniform_int_distribution<> uid2{ 50, 200 };
      int m = random_value(uid2);

  • const int random_value(const int lower_bound, const int upper_bound)
    설명 : [lower_bound, upper_bound] 범위의 랜덤값을 한 개 생성하여 반환한다
    예시
    • 0, 100 사이의 난수 생성
      int n = random_value(0, 100);

    • 50, 200 사이의 난수 생성
      int m = random_value(50, 200);

  • std::vector<int> random_value_vector(const std::uniform_int_distribution<>& uid, const size_t cnt)
    설명 : std::uniform_int_distribution 분포의 랜덤값을 cnt 개 만큼 생성하여 벡터로 반환한다
    예시
    • 0, 100 사이의 난수 10개 생성
      std::uniform_int_distribution<> uid{ 0, 100 };
      std::vector<int> v{ random_value_vector(uid, 10) };

    • 50, 200 사이의 난수 30개 생성
      std::uniform_int_distribution<> uid2{ 50, 200 };
      std::vector<int> nums{ random_value_vector(uid2, 30) };

  • std::vector<int> random_value_vector(const int lower_bound, const int upper_bound, const size_t cnt)
    설명 : [lower_bound, upper_bound] 범위의 랜덤값을 cnt 개 만큼 생성하여 벡터로 반환한다
    예시
    • 0, 100 사이의 난수 10개 생성
      std::vector<int> v{ random_value_vector(0, 100, 10) };

    • 50, 200 사이의 난수 30개 생성
      std::vector<int> nums{ random_value_vector(50, 200, 30) };

  • template <size_t N>
    std::array<int, N> random_value_array(const std::uniform_int_distribution<>& uid)
    설명 : std::uniform_int_distribution 분포의 랜덤값을 N 개 만큼 생성하여 배열로 반환한다
    예시
    • 0, 100 사이의 난수 10개 생성
      std::uniform_int_distribution<> uid{ 0, 100 };
      std::array<int, 10> arr{ random_value_array<10>(uid) };
    • 50, 200 사이의 난수 30개 생성
      std::uniform_int_distribution<> uid2{ 50, 200 };
      std::array<int, 30> nums{ random_value_array<30>(uid2) };

  • template <size_t N>
    std::array<int, N> random_value_array(const int lower_bound, const int upper_bound)
    설명 : [lower_bound, upper_bound] 범위의 랜덤값을 N 개 만큼 생성하여 배열로 반환한다
    예시
    • 0, 100 사이의 난수 10개 생성
      std::array<int, 10> arr{ random_value_array<10>(0, 100) };

    • 50, 200 사이의 난수 30개 생성
      std::array<int, 30> nums{ random_value_array<30>(50, 200) };

 

 

random_value와 random_value_vector 함수는 double 버전으로도 오버로딩하였다

 

 

// randomvalue.h
 
#ifndef _randomvalue
#define _randomvalue
#include <random>
#include <vector>
 
namespace _rv
{
    std::random_device rd;
    std::default_random_engine dre(rd());
}
 
// ...
 
// ...
cs

 

 

사용자는 더이상 std::default_random_engine을 생성할 필요가 없다

random_value와 random_value_vector는 절대로 std::default_random_engine을 사용자에게 직접 요구하지 않는다!

 

  • std::default_random_engine my_random_engine;
    std::uniform_int_distribution<> uid{ 0, 1 };
    int coin = uid(my_random_engine);
  • std::uniform_int_distribution<> uid{ 0, 100 };
    int n = uid(_rv::dre);

 

random_value 함수로 랜덤값을 생성하기로 정했다면, 위와 같은 코드가 나올 수조차 없다

다만 사용자가 굳이 std::default_random_engine을 생성하거나, 한 발 더 가서 숨겨둔 네임스페이스에서 dre를 꺼내오는 비상식적인 행동을 하더라도 말리진 않는다

제대로 쓰긴 쉽지만, 틀리게 쓰긴 어렵게 만들어져있을 뿐이다

 

 

// randomvalue.h
 
const auto random_value(const std::uniform_int_distribution<>& uid)
{
    return uid(_rv::dre);
}
 
const auto random_value(const std::uniform_real_distribution<>& urd)
{
    return urd(_rv::dre);
}
cs

 

 

랜덤 범위를 여러 곳에서 이용하는 경우 std::uniform_(...)_distribution을 생성해 std::uniform_(...)_distribution을 매개변수로 취하는 random_value를 호출하여 랜덤값을 얻는다

 

예시

  • std::uniform_int_distribution<> uid{ 80, 160 };
    int n = random_value(uid);
    int m = random_value(uid);
  • std::uniform_real_distribution<> urd{ 80.0, 160.0 };
    double lf = random_value(urd);
    double score = random_value(urd);

 

 

// randomvalue.h
 
const auto random_value(const int lower_bound, const int upper_bound)
{
    return random_value(std::uniform_int_distribution<>{lower_bound, upper_bound});
}
 
const auto random_value(const double lower_bound, const double upper_bound)
{
    return random_value(std::uniform_real_distribution<>{lower_bound, upper_bound});
}
cs

 

 

랜덤 범위가 일회성일 때에는 lower_bound, upper_bound 를 매개변수로 취하는 random_value를 호출하여 랜덤값을 얻는다
이때, std::uniform_(...)_distribution을 생성하는 과정이 생략되므로 코드는 한 줄이 된다

 

예시

  • int n = random_value(1000, 2000);
  • double lf = random_value(1.234, 5.678);

 

// randomvalue.h
 
auto random_value_vector(const std::uniform_int_distribution<>& uid, const size_t cnt)
{
    std::vector<int> v;
    v.reserve(cnt);
    for (int i = 0; i < cnt; ++i)
        v.push_back(random_value(uid));
    return v;
}
 
auto random_value_vector(const std::uniform_real_distribution<>& urd, const size_t cnt)
{
    std::vector<double> v;
    v.reserve(cnt);
    for (int i = 0; i < cnt; ++i)
        v.push_back(random_value(urd));
    return v;
}
cs

 

 

한 번에 여러 개의 랜덤 값을 동시에 얻고 싶을 경우 random_value_vector 함수를 호출한다
지정된 범위에서 cnt 개 만큼의 랜덤값을 생성하여 벡터로 반환한다

랜덤 범위를 여러 곳에서 이용하는 경우 std::uniform_(...)_distribution을 생성해 std::uniform_(...)_distribution을 매개변수로 취하는 random_value_vector를 호출하여 랜덤값을 얻는다

 

예시

  • std::uniform_int_distribution<> uid{ 555, 666 };
    auto v_25{ random_value_vector(uid, 25) };
    auto v_33{ random_value_vector(uid, 33) };
  • std::uniform_real_distribution<> urd{ 200.0, 400.55 };
    auto real_numbers{ random_value_vector(urd, 100) };
    auto tree_heights{ random_value_vector(urd, 50) };

 

자료구조를 통으로 반환하므로 랜덤값 여러개를 한번에 저장해놓고 이후에 수정할 수 있으며

벡터의 메서드인 push_back()이나 emplace_back(), insert(), emplace()를 이용하여 값을 추가할 수 있다

 

 

// randomvalue.h
 
auto random_value_vector(const int lower_bound, const int upper_bound, const size_t cnt)
{
    return random_value_vector(std::uniform_int_distribution<>{lower_bound, upper_bound}, cnt);
}
 
auto random_value_vector(const double lower_bound, const double upper_bound, const size_t cnt)
{
    return random_value_vector(std::uniform_real_distribution<>{lower_bound, upper_bound}, cnt);
}
cs

 

 

역시 한 번에 여러 개의 랜덤 값을 동시에 얻고 싶을 경우 random_value_vector 함수를 호출한다
지정된 범위에서 cnt 개 만큼의 랜덤값을 생성하여 벡터로 반환한다

랜덤 범위가 일회성일 때에는 lower_bound와 upper_bound를 매개변수로 취하는 random_value_vector를 호출하여 랜덤값들을 얻는다

std::uniform_(...)_distribution을 생성하는 과정이 생략되므로 코드는 한 줄이 된다

 

예시

  • auto vector1{ random_value_vector(10, 30, 50) };
  • auto vector2{ random_value_vector(2.3, 4.5, 6) };

 

// randomvalue.h
 
template <size_t N>
auto random_value_array(const std::uniform_int_distribution<>& uid)
{
    std::array<int, N> a;
    for (auto& elem : a)
        elem = random_value(uid);
    return a;
}
 
template <size_t N>
auto random_value_array(const std::uniform_real_distribution<>& urd)
{
    std::array<double, N> a;
    for (auto& elem : a)
        elem = random_value(urd);
    return a;
}
cs

 

 

한 번에 여러 개의 랜덤 값을 동시에 얻고 싶을 경우 random_value_array 함수를 호출한다

지정된 범위에서 N 개 만큼의 랜덤값을 생성하여 배열로 반환한다

랜덤 범위를 여러 곳에서 이용하는 경우 std::uniform_(...)_distribution을 생성해 std::uniform_(...)_distribution을 매개변수로 취하는 random_value_array를 호출하여 랜덤값을 얻는다

 

예시

  • std::uniform_int_distribution<> uid{ 1, 9 };
    auto grades{ random_value_array<6>(uid) };
    auto nums{ random_value_array<2000>(uid) };
  • std::uniform_real_distribution<> urd{ 130.0, 230.0 };
    auto men_hegihts{ random_value_array<10000>(urd) };
    auto women_heights{ random_value_array<5000>(urd) };

 

random_value_vector를 호출하기에는 빈번한 호출 문제로 동적 할당이 부담스러울 때 random_value_array를 이용한다

템플릿 매개변수 N으로 얻어올 랜덤값의 갯수를 정한다

 

random_value_array<1>과 random_value_array<2>, random_value_array<3>은 모두 다른 함수이므로

랜덤값의 갯수를 꽤나 동적으로 받아야 할 경우엔 되도록 array 버전 대신 vector 버전을 사용한다

1~100000 까지의 숫자를 전부 이용한다면 100000개의 서로 다른 함수가 컴파일러에 의해 작성되어 코드 비대화(code bloating)가 발생한다

 

 

// randomvalue.h
 
template <size_t N>
auto random_value_array(const int lower_bound, const int upper_bound)
{
    return random_value_array<N>(std::uniform_int_distribution<>{lower_bound, upper_bound});
}
 
template <size_t N>
auto random_value_array(const double lower_bound, const double upper_bound)
{
    return random_value_array<N>(std::uniform_real_distribution<>{lower_bound, upper_bound});
}
cs

 

 

역시 한 번에 여러 개의 랜덤 값을 동시에 얻고 싶을 경우 random_value_array 함수를 호출한다
지정된 범위에서 N 개 만큼의 랜덤값을 생성하여 배열로 반환한다

랜덤 범위가 일회성일 때에는 lower_bound와 upper_bound를 매개변수로 취하는 random_value_array를 호출하여 랜덤값들을 얻는다

std::uniform_(...)_distribution을 생성하는 과정이 생략되므로 코드는 한 줄이 된다

 

예시

  • auto arr1{ random_value_array<60>(20, 40) };
  • auto arr2{ random_value_array<100>(13.57, 35.79) };

 

 

모든 함수들이 내부적으로는 std::default_random_engine과 std::uniform_(...)_distribution 둘 다 사용한다

그러나 std::default_random_engine은 사용자가 사용하지 않는 네임스페이스 _rv로 옮겨갔으며

uniform_(...)_distribution도 굳이 사용자가 생성할 필요가 없다면 생성하지 않을 수 있다

 

 

 

더보기

 

// randomvalue.h
 
#ifndef _randomvalue
#define _randomvalue
#include <random>
#include <vector>
#include <array>
 
namespace _rv
{
    std::random_device rd;
    std::default_random_engine dre(rd());
}
 
const auto random_value(const std::uniform_int_distribution<>& uid)
{
    return uid(_rv::dre);
}
 
const auto random_value(const int lower_bound, const int upper_bound)
{
    return random_value(std::uniform_int_distribution<>{lower_bound, upper_bound});
}
 
auto random_value_vector(const std::uniform_int_distribution<>& uid, const size_t cnt)
{
    std::vector<int> v;
    v.reserve(cnt);
    for (int i = 0; i < cnt; ++i)
        v.push_back(random_value(uid));
    return v;
}
 
auto random_value_vector(const int lower_bound, const int upper_bound, const size_t cnt)
{
    return random_value_vector(std::uniform_int_distribution<>{lower_bound, upper_bound}, cnt);
}
 
template <size_t N>
auto random_value_array(const std::uniform_int_distribution<>& uid)
{
    std::array<int, N> a;
    for (auto& elem : a)
        elem = random_value(uid);
    return a;
}
 
template <size_t N>
auto random_value_array(const int lower_bound, const int upper_bound)
{
    return random_value_array<N>(std::uniform_int_distribution<>{lower_bound, upper_bound});
}
 
const auto random_value(const std::uniform_real_distribution<>& urd)
{
    return urd(_rv::dre);
}
 
const auto random_value(const double lower_bound, const double upper_bound)
{
    return random_value(std::uniform_real_distribution<>{lower_bound, upper_bound});
}
 
auto random_value_vector(const std::uniform_real_distribution<>& urd, const size_t cnt)
{
    std::vector<double> v;
    v.reserve(cnt);
    for (int i = 0; i < cnt; ++i)
        v.push_back(random_value(urd));
    return v;
}
 
auto random_value_vector(const double lower_bound, const double upper_bound, const size_t cnt)
{
    return random_value_vector(std::uniform_real_distribution<>{lower_bound, upper_bound}, cnt);
}
 
template <size_t N>
auto random_value_array(const std::uniform_real_distribution<>& urd)
{
    std::array<double, N> a;
    for (auto& elem : a)
        elem = random_value(urd);
    return a;
}
 
template <size_t N>
auto random_value_array(const double lower_bound, const double upper_bound)
{
    return random_value_array<N>(std::uniform_real_distribution<>{lower_bound, upper_bound});
}
 
// ...
 
// ...
 
#endif
cs

 

 

더보기

 

// randomvalue.h
 
// ...
 
#include <iostream>
void test_random_value()
{
    for (int i = 0; i < 10++i)
    {
        auto lower_bound{ random_value(010000) };
        auto upper_bound{ random_value(lower_bound, 10000) };
 
        auto n = random_value_vector(lower_bound, upper_bound, 5);
        auto random_num = random_value(std::uniform_int_distribution<>{lower_bound, upper_bound});
        std::uniform_int_distribution<> uid{ lower_bound, upper_bound };
        auto random_num2 = random_value(uid);
 
        std::cout << "lower_bound : " << lower_bound << " upper_bound : " << upper_bound << std::endl;
        std::cout << "{ " << n[0<< ", " << n[1<< ", " << n[2<< ", " << n[3<< ", " << n[4<< ", " << random_num << ", " << random_num2 << " }\n\n";
    }
}
 
// ...
cs

 

 

 

테스트 결과

 

 

벡터, int 버전

벡터, double 버전

배열, int 버전

배열, double 버전

모두 테스트를 통과하였다