我很惊讶居然找不到这个问题。我试着将它一般化(使用一些不错的未经测试的代码),使之成为每个人都能从中受益的东西。
假设我有一个多维的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优先。
回复评论和更新的问题编辑
如果你需要性能和"优雅",我会:
- 放弃多维数组方法并将其扁平化(即一个数组维度)。没有
new
,没有指针,使用c++现代方法与std::vector
或std::array
。 - 为您的多dim数组提供一个抽象容器,使用方便的方法,如通用嵌套循环"生成器"
- 将可变参数替换为固定大小的数组(因为您在编译时知道
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}]
来填充值)。
原始回答
我需要一个多维网格来满足我的需求,并且碰巧在代码审查时要求改进我的代码。这里有一个工作的例子,当然有些功能对你来说可能是不必要的…我的实现涉及模板和编译时计算。注意,维度大小必须在编译时已知。
简单地说,这个类看起来像这样: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 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));
如果您需要我在实现中遗漏的东西,请随时询问。如果有人能指出改进的地方,我也会很高兴。
从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 × 3 × 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 × 3 × 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];
}
}
这是我的解决方案,使用c++ 11可变模板和包扩展。
我的"foobar"作为constexpr
函数编译,所以我想说这是相当有效的:p
至于优雅,你可以评判,但我认为它很好。
基本上,这个想法是用函数式编程的迭代方式取代for
循环,我们只是寻求显式地构建我们想要首先迭代的集合。然后,该代码可以以更直接的方式推广到任意维度。
代码是完全独立的,保存<array>
头。
它为我在c++ 11标准下编译,gcc 4.8.2和clang 3.6。
注意:如果您想在代码中使用相同的技术,其中维度是一个运行时参数,基本上您要做的是使用std::vector<std::vector<size_t>>
之类的东西将下面的Cartesian_Product
元函数重新实现为运行时函数。然后,您可以通过获取dim
个数的笛卡尔积来构建迭代集,并使用单个for循环对其进行迭代以填充结果。
#include <array>
#include <iostream>
/////////////////////////////////////////////
// The point class from problem statement
/////////////////////////////////////////////
template <size_t dims> class Point { public: double data[dims]; };
// Some basic type list and meta function objects to work with
// List of indices
template<size_t... sl>
struct StList {
static constexpr size_t length = sizeof...(sl);
};
// Metafunction to compute a volume
template <typename T>
struct Compute_Volume;
template<size_t s>
struct Compute_Volume<StList<s>> {
static constexpr size_t value = s;
};
template <size_t s1, size_t s2, size_t... sl>
struct Compute_Volume<StList<s1, s2, sl...>> {
static constexpr size_t value = s1 * Compute_Volume<StList<s2, sl...>>::value;
};
// Concatenate
template<typename TL, typename TR>
struct Concat;
template<size_t... SL, size_t... SR>
struct Concat<StList<SL...>, StList<SR...>> {
typedef StList<SL..., SR...> type;
};
template<typename TL, typename TR>
using Concat_t = typename Concat<TL, TR>::type;
// Meta function to check if a typelist is component-wise less than another typelist
// For testing purposes
template <typename TL, typename TR>
struct all_less_than;
template <size_t l1, size_t... SL, size_t r1, size_t... SR>
struct all_less_than<StList<l1, SL...>, StList<r1, SR...>> {
static constexpr bool value = (l1 < r1) && all_less_than<StList<SL...>, StList<SR...>>::value;
};
template<>
struct all_less_than<StList<>, StList<>> {
static constexpr bool value = true;
};
/////////////////////////////////////////////////////////////////////////////
// constexpr template function for the point initializations you described
/////////////////////////////////////////////////////////////////////////////
template <typename index, typename dims>
struct point_maker;
template <size_t... idx, size_t... dim>
struct point_maker<StList<idx...>, StList<dim...>> {
static_assert(sizeof...(idx) == sizeof...(dim), "misuse of 'point_maker' template, idx and dim must have same number of coordinates");
static_assert(all_less_than<StList<idx...>, StList<dim...>>::value, "misuse of 'point_maker' template, idx is out of bounds");
static constexpr Point<sizeof...(idx)> make_point() {
return {{ ((idx + 0.5) / static_cast<double>(dim))... }};
}
};
//////////////////////////////////////////////////////////////////////////////////////////
// Now we need to abstract the for loops. We need a little more infrastructure for this.
//////////////////////////////////////////////////////////////////////////////////////////
// A basic typelist object
template <typename... tl>
struct TypeList {
static constexpr size_t length = sizeof...(tl);
};
// Specialization for the Concat metafunction
template<typename... TL, typename... TR>
struct Concat<TypeList<TL...>, TypeList<TR...>> {
typedef TypeList<TL..., TR...> type;
};
// Metafunction to compute the cartesian product of two lists of lists, and evaluate the `Concat` metafunction for each pair.
template <typename S, typename T>
struct Cartesian_Product;
template <typename s1, typename s2, typename... sl, typename T>
struct Cartesian_Product<TypeList<s1, s2, sl...>, T> {
typedef Concat_t<typename Cartesian_Product<TypeList<s1>, T>::type, typename Cartesian_Product<TypeList<s2, sl...>, T>::type> type;
};
template <typename s, typename t1, typename t2, typename... tl>
struct Cartesian_Product<TypeList<s>, TypeList<t1, t2, tl...>> {
typedef Concat_t<typename Cartesian_Product<TypeList<s>, TypeList<t1>>::type, typename Cartesian_Product<TypeList<s>, TypeList<t2, tl...>>::type> type;
};
template <typename s, typename t>
struct Cartesian_Product<TypeList<s>, TypeList<t>> {
typedef TypeList<Concat_t<s, t>> type;
};
template <typename S, typename T>
using Cartesian_Product_t = typename Cartesian_Product<S, T>::type;
// Some unit tests for the above :)
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1>, StList<2>>, TypeList<StList<3>, StList<4>>>, TypeList<StList<1,3>, StList<1,4>, StList<2,3>, StList<2,4>>>::value , "cartesian product not working");
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1,5>, StList<2>>, TypeList<StList<3>, StList<4>>>, TypeList<StList<1,5,3>, StList<1,5,4>, StList<2,3>, StList<2,4>>>::value , "cartesian product not working");
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1,5>, StList<2>>, TypeList<StList<>>>, TypeList<StList<1,5>, StList<2>>>::value , "cartesian product not working");
// Metafunction to count from 0 to n
// Count from zero to n-1, and make a sequence of singleton sets containing the numbers
template<size_t s>
struct Count {
typedef Concat_t<typename Count<s-1>::type, TypeList<StList<s-1>>> type;
};
template<>
struct Count<0> {
typedef TypeList<> type;
};
template<size_t s>
using Count_t = typename Count<s>::type;
// Metafunction to abstract a series of for loops, generating the list of all the index tuples the collection of loops would generate
template <typename T>
struct Volume_Maker;
template <>
struct Volume_Maker<StList<>> {
typedef TypeList<StList<>> type;
};
template <size_t s, size_t... sl>
struct Volume_Maker<StList<s, sl...>> {
typedef Cartesian_Product_t<Count_t<s>, typename Volume_Maker<StList<sl...>>::type> type;
};
template <typename T>
using Volume_t = typename Volume_Maker<T>::type;
// Some more quick unit tests
template <typename T>
struct Volume_Test {
static_assert( Volume_t<T>::length == Compute_Volume<T>::value, "volume computation mismatch");
};
Volume_Test<StList<1,2,3>> test1{};
Volume_Test<StList<1,1,1>> test2{};
Volume_Test<StList<1>> test3{};
Volume_Test<StList<7,6,8>> test4{};
/////////////////
// Grand finale
/////////////////
template <typename dim_list, typename idx_list = Volume_t<dim_list>>
struct foobar_helper;
template <size_t... dim, typename... idx>
struct foobar_helper<StList<dim...>, TypeList<idx...>> {
typedef StList<dim...> dim_list;
typedef std::array<Point<sizeof...(dim)>, sizeof...(idx)> array_type;
static constexpr array_type make_array() {
return {{ point_maker<idx, dim_list>::make_point()... }};
}
};
template <size_t... dim>
constexpr std::array<Point<sizeof...(dim)>, Compute_Volume<StList<dim...>>::value> foobar() {
return foobar_helper<StList<dim...>>::make_array();
}
/////////////////
// Example usage
/////////////////
template <size_t ndim>
std::ostream & operator << (std::ostream & s, const Point<ndim> & p) {
s << "[ ";
for (size_t i = 0; i < ndim; ++i) {
if (i) { s << ", "; }
s << p.data[i];
}
s << " ]";
return s;
}
template<size_t ndim, size_t N>
void print_array(std::ostream & s, const std::array<Point<ndim>, N> & a) {
for (size_t i = 0; i < N; ++i) {
s << a[i] << std::endl;
}
}
int main() {
constexpr auto array1 = foobar<2,2,2>();
print_array(std::cout, array1);
constexpr auto array2 = foobar<3,3,3,3>();
print_array(std::cout, array2);
}