我有代码创建了几个对象实例(每个实例都有一个适应值等),我想根据它们的适应值使用加权选择对N个唯一对象进行采样。然后丢弃所有未采样的对象(但是最初需要创建它们以确定它们的适应值)。
我当前的代码看起来像这样:
vector<Item> getItems(..) {
std::vector<Item> items
.. // generate N values for items
int N = items.size();
std::vector<double> fitnessVals;
for(auto it = items.begin(); it != items.end(); ++it)
fitnessVals.push_back(it->getFitness());
std::mt19937& rng = getRng();
for(int i = 0, i < N, ++i) {
std::discrete_distribution<int> dist(fitnessVals.begin() + i, fitnessVals.end());
unsigned int pick = dist(rng);
std::swap(fitnessVals.at(i), fitnessVals.at(pick));
std::swap(items.at(i), items.at(pick));
}
items.erase(items.begin() + N, items.end());
return items;
}
通常最初创建~10,000个实例,其中N为~200。适应度值是非负的,通常值为~70。它可能高达~3000,但更高的值越来越不可能。
有没有一种优雅的方法摆脱fitnessVals矢量?或者也许是更好的方法来做到这一点?效率很重要,但我也想知道好的C ++编码实践。
答案 0 :(得分:3)
如果您询问是否可以使用items
向量中的项目执行此操作,答案是肯定的。以下是一个相当可怕但却没有效果的方法:我提前为密度道歉。
这将毫无疑问的容器迭代器包装在我们自己的设备的另一个迭代器中;将它与您选择的成员函数配对的一个。您可能需要与const
共舞,以使其与您的成员函数选择正常工作。那个任务我留给你。
template<typename Iter, typename R>
struct memfn_iterator_s :
public std::iterator<std::input_iterator_tag, R>
{
using value_type = typename std::iterator_traits<Iter>::value_type;
memfn_iterator_s(Iter it, R(value_type::*fn)())
: m_it(it), mem_fn(fn) {}
R operator*()
{
return ((*m_it).*mem_fn)();
}
bool operator ==(const memfn_iterator_s& arg) const
{
return m_it == arg.m_it;
}
bool operator !=(const memfn_iterator_s& arg) const
{
return m_it != arg.m_it;
}
memfn_iterator_s& operator ++() { ++m_it; return *this; }
private:
R (value_type::*mem_fn)();
Iter m_it;
};
生成上面的怪物:
template<typename Iter, typename R>
memfn_iterator_s<Iter,R> memfn_iterator(
Iter it,
R (std::iterator_traits<Iter>::value_type::*fn)())
{
return memfn_iterator_s<Iter,R>(it, fn);
}
这给你带来的是能够做到这一点:
auto it_end = memfn_iterator(items.end(), &Item::getFitness);
for(unsigned int i = 0; i < N; ++i)
{
auto it_begin = memfn_iterator(items.begin()+i, &Item::getFitness);
std::discrete_distribution<unsigned int> dist(it_begin, it_end);
std::swap(items.at(i), items.at(i+dist(rng)));
}
items.erase(items.begin() + N, items.end());
不需要临时数组。当离散分布需要时,会为相应的项目调用成员函数(通常保留它自己的权重向量,因此复制这种努力将是多余的)。
Dunno如果你能从中获得任何有用或有用的东西,但想想它很有趣。
答案 1 :(得分:2)
他们在STL中有一个离散的分布是非常好的。据我所知,从一组加权对象中采样的最有效算法(即,与权重成比例的概率)是别名方法。这里有一个Java实现:http://www.keithschwarz.com/interesting/code/?dir=alias-method
我怀疑这就是STL离散分配所使用的东西。如果您要经常调用getItems函数,您可能需要创建一个“FitnessSet”类或其他东西,这样您就不必每次想要从同一个集合中进行采样时都要构建分布。
编辑:另一个建议......如果您希望能够删除项目,则可以将对象存储在二叉树中。每个节点将包含其下方子树中的权重之和,并且对象本身可以位于叶子中。您可以通过一系列log(N)硬币投掷来选择对象:在给定节点处,选择0和node.subtreeweight之间的随机数。如果它小于node.left.subtreeweight,则向左走;否则就走吧递归地继续,直到你到达一片叶子。
答案 2 :(得分:1)
我会尝试类似下面的内容(参见代码注释):
#include <algorithm> // For std::swap and std::transform
#include <functional> // For std::mem_fun_ref
#include <random> // For std::discrete_distribution
#include <vector> // For std::vector
size_t
get_items(std::vector<Item>& results, const std::vector<Item>& items)
{
// Copy the items to the results vector. All operations should be
// done on it, rather than the original items vector.
results.assign(items.begin(), items.end());
// Create the fitness values vector, immediately allocating
// the number of doubles required to match the size of the
// input item vector.
std::vector<double> fitness_vals(results.size());
// Use some STL "magic" ...
// This will iterate over the items vector, calling the
// getFitness() method on each item, and storing the result
// in the fitness_vals vector.
std::transform(results.begin(), results.end(),
fitness_vals.begin(),
std::mem_fun_ref(&Item::getFitness));
//
std::mt19937& rng = getRng();
for (size_t i=0; i < results.size(); ++i) {
std::discrete_distribution<int> dist(fitness_vals.begin() + i, fitness_vals.end());
unsigned int pick = dist(rng);
std::swap(fitness_vals[ii], fitness_vals[pick]);
std::swap(results[i], results[pick]);
}
return (results.size());
}
调用者不是返回结果向量,而是提供应该添加结果的向量。此外,原始矢量(作为第二个参数传递)保持不变。如果这不是您感兴趣的事情,您可以随时传递一个向量并直接使用它。
我没有看到没有健身值矢量的方法; discrete_distribution构造函数需要有开始和结束迭代器,所以从我所知道的,你需要有这个向量。
其余部分基本相同,返回值是结果向量中的项数,而不是向量本身。
此示例使用了许多STL功能(算法,容器,仿函数),我发现这些功能非常有用并且是我日常开发的一部分。
编辑:对items.erase()
的调用是多余的; items.begin() + N
其中N == items.size()
等同于items.end()
。对items.erase()
的调用等同于无操作。