-
-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement weighted random number generation using maps #44
Changes from all commits
4ce1e58
b295775
1c74716
250515c
10cf605
358d05a
42d1a41
00e925c
2e0e03d
75d8399
b8d165b
f6ef164
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,7 +47,10 @@ namespace effolkronium { | |
|
||
namespace details { | ||
/// Key type for getting common type numbers or objects | ||
struct common{ }; | ||
struct common{ }; | ||
|
||
/// Key type for weighted random number generation | ||
struct weight{ }; | ||
|
||
/// True if type T is applicable by a std::uniform_int_distribution | ||
template<typename T> | ||
|
@@ -145,6 +148,15 @@ namespace effolkronium { | |
decltype(test<T>(0)), long>::value; | ||
}; | ||
|
||
template<typename...> | ||
using void_t = void; | ||
|
||
template<typename Type, typename = void> | ||
struct is_map : public std::false_type {}; | ||
|
||
template<typename Type> | ||
struct is_map<Type, void_t<typename Type::key_type, typename Type::mapped_type, typename Type::value_type>> : public std::true_type{}; | ||
|
||
} // namespace details | ||
|
||
/// Default seeder for 'random' classes | ||
|
@@ -202,6 +214,9 @@ namespace effolkronium { | |
/// Key type for getting common type numbers or objects | ||
using common = details::common; | ||
|
||
/// Key type for weighted random number generation | ||
using weight = details::weight; | ||
|
||
/** | ||
* \return The minimum value | ||
* potentially generated by the random-number engine | ||
|
@@ -419,11 +434,11 @@ namespace effolkronium { | |
} | ||
|
||
/** | ||
* \brief Return random value from initilizer_list | ||
* \param init_list initilizer_list with values | ||
* \return Random value from initilizer_list | ||
* \note Should be 1 or more elements in initilizer_list | ||
* \note Warning! Elements in initilizer_list can't be moved: | ||
* \brief Return random value from initializer_list | ||
* \param init_list initializer_list with values | ||
* \return Random value from initializer_list | ||
* \note Should be 1 or more elements in initializer_list | ||
* \note Warning! Elements in initializer_list can't be moved: | ||
* https://stackoverflow.com/a/8193157/5734836 | ||
*/ | ||
template<typename T> | ||
|
@@ -641,6 +656,87 @@ namespace effolkronium { | |
return dist( engine_instance( ) ); | ||
} | ||
|
||
/** | ||
* \brief Return a random iterator from given map container by | ||
* utilizing the values of the map container as weights | ||
* for weighted random number generation | ||
* \param Key The Key type for this version of 'get' method | ||
* Type should be '(THIS_TYPE)::common' struct | ||
* \param map_container A container that has mapped_type, | ||
* value_type and key_type defined | ||
* \note return the end iterator if the iterator is empty or total weight is equals to sum | ||
*/ | ||
template< | ||
typename Key, | ||
class MapContainer | ||
> | ||
static auto get(const MapContainer& map_container) -> typename std::enable_if< | ||
details::is_map<MapContainer>::value && | ||
details::is_iterator<decltype(std::begin(map_container))>::value && | ||
!std::is_signed<typename MapContainer::mapped_type>::value && | ||
std::is_same<Key, details::weight>::value, | ||
decltype(std::begin(map_container))>::type { | ||
using MappedType = typename MapContainer::mapped_type; | ||
using IteratorType = decltype(std::begin(map_container)); | ||
|
||
MappedType total_weight = 0; | ||
for (IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) { | ||
total_weight += it->second; | ||
} | ||
if(total_weight == MappedType(0)) return std::end(map_container); | ||
|
||
MappedType random_weight = get(MappedType(0), total_weight - 1); | ||
MappedType sum = 0; | ||
|
||
for(IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) | ||
{ | ||
sum += it->second; | ||
if(sum > random_weight) return it; | ||
} | ||
return std::end(map_container); | ||
} | ||
|
||
/** | ||
* \brief Return a random iterator from given map container by | ||
* utilizing the values of the map container as weights | ||
* for weighted random number generation | ||
* \param Key The Key type for this version of 'get' method | ||
* Type should be '(THIS_TYPE)::common' struct | ||
* \param map_container A container that has mapped_type, | ||
* value_type and key_type defined | ||
* \note return the end iterator if the iterator is empty | ||
*/ | ||
template< | ||
typename Key, | ||
class MapContainer | ||
> | ||
static auto get(const MapContainer& map_container) -> typename std::enable_if< | ||
details::is_map<MapContainer>::value && | ||
details::is_iterator<decltype(std::begin(map_container))>::value && | ||
details::is_uniform_real<typename MapContainer::mapped_type>::value && | ||
std::is_same<Key, details::weight>::value, | ||
decltype(std::begin(map_container))>::type { | ||
using MappedType = typename MapContainer::mapped_type; | ||
using IteratorType = decltype(std::begin(map_container)); | ||
|
||
MappedType total_weight = 0; | ||
for (IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) { | ||
assert(it->second >= MappedType(0)); | ||
total_weight += it->second; | ||
} | ||
if(total_weight == MappedType(0)) return std::end(map_container); | ||
|
||
MappedType random_weight = get(MappedType(0), std::nextafter(total_weight, (std::numeric_limits<MappedType>::min)( ))); | ||
MappedType sum = 0; | ||
|
||
for(IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By iterating through map we're missing reason of using the map instead of And for unordered map the order of weights will be different and result of this function will be different I'm not expert in this algorithm, can it be done with map::upper_bound or map::lower_bound function ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The upper_bound and lower_bound functions operate on the keys of the map so, it is not suitable for this case. And even if it operated on values, we had to store total weight until that object instead of the weight of that object as the value. Also, the "ordering" of std::unordered_map should not affect the result. Because the probability of each item getting picked is directly tied to its weight. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine, can you code a simple test case for this inside I can do it by myself but later next week There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During testing, I found out the algorithm fails for floats and doubles as values. I am also fixing that. Also, do you have specific test case(s) that you want me to code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have nothing special, you can try your best There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's much better to not allow users to use negative values at all by disallowing it with extra condition for enable_if Something like this: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For real numbers (float, double, long double) assert is ok There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But this completely disallows users to use reals as weights. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
{ | ||
sum += it->second; | ||
if(sum > random_weight) return it; | ||
} | ||
return std::end(map_container); | ||
} | ||
|
||
/** | ||
* \brief Reorders the elements in the given range [first, last) | ||
* such that each possible permutation of those elements | ||
|
@@ -1018,11 +1114,11 @@ namespace effolkronium { | |
} | ||
|
||
/** | ||
* \brief Return random value from initilizer_list | ||
* \param init_list initilizer_list with values | ||
* \return Random value from initilizer_list | ||
* \note Should be 1 or more elements in initilizer_list | ||
* \note Warning! Elements in initilizer_list can't be moved: | ||
* \brief Return random value from initializer_list | ||
* \param init_list initializer_list with values | ||
* \return Random value from initializer_list | ||
* \note Should be 1 or more elements in initializer_list | ||
* \note Warning! Elements in initializer_list can't be moved: | ||
* https://stackoverflow.com/a/8193157/5734836 | ||
*/ | ||
template<typename T> | ||
|
@@ -1240,6 +1336,87 @@ namespace effolkronium { | |
return dist( m_engine ); | ||
} | ||
|
||
/** | ||
* \brief Return a random iterator from given map container by | ||
* utilizing the values of the map container as weights | ||
* for weighted random number generation | ||
* \param Key The Key type for this version of 'get' method | ||
* Type should be '(THIS_TYPE)::common' struct | ||
* \param map_container A container that has mapped_type, | ||
* value_type and key_type defined | ||
* \note return the end iterator if the iterator is empty or total weight is equals to sum | ||
*/ | ||
template< | ||
typename Key, | ||
class MapContainer | ||
> | ||
auto get(const MapContainer& map_container) -> typename std::enable_if< | ||
details::is_map<MapContainer>::value && | ||
details::is_iterator<decltype(std::begin(map_container))>::value && | ||
!std::is_signed<typename MapContainer::mapped_type>::value && | ||
std::is_same<Key, details::weight>::value, | ||
decltype(std::begin(map_container))>::type { | ||
using MappedType = typename MapContainer::mapped_type; | ||
using IteratorType = decltype(std::begin(map_container)); | ||
|
||
MappedType total_weight = 0; | ||
for (IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) { | ||
total_weight += it->second; | ||
} | ||
if(total_weight == MappedType(0)) return std::end(map_container); | ||
|
||
MappedType random_weight = get(MappedType(0), total_weight - 1); | ||
MappedType sum = 0; | ||
|
||
for(IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) | ||
{ | ||
sum += it->second; | ||
if(sum > random_weight) return it; | ||
} | ||
return std::end(map_container); | ||
} | ||
|
||
/** | ||
* \brief Return a random iterator from given map container by | ||
* utilizing the values of the map container as weights | ||
* for weighted random number generation | ||
* \param Key The Key type for this version of 'get' method | ||
* Type should be '(THIS_TYPE)::common' struct | ||
* \param map_container A container that has mapped_type, | ||
* value_type and key_type defined | ||
* \note return the end iterator if the iterator is empty | ||
*/ | ||
template< | ||
typename Key, | ||
class MapContainer | ||
> | ||
auto get(const MapContainer& map_container) -> typename std::enable_if< | ||
details::is_map<MapContainer>::value && | ||
details::is_iterator<decltype(std::begin(map_container))>::value && | ||
details::is_uniform_real<typename MapContainer::mapped_type>::value && | ||
std::is_same<Key, details::weight>::value, | ||
decltype(std::begin(map_container))>::type { | ||
using MappedType = typename MapContainer::mapped_type; | ||
using IteratorType = decltype(std::begin(map_container)); | ||
|
||
MappedType total_weight = 0; | ||
for (IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) { | ||
assert(it->second >= MappedType(0)); | ||
total_weight += it->second; | ||
} | ||
if(total_weight == MappedType(0)) return std::end(map_container); | ||
|
||
MappedType random_weight = get(MappedType(0), std::nextafter(total_weight, (std::numeric_limits<MappedType>::min)( ))); | ||
MappedType sum = 0; | ||
|
||
for(IteratorType it = std::begin(map_container); it != std::end(map_container); ++it) | ||
{ | ||
sum += it->second; | ||
if(sum > random_weight) return it; | ||
} | ||
return std::end(map_container); | ||
} | ||
|
||
/** | ||
* \brief Reorders the elements in the given range [first, last) | ||
* such that each possible permutation of those elements | ||
|
@@ -1285,7 +1462,7 @@ namespace effolkronium { | |
/** | ||
* \brief The basic static random alias based on a std::mt19937 | ||
* \note It uses static methods API and data with static storage | ||
* \note Not thread safe but more prefomance | ||
* \note Not thread safe but more performance | ||
*/ | ||
using random_static = basic_random_static<std::mt19937>; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍