在 C++ 映射中选择随机元素的百分比
Selecting percentage of random elements in a C++ map
我有一个 C++ 映射:std::map <std::string, int>
我想从此地图中选取 p 百分比的随机元素。这里 p 是动态的。例如,这张地图中所有 Key:Value 对中的 10% 或 30% 是随机挑选的。不能使用 c++11。
最好的方法是什么?
谢谢。
- 将布尔向量初始化为与地图大小相同
- 计算
T = map.size() * percentage
- 将向量的前T个元素初始化为"true",其余为false
- 随机打乱向量中的元素
- map 和 vector 的迭代器 - 当 vector 中相应的索引位置为真时指定 map 中的一个项目
示例代码:
#include <iostream>
#include <map>
#include <vector>
#include <string>
using namespace std;
void getRandomMapElements(map<string, int>& items, double percentage)
{
const size_t count = items.size();
vector<bool> vec;
vec.resize(count); // all items in vec are "false"
if (percentage < 0)
{
percentage = 0;
}
else if (percentage > 1.0)
{
percentage = 1.0;
}
size_t target = (size_t)(count * percentage); // actual number of items extracted
// fill up the first TARGET count elements of the vector with true, the rest are kept at false
for (size_t i = 0; i < target; i++)
{
vec[i] = true;
}
// shuffle the boolean vector
for (size_t i = 0; i < count; i++)
{
bool val = vec[i];
size_t swap = rand() % count;
vec[i] = vec[swap];
vec[swap] = val;
}
// iterate over the vector and map together
map<string, int>::iterator itor = items.begin();
for (size_t i = 0; i < count; i++)
{
if (vec[i])
{
cout << itor->first << " : " << itor->second << endl;
}
itor++;
}
}
对于 C++17,std::sample
完全可以满足您的需求,但对于 C++98,它稍微复杂一些。
兼容c++98的最短代码是:
unsigned pick_below(unsigned n)
{
// poor distribution:
return std::rand() % n;
}
std::vector<std::pair<std::string, int> >
sample(const std::map<std::string, int> & data_in,
unsigned p)
{
std::vector<std::pair<std::string, int> > shuffled(data_in.begin(), data_in.end());
for (unsigned i=shuffled.size() ; i > 1 ; --i)
std::swap(shuffled[i-1], shuffled[pick_below(i)]);
shuffled.erase(shuffled.begin() +p, shuffled.end());
}
此代码在两个层面上存在问题:
std::random
质量无保障。
- 使用 modulo in pick_below distorts the distribution.
要克服问题 2,请使用 boost::random::uniform_int_distribution
或根据 this:
重写 pick_below
函数
unsigned pick_below(unsigned n)
{
unsigned x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
return x % n;
}
可以通过使用第三方随机生成器(例如 boost::random::mt19937
)来解决问题 1。
不幸的是,这个解决方案的复杂度平均为 O(n)(因为 pick_below
不能保证终止,但在任何值 p < RAND_MAX / 2
上迭代它的概率超过 K 次指数递减到小于 0.5K。复杂度不可能比 O(n) 好,因为没有办法选择第 kth 个元素地图的一部分,而不是迭代所有地图。
我有一个 C++ 映射:std::map <std::string, int>
我想从此地图中选取 p 百分比的随机元素。这里 p 是动态的。例如,这张地图中所有 Key:Value 对中的 10% 或 30% 是随机挑选的。不能使用 c++11。
最好的方法是什么?
谢谢。
- 将布尔向量初始化为与地图大小相同
- 计算
T = map.size() * percentage
- 将向量的前T个元素初始化为"true",其余为false
- 随机打乱向量中的元素
- map 和 vector 的迭代器 - 当 vector 中相应的索引位置为真时指定 map 中的一个项目
示例代码:
#include <iostream>
#include <map>
#include <vector>
#include <string>
using namespace std;
void getRandomMapElements(map<string, int>& items, double percentage)
{
const size_t count = items.size();
vector<bool> vec;
vec.resize(count); // all items in vec are "false"
if (percentage < 0)
{
percentage = 0;
}
else if (percentage > 1.0)
{
percentage = 1.0;
}
size_t target = (size_t)(count * percentage); // actual number of items extracted
// fill up the first TARGET count elements of the vector with true, the rest are kept at false
for (size_t i = 0; i < target; i++)
{
vec[i] = true;
}
// shuffle the boolean vector
for (size_t i = 0; i < count; i++)
{
bool val = vec[i];
size_t swap = rand() % count;
vec[i] = vec[swap];
vec[swap] = val;
}
// iterate over the vector and map together
map<string, int>::iterator itor = items.begin();
for (size_t i = 0; i < count; i++)
{
if (vec[i])
{
cout << itor->first << " : " << itor->second << endl;
}
itor++;
}
}
对于 C++17,std::sample
完全可以满足您的需求,但对于 C++98,它稍微复杂一些。
兼容c++98的最短代码是:
unsigned pick_below(unsigned n)
{
// poor distribution:
return std::rand() % n;
}
std::vector<std::pair<std::string, int> >
sample(const std::map<std::string, int> & data_in,
unsigned p)
{
std::vector<std::pair<std::string, int> > shuffled(data_in.begin(), data_in.end());
for (unsigned i=shuffled.size() ; i > 1 ; --i)
std::swap(shuffled[i-1], shuffled[pick_below(i)]);
shuffled.erase(shuffled.begin() +p, shuffled.end());
}
此代码在两个层面上存在问题:
std::random
质量无保障。- 使用 modulo in pick_below distorts the distribution.
要克服问题 2,请使用 boost::random::uniform_int_distribution
或根据 this:
pick_below
函数
unsigned pick_below(unsigned n)
{
unsigned x;
do {
x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));
return x % n;
}
可以通过使用第三方随机生成器(例如 boost::random::mt19937
)来解决问题 1。
不幸的是,这个解决方案的复杂度平均为 O(n)(因为 pick_below
不能保证终止,但在任何值 p < RAND_MAX / 2
上迭代它的概率超过 K 次指数递减到小于 0.5K。复杂度不可能比 O(n) 好,因为没有办法选择第 kth 个元素地图的一部分,而不是迭代所有地图。