悲催的科学匠人 - 冷水's blog
C++中实现动态多维数组模板的分析-06
这里先给出arrayindex类的源代码。
#ifndef ARRAYINDEX_HPP #define ARRAYINDEX_HPP #include <cstring> #ifdef debug0 #include <cstdio> #endif class ArrayIndex { public: ArrayIndex(size_t ndim, int ranges[][2]); ~ArrayIndex(void); size_t Getsidx(int a_midx[], int& o_stat); void Getmidx(size_t a_sidx, int o_midx[]); size_t Getlength(void); size_t Getndim(void); size_t Getdim(size_t a_n); void Getrange(size_t a_dim, int o_range[2], int& o_stat); public: enum{NORMAL, WRONG_DIM, WRONG_RANGE}; private: size_t length_; size_t ndim_; size_t maxdim_; size_t *dims_; size_t *size_; size_t **shift_; int **range_; }; inline size_t ArrayIndex::Getlength(void) { return length_; } inline size_t ArrayIndex::Getndim(void) { return ndim_; } inline size_t ArrayIndex::Getdim(size_t a_n) { return dims_[a_n]; } inline void ArrayIndex::Getrange(size_t a_dim, int o_range[2], int& o_stat) { if(a_dim>=ndim_) {o_stat = ArrayIndex::WRONG_DIM; return;} o_range[0] = range_[a_dim][0]; o_range[1] = range_[a_dim][1]; o_stat = ArrayIndex::NORMAL; return; } size_t ArrayIndex::Getsidx(int a_midx[], int& o_stat) { size_t sidx=a_midx[0]-range_[0][0]; for(size_t n=1;n<ndim_;n++) { sidx += shift_[n][ a_midx[n]-range_[n][0] ]; } return sidx; } void ArrayIndex::Getmidx(size_t a_sidx, int o_midx[]) { size_t loc = a_sidx; for(size_t n=ndim_-1; n>0; n--) { for(size_t m=0;m<dims_[n];m++) if( loc >= shift_[n][m] ) o_midx[n] = m; loc -= shift_[n][ o_midx[n] ]; o_midx[n] += range_[n][0]; } o_midx[0] = loc + range_[0][0]; } ArrayIndex::ArrayIndex(size_t ndim, int ranges[][2]): ndim_(ndim), range_(NULL) { range_ = new int* [ndim_]; range_[0] = new int [ndim_ * 2]; for(size_t n=1;n<ndim_;n++) range_[n] = range_[n-1] + 2; dims_ = new size_t[ndim_]; size_ = new size_t[ndim_]; length_ = 1; maxdim_ = 0; for(size_t n=0;n<ndim_;n++) { dims_[n] = ranges[n][1] - ranges[n][0] + 1; range_[n][0] = ranges[n][0]; range_[n][1] = ranges[n][1]; size_[n] = length_; length_ *= dims_[n]; if(dims_[n]>maxdim_) maxdim_ = dims_[n]; } shift_ = new size_t* [ndim_]; shift_[0] = new size_t[maxdim_*ndim_]; for(size_t n=1;n<ndim_;n++) shift_[n] = shift_[n-1] + maxdim_; for(size_t n=1;n<ndim_;n++) { shift_[n][0] = 0; for(size_t m=1;m<dims_[n];m++) { shift_[n][m] = shift_[n][m-1] + size_[n]; } } return; } ArrayIndex::~ArrayIndex(void) { delete [] shift_[0]; delete [] shift_; delete [] range_[0]; delete [] range_; delete [] size_; delete [] dims_; } #endif
还有一个测试代码
#include "qkarray.hpp" int main(void) { int stat; size_t ndim=5; int range[5][2] = {{1,2},{5,7},{1,5},{9,10},{1,1}}, midx[20]; ArrayIndex index(ndim,range); index.Getrange(4, range[0], stat); if(ndim==5) { size_t count = 0; for(size_t n=0;n<ndim;n++) { midx[n] = 0; index.Getrange(n,range[n],stat); printf(" Range[%5ld] = %5ld:%5ld\n", n, range[n][0],range[n][1]); } printf(" Array size = %d\n",index.Getlength() ); count = 0; for(int m=range[4][0];m<=range[4][1];m++) for(int l=range[3][0];l<=range[3][1];l++) for(int k=range[2][0];k<=range[2][1];k++) for(int j=range[1][0];j<=range[1][1];j++) for(int i=range[0][0];i<=range[0][1];i++) { midx[0] = i; midx[1] = j; midx[2] = k; midx[3] = l;midx[4] = m; index.Getmidx(count, midx); count ++; printf("[%5ld] [%5ld] [%5ld] [%5ld] [%5ld] == %5ld \n", midx[0], midx[1], midx[2], midx[3],midx[4], index.Getsidx(midx, stat)); } } return 1; }
C++中实现动态多维数组模板的分析-05
在分析04中,我写出了多维编号到一维编号的转换关系。不过那时假设多维编号采用C风格,都是从0开始的。实际我需要支持非0开始的编号,即可以从任一一个整数a开始以1递增到另外一个不小于a的整数b。在这种情况下,要将非0开始的多维编号转换为内部以0开始的编号,就可以使用前述的公式了。
这次,开始进行设计。
首先命名这个类为 arrayindex
它必须具备的最重要的方法有
1 根据给定的维数和每个维的范围创建一个实例
ArrayIndex(size_t ndim, int ranges[][2]);
ndim是一个非负数,表示维数。range是每个维的范围range[i][0]是起始编号,
range[i][1]是终止编号。为了方便,令i>=1
2 从一个多维编号转换到一维编号
size_t Getsidx(int a_midx[], int& o_stat);
a_midx[]存储多维编号,为方便起见,从a_mix[1]开始有效。
3 从一个一维编号转换到多维编号
void Getmidx(size_t a_sidx, int o_midx[]);
这个类的关键成员应该包含
size_t length_; // 数组数据总个数
size_t ndim_; // 维数
size_t maxdim_; // 最大维数
size_t *dims_; // 每个维的尺寸
size_t *size_; // 每个维度上切片的尺寸。比如,数组[5][3][2],第一维是一维的串,它的切片就是单个元素,尺寸为1;第二维是一个二维的片,其切片是低一维的串,每个串的尺寸是5;第三维是一个三维块,其切片是低一维片,尺寸是5X3。如此类推到更高维。
size_t **shift_; // 每个维上所有标号的偏移。比如数组[5][3][2],按照我们先前的分析,必须存储第一维的5个偏移,第二维的3个偏移和第三维的2个偏移。因此shift_是个二维数组。
int **range_; // 每个维的范围
C++中实现动态多维数组模板的分析-04
对于一维化存储的数据,实现多维编号访问,索引是必须的。
考虑2D数组 array[0..I-1][0..J-1],为了方便采用FORTRAN的数组元素排列顺序,即
for (j=0;j<J;j++) for (i=0;i<I;i++)
那么一维编号
为了避免乘法,我们可以预先计算好。则有一维编号计算公式为
代价是我们必须事先计算好个。
再考虑3D数组 array[0..I-1][0..J-1][0..K-1],其一维编号计算公式为
同理,令和,上式可写为
这个方法可以推导到任意维数组。即对于维数组,设其第维范围是,我们事先计算好偏移
此外对于第一维
则元素的一维编号计算格式为
这样对于多维数组,只需要实现计算好个常数,即可采用上式来快速计算一维编号。
C++中实现动态多维数组模板的分析-03
基本上,我计划采用的方案是:
- 数据在内部采用一维数组方式存储。这样数据在内存中连续分布,有利于cach命中(其实我也不知道这个名称确切意义)。这对高性能计算这样的应用来说是必须的。
- 在数组类中封装若干索引数据,以支持多维指标到一维指标的互相转换。
- 索引数据可单独封装为一个类,实现数据和维数表达的分离。以便于多个数组对象之间共享,或者数组维数变化情况。
- 索引数据应该尽量的少。C语言中利用指针数组实现多维特性是不可以接收的,因为指针数组的维数只比数据数组的维数少了一维而已。当数组很大时(比如要几个G的空间),指针数组也很大,造成空间浪费。在我所考虑的方案中,对于数组[L][M][N],只需要L+M+N个size_t类型的索引。因此,即使L*M*N很大(比如1000*1000*1000),L+M+N(才3000而已)也很小。
- 目前不打算支持动态的数组尺寸。但是当基本功能实现之后,这个特性应该不难扩展。