创建参考向量的优雅方式
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<Foo*>
:
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 个缺点:
视觉效果,从不得不在使用它们的代码中添加&
、*
和 ->
;
实用:如果你想要的只是一个对象的引用,现在你有一个可以从其他指针中减去的东西(可能不相关),增加/减少(如果不是const
),做重载决议或转换等的东西——你都不想要。我敢肯定每个人都在嘲笑这个并说'我永远不会犯这样愚蠢的错误',但你知道,在足够长的时间线上,它会 em>发生了。
以及缺乏自我记录,因为它们没有固有的所有权语义或缺乏所有权。
我通常更喜欢std::reference_wrapper
,它
for
等)的运算符噪音......尽管会干扰对auto
的现代偏好——至少在我们得到建议的operator.
或operator auto
之前——并且在其他情况下需要更详细的.get()
,或者如果你只是想避免这种不一致。尽管如此,我认为这些皱纹既不比指针的皱纹更糟糕,也不可能是永久性的,因为有各种积极的提议来美化包装器/代理类型的使用。
我会推荐那个或其他词汇课程,尤其是对于公开的数据。有针对observer_ptr
s 之类的实验性建议,但同样,如果您真的不想要类似指针的行为,那么您应该使用模拟引用的包装器......我们已经有一个那些。
所以...接受的答案中的代码可以这样重写(现在使用#include
s 和我的格式偏好):
#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; );
这利用范围构造函数从实际Foo
s 的范围中构造reference_wrapper
s 的范围,并在lambda 参数列表中隐式转换为Foo&
以避免必须执行reference_wrapper.get()
(然后我们可以通过.
而不是->
直接访问不那么混乱的成员。
当然,这可以概括:分解为可重用辅助函数的主要候选者是为任意Foo
构造vector< reference_wrapper<Foo> >
,只给定一对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<int*>
),或者,如果需要非指针访问语法,则可以使用包装器 - std::reference_wrapper
。
【讨论】:
【参考方案5】:所以我想我需要创建一个参考向量然后对其进行排序?创建参考向量是否合法?
不,不可能有引用向量。 std::reference_wrapper
用于此目的,或者您可以使用裸指针。
除了 Christophe 展示的两种方式之外,还有一种方式是转换迭代器适配器,它可用于使用 std::partial_sort_copy
将前 3 个指针/引用包装器排序到一个数组中。
转换迭代器只是通过调用函数在赋值时转换输入来调整输出迭代器。不过标准库中没有迭代器适配器,因此您需要自己实现一个,或者使用库。
【讨论】:
迭代器适配器是个好主意,我敢打赌 boost 有类似的东西。 我想了一些关于迭代器适配器的想法,我认为不可能实现,因为std::partial_sort_copy
需要目标范围的随机访问迭代器。前向迭代器(随机访问迭代器也是)要求它们的引用类型正好是 T&
和它们的 operator*
以返回引用类型。
@Pezo (boost) 转换迭代器是随机访问,如果包装的迭代器是随机访问。我认为它应该有效,但我还没有尝试过。
我刚刚尝试过(虽然没有使用 boost 适配器)但失败了。我遇到的一个问题是partial_sort_copy
想要将一个值的本地副本分配给输出迭代器(通过移动),因此即使我们确实获取了该值的地址,它也不会是内部原始值的地址输入向量。以上是关于创建参考向量的优雅方式的主要内容,如果未能解决你的问题,请参考以下文章
MATLAB 是不是提供了一种更优雅的方式来遍历 3D 数组以获取 3 维向量?