迭代成员函数的结果
Iterating over the result of a member function
我的代码创建了几个对象实例(每个实例都有一个适应度值),我想根据它们的适应度值使用加权选择对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,但更高的值越来越不可能。
是否有一种优雅的方法来摆脱适应度值向量?或者是一种更好的方法?效率很重要,但我也想知道好的c++编码实践。
如果您问是否可以仅对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());
不需要临时数组。当离散分布(通常保留其自己的权重向量,因此复制该工作将是多余的)需要时,对各自的项调用成员函数。
不知道你能不能从中得到什么有用的东西,但是想想还是很有趣的。
在STL中有一个离散分布是非常好的。据我所知,从一组加权对象(即概率与权重成正比)中进行采样的最有效算法是别名方法。这里有一个Java实现:http://www.keithschwarz.com/interesting/code/?dir=alias-method
我怀疑这就是STL discrete_distribution所使用的。如果你要经常调用gettitems函数,你可能想要创建一个"FitnessSet"类或其他东西,这样你就不必每次都从同一个集合中采样时都构建你的分布。
编辑:另一个建议…如果您希望能够删除项,则可以将对象存储在二叉树中。每个节点包含它下面的子树的权重之和,对象本身可以在叶节点中。您可以通过一系列的log(N)次抛硬币来选择一个对象:在给定的节点上,在0和node.subtreeweight之间选择一个随机数。如果小于node。left。子树权重,向左;否则往右转。继续递归,直到到达一个叶节点。我会尝试如下操作(参见代码注释):
#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构造函数需要有begin和end迭代器,所以从我可以看出,您需要有这个vector。
其余部分基本相同,返回值是结果向量中的项数,而不是向量本身。
这个例子使用了许多STL特性(算法,容器,函子),我发现这些特性很有用,是我日常开发的一部分。
编辑:调用items.erase()
是多余的;items.begin() + N
,其中N == items.size()
等于items.end()
。对items.erase()
的调用将等同于无操作
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- C++Brute Force攻击函数不会返回结果
- 如何从递归函数中完全返回,该函数给出了每个函数结果的累积相加?
- C++ SSE 内部函数:将结果存储在变量中
- c++ lambda:柯里和函数:使用按值捕获与按引用捕获返回不同的结果
- C++ 随机数生成器:尝试将结果作为向量获取,但通过制作 void 函数来执行此操作而出现错误
- C++ 获取函数在常量引用中按值返回的结果
- C++ 犰狳库中的sort_index()函数给出了错误的结果
- 从返回 std::optional of std::vector 的函数中获取结果到调用方
- 类中静态函数C++意外结果
- 为什么函数 tellg() 没有返回好的结果?
- 模板函数意外的结果
- 使用具有默认参数的函数模板进行 decltype 会使结果混乱(一个有趣的问题或 gcc 的错误)
- lambda 函数未显示正确的结果
- 为什么我可以改变常量对象中的成员变量,这是返回常量对象函数的结果?
- 我们如何并行运行算法的 n 个实例并以有效的方式计算结果函数的平均值?
- Fmod 函数清楚地输出一个预期的双精度值,但 if(fmod == 预期的双精度值)的计算结果不是 true
- 有没有办法将 for 循环结果返回到像三元运算符这样的函数中?
- 为什么这个涉及 std::enable_if 的模板元函数会产生不希望的结果?
- 使用类似的比较函数时,在 c++ 中为 std:sort 获得不同的结果