迭代成员函数的结果

Iterating over the result of a member function

本文关键字:结果 函数 成员 迭代      更新时间:2023-10-16

我的代码创建了几个对象实例(每个实例都有一个适应度值),我想根据它们的适应度值使用加权选择对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()的调用将等同于无操作

相关文章: