对于 unordered_map(hash 结构)来说,随机获取其中一个元素并不像数组那样简单。以下分析两种思路以及推荐的实现方式。
思路分析
思路 1:直接对内部 list 做随机下标访问。这种方式在数据量大时,迭代器定位极为麻烦,性能较差。
思路 2:先随机到一个非空 bucket,再在该 bucket 内随机取一个元素。
思路 2 存在一个问题:bucket 数量会随着 rehash / resize 等扩容操作而变化,而实际有效元素数 size 可能远小于 bucket_count。这使得随机命中目标元素的概率很低。通过 rehash 剔除多余的空 bucket 虽然可以缓解问题,但 rehash 本身代价较高。纯粹为了解决随机问题而引入 std::map 也不合适,因为查找性能会下降。综合来看,有必要自己实现一个适合这种场景的随机访问逻辑,而不是直接依赖 std::unordered_map 的标准接口。
具体实现(C++)
具体类版本
const std::string & GatewayLoginInfo::RandomMember()
{
if (this->uuid_to_session_id.size() <= 0)
{
return empty_string;
}
//way2 to random
int max_random_counter = this->uuid_to_session_id.bucket_count();
int iter = max_random_counter * 2;
int bucket;
while ((--iter) >= 0)
{
// find a not empty bucket
bucket = rand() % max_random_counter;
int size = this->uuid_to_session_id.bucket_size(bucket);
if (size > 0)
{
// this bucket is not empty
//大部分情况下 size 一般为0 1 因此比way1 好很多
auto this_bucket_iter = this->uuid_to_session_id.begin(bucket);
int counter = rand() % size;
while (--counter >= 0)
{
++this_bucket_iter;
}
return this_bucket_iter->first;
}
}
return empty_string;
//way1 to random
/*auto it = this->uuid_to_session_id.begin();
int counter = rand() % this->uuid_to_session_id.size();
while (counter--)
{
++it;
}
return it->first;*/
}
泛型版本(带条件过滤)
//从hash表中随机一个元素
template< typename HashMap>
auto unordered_map_random_member_if(HashMap & hash, const std::function<bool(const decltype(hash.begin())&)> &cb)->decltype(hash.begin())
{
if (hash.size() <= 0)
{
return hash.end();
}
int max_random_counter = hash.bucket_count();
int iter = max_random_counter * 2;
int bucket;
while ((--iter) >= 0)
{
// find a not empty bucket
bucket = rand() % max_random_counter;
int size = hash.bucket_size(bucket);
if (size > 0)
{
// this bucket is not empty
//大部分情况下 size 一般为0 1
auto this_bucket_iter = hash.begin(bucket);
int counter = rand() % size;
while (--counter >= 0)
{
++this_bucket_iter;
}
if (cb(this_bucket_iter))
{
return (this_bucket_iter);
}
}
}
return hash.end();
}
泛型版本(无过滤)
//从hash表中随机一个元素
template< typename HashMap>
auto unordered_map_random_member(HashMap & hash)->decltype(hash.begin())
{
has_find_one = false;
if (hash.size() <= 0)
{
return hash.end();
}
int max_random_counter = hash.bucket_count();
int iter = max_random_counter * 2;
int bucket;
while ((--iter) >= 0)
{
// find a not empty bucket
bucket = rand() % max_random_counter;
int size = hash.bucket_size(bucket);
if (size > 0)
{
// this bucket is not empty
//大部分情况下 size 一般为0 1
auto this_bucket_iter = hash.begin(bucket);
int counter = rand() % size;
while (--counter >= 0)
{
++this_bucket_iter;
}
return (this_bucket_iter);
}
}
return hash.end();
}
std::map(非 hash 结构)的随机元素获取方式 TODO。