创建参考向量的优雅方式

Posted

技术标签:

【中文标题】创建参考向量的优雅方式【英文标题】:Elegant way to create a vector of reference 【发布时间】:2018-10-30 18:49:37 【问题描述】:

我有一个 Foo 的向量

vector<Foo> inputs

Foo 是一个结构体,里面有一些分数

struct Foo 
    ...
    float score
    bool winner

现在我想按分数对输入进行排序,只将获胜者分配给前 3 名。但我不想更改原始输入向量。所以我想我需要创建一个参考向量然后对其进行排序?创建参考向量是否合法?有没有一种优雅的方式来做到这一点?

【问题讨论】:

非拥有指针向量会更容易管理 指针向量可能是您正在寻找的。但是根据您的Foo 的大小,简单复制可能会更便宜。如果Foo 真的只是一个浮点数和一个布尔值,那么我建议复制整个向量。 在旁注中,排序对此非常低效。您可以使用简单的“查找最大值”算法在 O(n) 时间内获得前三名。 FWIW,只需几行代码就可以一次完成并持有几个迭代器。这将为您节省 O(N) 空间和 O(logN) 比较。 对不起,我没有说清楚。重点是更新原始输入,而不是找到前 3 个。 【参考方案1】:

这里有两种不同的方式来创建vector&lt;Foo*&gt;

vector<Foo*> foor; 
for (auto& x:inputs)
   foor.push_back(&x);

vector<Foo*> foob(inputs.size(),nullptr); 
transform(inputs.begin(), inputs.end(), foob.begin(), [](auto&x) return &x;); 

然后您可以使用标准算法来sort 您的指针向量,而无需更改原始向量(如果需要):

// decreasing order according to score
sort(foob.begin(), foob.end(), [](Foo*a, Foo*b)->bool return a->score>b->score;); 

您最终可以使用for_each_n() 算法(如果是C++17)或简单地使用普通循环来更改前n 个元素。

Online demo

【讨论】:

【参考方案2】:

给出的唯一示例代码是针对指针的,仅提到了更适合 IMO 的std::reference_wrapper,没有说明如何在这种情况下使用它。我想解决这个问题!


非拥有指针至少有 3 个缺点:

视觉效果,从不得不在使用它们的代码中添加 &amp;*-&gt;; 实用:如果你想要的只是一个对象的引用,现在你有一个可以从其他指针中减去的东西(可能不相关),增加/减少(如果不是const),做重载决议或转换等的东西——你都不想要。我敢肯定每个人都在嘲笑这个并说'我永远不会犯这样愚蠢的错误',但你知道,在足够长的时间线上,它 em>发生了。 以及缺乏自我记录,因为它们没有固有的所有权语义或缺乏所有权。

我通常更喜欢std::reference_wrapper,它

清楚地自我记录其纯粹的观察语义, 只能产生对对象的引用,因此没有任何类似指针的陷阱,并且 通过隐式转换为真正的引用类型来回避许多语法问题,从而最大限度地减少可以调用转换(传递给函数、初始化引用、范围-for 等)的运算符噪音......尽管会干扰对auto 的现代偏好——至少在我们得到建议的operator.operator auto 之前——并且在其他情况下需要更详细的.get(),或者如果你只是想避免这种不一致。尽管如此,我认为这些皱纹既不比指针的皱纹更糟糕,也不可能是永久性的,因为有各种积极的提议来美化包装器/代理类型的使用。

我会推荐那个或其他词汇课程,尤其是对于公开​​的数据。有针对observer_ptrs 之类的实验性建议,但同样,如果您真的不想要类似指针的行为,那么您应该使用模拟引用的包装器......我们已经有一个那些。


所以...接受的答案中的代码可以这样重写(现在使用#includes 和我的格式偏好):

#include <algorithm>
#include <functional>
#include <vector>

// ...

void
modify_top_n(std::vector<Foo>& v, int const n)

    std::vector< std::reference_wrapper<Foo> > tmp v.begin(), v.end() ;

    std::nth_element( tmp.begin(), tmp.begin() + n, tmp.end(),
        [](Foo const& f1, Foo const& f2) return f1.score > f2.score;  );

    std::for_each( tmp.begin(), tmp.begin() + n,
        [](Foo& f) f.winner = true;  );

这利用范围构造函数从实际Foos 的范围中构造reference_wrappers 的范围,并在lambda 参数列表中隐式转换为Foo&amp; 以避免必须执行reference_wrapper.get() (然后我们可以通过. 而不是-&gt; 直接访问不那么混乱的成员。

当然,这可以概括:分解为可重用辅助函数的主要候选者是为任意Foo 构造vector&lt; reference_wrapper&lt;Foo&gt; &gt;,只给定一对Foo 的迭代器。但是我们总是要给读者留一些东西作为练习。 :P

【讨论】:

【参考方案3】:

如果您真的不想修改原始向量,则必须将指针或索引向量排序到原始向量中。要回答您的部分问题,没有办法制作参考向量,您不应该这样做。

要查找前三个(或n)元素,您甚至不必对整个向量进行排序。 STL 为您提供std::nth_element(或std::partial_sort,如果您关心顶部元素的顺序),您可以执行以下操作:

void modify_top_n(std::vector<Foo> &v, int n) 
    std::vector<Foo*> tmp(v.size());
    std::transform(v.begin(), v.end(), tmp.begin(), [](Foo &f)  return &f; );

    std::nth_element(tmp.begin(), tmp.begin() + n, tmp.end(),
        [](const Foo* f1, const Foo *f2)  return f1->score > f2->score; );
    std::for_each(tmp.begin(), tmp.begin() + n, [](Foo *f) 
        f->winner = true;
    );

假设向量至少有n 个条目。我使用 for_each 只是因为当你有一个迭代器范围时它更容易,你也可以使用一个 for 循环(或者 for_each_n 如 Christophe 提到的,如果你有 C++17)。

【讨论】:

【参考方案4】:

回答关于它的面值的问题:

引用向量(以及它们的内置数组)在 C++ 中是不合法的。以下是数组的规范标准措辞:

不应有对引用的引用,没有引用数组, 并且没有指向引用的指针。

对于向量,向量元素必须是可分配的(而引用不是)这一事实是禁止的。

要获得间接对象的数组或向量,可以使用非拥有指针 (std::vector&lt;int*&gt;),或者,如果需要非指针访问语法,则可以使用包装器 - std::reference_wrapper

【讨论】:

【参考方案5】:

所以我想我需要创建一个参考向量然后对其进行排序?创建参考向量是否合法?

不,不可能有引用向量。 std::reference_wrapper 用于此目的,或者您可以使用裸指针。

除了 Christophe 展示的两种方式之外,还有一种方式是转换迭代器适配器,它可用于使用 std::partial_sort_copy 将前 3 个指针/引用包装器排序到一个数组中。

转换迭代器只是通过调用函数在赋值时转换输入来调整输出迭代器。不过标准库中没有迭代器适配器,因此您需要自己实现一个,或者使用库。

【讨论】:

迭代器适配器是个好主意,我敢打赌 boost 有类似的东西。 我想了一些关于迭代器适配器的想法,我认为不可能实现,因为std::partial_sort_copy 需要目标范围的随机访问迭代器。前向迭代器(随机访问迭代器也是)要求它们的引用类型正好是 T&amp; 和它们的 operator* 以返回引用类型。 @Pezo (boost) 转换迭代器是随机访问,如果包装的迭代器是随机访问。我认为它应该有效,但我还没有尝试过。 我刚刚尝试过(虽然没有使用 boost 适配器)但失败了。我遇到的一个问题是partial_sort_copy 想要将一个值的本地副本分配给输出迭代器(通过移动),因此即使我们确实获取了该值的地址,它也不会是内部原始值的地址输入向量。

以上是关于创建参考向量的优雅方式的主要内容,如果未能解决你的问题,请参考以下文章

MATLAB 是不是提供了一种更优雅的方式来遍历 3D 数组以获取 3 维向量?

优雅的索引到向量/矩阵的末尾

使用 STL 算法在表(向量的向量、二维数组)中查找最小值/最大值的优雅方法

矢量调整大小 - 检测的便携方式

Spring 优雅注册 Bean 的方式

处理 PostgreSQL 异常的优雅方式?