从结构向量中,获取一个向量,该向量收集每个结构的一个字段

From a vector of structs, get a vector that collects one of the fields for every struct

本文关键字:向量 结构 一个 字段 获取      更新时间:2023-10-16

假设我有以下结构:

struct Point {
double X,Y,Z;
};

和以下向量:

std::vector<Point> v;
// populate v with random points

现在,我想调用类似collect(v, X)的东西,并获取一个包含原始结构向量X值的std::vector,例如:

v.push_back(Point{1.0, 2.0,  3.0});
v.push_back(Point{1.1, 0.0, -0.5});
auto ans = collect(v,X);
// ans = [1.0, 1.1]

我认为这是一项非常常见的任务,而且我确信有一个好名字,我在询问时想不出(随时指出我!

我可以这样做:

std::vector<double> collectX(std::vector<Point> v) {
std::vector<double> output;
for (auto elem : v) {
output.push_back(elem.X);
}
}
/* Repeat for each field the struct Point has... */

我知道C++没有反思。我想知道是否有解决方法?正如您可能想象的那样,我正在使用的结构不仅有 3 个字段,因此为每个字段编写一个方法有点令人生畏和不优雅。

所以为每个字段编写一个方法有点令人生畏和不优雅

立即解决此问题的是将字段标识符作为参数传递。

std::vector<double> collect(double Point::* f, std::vector<Point> const& v) {
std::vector<double> output;
for (auto const& elem : v) {
output.push_back(elem.*f);
}
return output;
}

这样称呼:

collect(&Point::X, v);

如果类型并不总是double,那么上面的内容可以很容易地成为成员类型的模板:

template<typename T>
std::vector<T> collect(T Point::* f, std::vector<Point> const& v) {
std::vector<T> output;
for (auto const& elem : v) {
output.push_back(elem.*f);
}
return output;
}

最后,您正在寻找这种提取的术语是"投影"。即,粗略地说,将函数投影到轴上时会得到什么。在我们的例子中,函数将向量的索引映射到Point,并且投影位于x轴上,就像它一样。

它也可以使用 C++ 标准库或 ranges-v3 库即时编写。投影是项目范围的一种非常常见的操作,因此许多以范围为中心的库都有这样做的功能。

使用std::transformstd::back_inserterstd::mem_fn

#include <functional>
//...
std::vector<Point> v{{0,1,2},{9,8,7}};
std::vector<double> x;
std::transform(v.begin(), v.end(), std::back_inserter(x),
std::mem_fn(&Point::x));

编译器通常可以优化std::mem_fn背后的间接寻址。

您可以使用std::transformstd::back_inserter

std::vector<Point> v;
v.push_back(Point{1.0, 2.0,  3.0});
v.push_back(Point{1.1, 0.0, -0.5});
std::vector<double> x;
std::transform(v.begin(), v.end(), std::back_inserter(x),
[](Point const& p) -> double { return p.x; });

你可以为这种东西使用模板

template<typename C, typename F>
auto vmap(F f, const C& c) -> std::vector<decltype(f(*c.begin()))> {
std::vector<decltype(f(*c.begin()))> res;
for (auto& x : c) res.push_back(f(x));
return res;
}

用作

auto x_values = vmap([](const Point& p){ return p.x; }, pts);

vmap(f, c)返回应用于c元素的任何fstd::vectorc是任何标准容器。

要提取我正在使用的xflambda[](const Point& p){ return p.x; }.

范围 v3 + 宏解决方案,可用于所有字段和类型:

#define view_extract_field(fname) (view::transform([](const auto& val) { return val.fname; }))
auto vx = v | view_extract_field(X) | to_vector;

这不会直接回答您的问题。如果您担心性能,并且需要经常执行此拆分,则考虑不同的存储设计可能会很有用。

class Points {
public:
//Constructor ...
std::vector<double> & getX() const;
private:
std::vector<double> x; 
std::vector<double> y;
std::vector<double> z;
};
std::vector<double> & Points::getX() {
return x;
}

通过这种方式,您不必复制点的 x 值(这可能是非常大的内存量),并且您可以快速访问它们。另一方面,缓存一个点的局部性会变得更糟。因此,如果您有性能问题衡量标准,那么这可能是一个想法。