C确定n维阵列中“扁平”第i个元素的“未展平”位置

时间:2013-10-18 06:50:25

标签: c arrays indexing metaprogramming

我有以下(不完整)功能:

/* Populates char* name with the named location of the ith (flat) element
 * of an array with ndim dimensions where the length of each dimension 
 * is already stored in the int* dim.
 * 
 * name: a pointer to where the name should be populated
 * n: the base name of the array
 * dim: an int[] containing the length of each dimension
 * ndim: length of the dim array
 * i: name of the iteration variable being used
 **/
void populateName(char *name, const char *n, int *dim, int ndim, const char *i) {
  strcpy(name, n);
  char *loc = (char*)(name + strlen(n));
  char *curr;
  for (int k = 0; k < ndim; k++) {
    ...
    sprintf(loc, "[%s]", curr);
    loc += strlen(loc);
  }
}

for循环中的“...”应该包含哪些内容?例如,使用:

调用populateName()
int dim[2] = {3, 4};
char name[1024];
populateName(name, "x", dim, 2, "i");

应该会产生类似的结果:

name = "x[i / 3][i % 4]"

或用于访问定义为:

的数组中的第i个位置的其他有效名称
int x[3][4];

上下文:我正在编写一个C程序,它生成C程序,根据用户定义的数据类型和用IDL编写的规则过滤大量数据。

编辑:一个返回包含数组中位置/坐标的元组的python函数可能会让我朝着正确的方向前进。特别是下面的数组应该让每个元素对应于它在数组中的平面位置(在这里使用pylab):

In [14]: x
Out[14]: 
array([[[ 0,  1,  2],
    [ 3,  4,  5]],

   [[ 6,  7,  8],
    [ 9, 10, 11]],

   [[12, 13, 14],
    [15, 16, 17]]])

In [15]: x.flat.copy()
Out[15]: 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17])

1 个答案:

答案 0 :(得分:3)

解决像这样的问题的好方法是尝试一些例子。请考虑以下图片,其中显示了3D阵列x[2][3][5]的内存布局:

description

我们如何将偏移14转换为位置x[0][2][4]?好吧,首先,我们看到每个x[i]包含15(3 * 5)个块,所以首先我们通过计算整数除法14/15 = 0来确定14属于哪个块。所以,偏移14位于x[0]内。

我们现在可以应用相同的方法。 x[i][j]包含5个块,因此偏移量14属于块号14/5 = 2.实际上,正确的计算是(14/5)%3,正如我们将看到的偏移量18.最后,{{{ 1}}包含单个块,因此最后一个索引由14%5给出。可以这样想:我们正在解释这些内存块,就好像它们在每一步都有不同的大小。首先,我们假设所有内容都分为15个元素。然后,我们假设所有内容都被划分为5个元素。

您可以使用此示例,并看到偏移18映射到x[i][j][k],因为18/15 = 1; (18/5)%3 = 0,18%5 = 3.

可以看出,一般情况是对于维x[1][0][3],我们将内存布局解释为在n块中组织,其中j是每个j的产物维度大于n,因此我们必须将位置(i/j)%n编入索引。

这是我的实施:

void populateName(char *name, const char *n, int *dim, int ndim, const char *i) {
  strcpy(name, n);
  char *loc = (char*)(name + strlen(n));
  int j;
  int *mul = malloc(sizeof(int)*ndim);
  mul[ndim-1] = 1;
  /* Compute cumulative multipliers array */
  for (j = ndim-2; j >= 0; j--) {
    mul[j] = mul[j+1] * dim[j+1];
  } 
  for (j = 0; j < ndim; j++) {
    loc += sprintf(loc, "[(%s/%d)%%%d]", i, mul[j], dim[j]);
  }
  free(mul);
}

如您所见,它使用累加的乘数数组,其中mul[i]包含大于i的每个维度的乘积。

顺便说一下,你不需要curr;由于sprintf会返回打印的字符数,因此我们只需移动相同金额的loc。它比strlen之后重复调用sprintf更有效率。

我没有太多时间来测试这个,但是根据我展示的例子,我得到了这个:

x[(i/15)%2][(i/5)%3][(i/1)%5]

哪个看起来正确。这是一个示例程序:

int main()
{
    int dims[] = { 2, 3, 5, 7, 9 };
    char name[1024];
    populateName(name, "x", dims, 5, "i");
    printf("%s\n", name);
    return 0;
}

打印:

x[(i/945)%2][(i/315)%3][(i/63)%5][(i/9)%7][(i/1)%9]

读取任意n维数组变得比较棘手,但原理总是一样的。