我很惊讶我找不到这个问题。我试图将它(带有一些很好的未经测试的代码)推广到每个人都可以从中受益的东西。
假设我有一个多维Point
:
template <int dims> class Point { public: double data[dims]; };
现在我创建一个多维数组:
template <int dims> void foobar(int count0, ...) {
//Using variadic function. Could also use variadic templates in C++ (arguably better)
int counts[dims], total_count=count0; counts[0]=count0;
va_list args; va_start(args,count0);
for (int i=1;i<dims;++i) {
int count = va_arg(args,int);
counts[i] = count;
total_count *= count;
}
va_end(args);
Point<dims>* array = new Point<dims>[total_count];
//...
}
如您所见,array
是一个未知维度的多维数组,以一维数组表示。
我的问题:如何干净地将此数组初始化为多维网格点?
以下是我想要的1,2和3维示例行为。显然,我不想为我可能想要使用的每个可能的维度编写这个!目标是概括这个。
//Example: dim==1
for (int x=0; x<counts[0]; ++x) {
Point<1>& point = array[x];
point.data[0] = (x+0.5) / (double)counts[0];
}
//Example: dim==2
for (int y=0; y<counts[1]; ++y) {
for (int x=0; x<counts[0]; ++x) {
Point<2>& point = array[y*counts[0]+x];
point.data[0] = (x+0.5) / (double)counts[0];
point.data[1] = (y+0.5) / (double)counts[1];
}
}
//Example: dim==3
for (int z=0; z<counts[2]; ++z) {
for (int y=0; y<counts[1]; ++y) {
for (int x=0; x<counts[0]; ++x) {
Point<3>& point = array[(z*counts[1]+y)*counts[0]+x];
point.data[0] = (x+0.5) / (double)counts[0];
point.data[1] = (y+0.5) / (double)counts[1];
point.data[2] = (z+0.5) / (double)counts[2];
}
}
}
同样,我的问题:以干净的方式概括上述任意数量的嵌套循环/维度。
注意:我想出了一些讨厌的方式,而且它们不够优雅和缓慢。特别是,如果可能的话,我想避免递归,因为这将经常在高维小数据集上调用。 注意:C中有明显的相似之处,因此C或C ++都可以。 C ++ 11首选。
答案 0 :(得分:1)
编辑以回复评论和更新的问题
如果你需要表演和“优雅”,我会:
new
,没有指针,使用std::vector
或std::array
的C ++现代方法。dims
。所以我找到了一个与您的实现和需求非常一致的以下解决方案,并尽量保持简单。
我已经用“现代C ++ 11方式”重写了一个小的MultiArray类。我在这里考虑在编译时可能不知道count
维度,因此现在使用std::vector
。当然可以使用std::array
获得更通用的编译时代码,请参阅下面的原始答案。
#include <iostream>
#include <array>
#include <vector>
#include <numeric>
template<size_t DIMS>
class MultiArray {
public:
// Point here is just an array
using Point = std::array<double,DIMS>;
// fill data_ with an init array
// not that count is just a fix sized array here no variadic arguments needed
MultiArray(const std::array<size_t, DIMS>& count)
: data_{init_array(count)} {}
private:
// the init functions are used for the constructor
void init_point(Point& point, const std::array<size_t,DIMS>& coord, const std::array<size_t, DIMS>& count) {
std::cout << " -> { ";
for (size_t i = 0; i < DIMS; i ++) {
point[i] = (coord[i] + 0.5) / count[i];
std::cout << point[i] << ";";
}
std::cout << " }\n";
}
std::vector<Point> init_array(const std::array<size_t, DIMS>& count) {
std::vector<Point> data(std::accumulate(count.begin(), count.end(), 1, std::multiplies<int>())); // accumulate computes the prod of DIMS total_count
std::array<size_t, DIMS> current{};
size_t i=0;
do {
for (size_t i = 0; i < DIMS; i ++)
std::cout << current[i] << ";";
init_point(data[i++],current,count);
} while (increment(current, count));
return data;
}
// the following function allows to imitate the nested loop by incrementing multidim coordinates
bool increment( std::array<size_t, DIMS>& v, const std::array<size_t, DIMS>& upper) {
for (auto i = v.size(); i-- != 0; ) {
++v[i];
if (v[i] != upper[i]) {
return true;
}
v[i] = 0;
}
return false;
}
private:
std::vector<Point> data_; // A flatten multi dim vector of points
};
int main() {
std::array<size_t, 3> count{{4,5,3}};
MultiArray<3> test{count};
}
<强> Live on Coliru 强>
正如您在结果中看到的那样data_
可以针对N
维度进行初始化。如果您需要更高级别的抽象类,可以在下面检查我的原始答案,您可以在其中执行一些方便的操作(即访问grid[{i,j,k}]
以填充值)。
原始回答
我需要一个满足我需求的多维网格,并碰巧在code review上要求改进我的代码。这里有一个工作example,当然你可能不需要一些功能......我的实现与模板和编译时计算有关。请注意,尺寸大小必须在编译时知道。
简而言之,该课程将如下所示:
template<typename T, size_t... DIMS> // variadic template here for the dimensions size
class MultiGrid {
// Access from regular idx such as grid[64]
T& operator[] (size_type idx) { return values_[idx]; };
// Access from multi dimensional coordinates such as grid[{6,3,4}]
T& operator[] (const std::array<size_t,sizeof...(DIMS)>& coord) { // can give code for runtime here };
private:
std::array<T,sizeof...(DIMS)> data_;
}
然后你可以构造你的多维数组并以这些方式初始化它:
MultiGrid<float,DIM1,DIM2,DIM3> data; // 3D
// MultiGrid<float,DIM1,DIM2,DIM3,DIM4> data; // 4D
// etc...
// initialize it like this with nested arrays
for (size_t z=0; z < DIM3; z ++)
for (size_t y=0; y < DIM2; y ++)
for (size_t x=0; x < DIM1; x ++)
data[{x,y,z}] = [...] // whatever
// or like this in C++11/14 way
for (auto &x : data) x = [...] // this is convenient to provide a container like approach since no nested arrays are needed here.
如果您需要为可变参数嵌套循环指定算法以填充值,您可以查看here并使用第一个答案这样做:
// here lower_bound is 0-filled vector
std::vector<int> current = lower_bound;
do {
data[current] = [...] // fill in where current is a coordinate
} while (increment(current, lower_bound, upper_bound));
如果您需要我在实施中遗漏的内容,请随时提出。如果有人能指出改进,我也会很高兴。
答案 1 :(得分:1)
从X,Y,Z
到扁平数组(F),我们有以下等式
F=(Z*DimY+y)*DimX+X
或
F=Z*DimY*DimX+Y*DimX+X
X = F % DimX
Y = F % DimX*DimY/DimX
Z = F % DimX*DimY*DimZ/DimX*DimY
在7 x 3 x 5阵列中,Z = 3,Y = 1,X = 2将为3
* 3 * 5 + 1
* 5 + 2
= 45 + 5 + 2 = 52
X = `52` % 5 = 2
Y = `52` % (5 * 3) / 5 = 7 / 5 = 1
Z = `52` % (7 * 5 * 3)/15 = 52/15 = 3
在7 x 3 x 5阵列中,Z = 4,Y = 2,X = 3将在4
* 3 * 5 + 2
* 5 + 3
= 60 + 10 + 3 = 73
X = `73` % 5 = 3
Y = `73` % (5 * 3) / 5 = 13 / 5 = 2
Z = `73` % (7 * 5 * 3)/15 = 73/15 = 4
如果我们将累积产品保留在数组中,mult
{ 1, X, X*Y, X*Y*Z, ...}
以及数组中的所有点,val
指向扁平阵列:
F=sum(mult[i]*val[i]);
平面阵列到点:
i[0]=F%mult[1]/mult[0];
i[1]=F%mult[2]/mult[1];
...
然后我们可以迭代F(平面数组),从索引反向工程到平面数组所有点:X,Y,...如上所述,并在通用循环中进行初始化:
将mult
视为mult[0]=1; mult[d+1]=mult[d]*count[d];
for (int i = 0; i < total_count; ++i) {
for (int d=0; d < dims; ++d) {
int dim=(i%mult[d+1])/mult[d];
point.data[d] = (dim+0.5) / (double)counts[d];
}
}
答案 2 :(得分:0)
这是我的解决方案,使用C ++ 11可变参数模板和打包扩展。
我的&#34; foobar&#34;编译为constexpr
函数,因此我说这非常有效:p
至于优雅,你可以成为裁判,但我认为它很不错。
基本上,我们的想法是用函数式编程方式替换for
循环,我们只是寻求显式构建我们想要迭代的集合。然后可以以更直接的方式将该代码推广到任意维度。
除<array>
标题外,代码完全独立。
它在C ++ 11标准下用gcc 4.8.2和clang 3.6编译。
注意:如果要对维度为运行时参数的代码使用相同的技术,基本上您要做的是使用类似{之类的东西重新实现下面的Cartesian_Product
元函数作为运行时函数{1}}。然后,您可以通过获取std::vector<std::vector<size_t>>
个笛卡尔积来构建迭代集,并使用单个for循环对其进行迭代以填充结果。
dim