在 C++ 中迭代具有可变维度大小的 N 维矩阵

Posted

技术标签:

【中文标题】在 C++ 中迭代具有可变维度大小的 N 维矩阵【英文标题】:Iterating over an N-dimensional matrix with variable dimension sizes in C++ 【发布时间】:2019-05-08 14:22:30 【问题描述】:

我正在尝试迭代一个 n 维矩阵,其中矩阵的每个维度也具有可变大小。

我特别希望每个元素都具有多维坐标,例如 for loop 提供 i 以访问数组中的元素。

我想基本上重新创建一个嵌套 for 循环,如下面的代码,我可以在其中将任何不同类型的数字放入 dimensionSize

std::vector<int> dimensionSize = 1, 4, 4;

for (int i = 0; i < dimensionSize [0]; i++)

    for (int j = 0; j < dimensionSize [1]; j++)
    
        for (int k = 0; k < dimensionSize [2]; k++)
        
            std::cout << i << " " << j << " " << k << std::endl;
        
    

这个嵌套循环的结果是……

0 0 0
0 0 1
0 0 2
0 0 3
0 1 0
0 1 1
0 1 2
0 1 3
0 2 0
0 2 1
0 2 2
0 2 3
0 3 0
0 3 1
0 3 2
0 3 3

到目前为止,我在 C++ 中遍历 n 维矩阵时发现的所有示例/解决方案都是非常混乱的代码,而且对于固定维度大小,例如9x9x9 或 3x3x3。

但是我的矩阵可以是 1x256x513x3 或各种尺寸。

我目前有以下功能,它接近于我想要的,但是它有一个问题。

void iterate_over_n_dimensions (std::vector<int>& counters,
                                std::vector<int> dimensionSizes, 
                                int currentDimension)

    for (long i = 0; i < dimensionSizes [currentDimension]; i++)
    
        print_counter (counters);

        if (currentDimension + 1 != dimensionSizes.size())
        
            iterate_over_n_dimensions (counters, 
                                       dimensionSizes, 
                                       currentDimension + 1);
        

        counters [currentDimension] ++;
        counters [currentDimension] %= dimensionSizes [currentDimension];
    

运行以下...

int main(int argc, const char * argv[])

    std::vector<int> dimensionSizes = 1, 4, 4;
    std::vector<int> counters(dimensionSizes.size(), 0);

    iterate_over_n_dimensions (counters, dimensionSizes, 0);

    return 0;


这个递归函数的结果是……

0 0 0 
0 0 0 
0 0 0 
0 0 1 
0 0 2 
0 0 3 
0 1 0 
0 1 0 
0 1 1 
0 1 2 
0 1 3 
0 2 0 
0 2 0 
0 2 1 
0 2 2 
0 2 3 
0 3 0 
0 3 0 
0 3 1 
0 3 2 
0 3 3 

您可以看到,每次嵌套 for 循环完成时,输出都会重复!重复...

0 1 0 
0 2 0 
0 3 0 

我添加的维度越多,这个问题就越严重。 例如,一个 1x4x4x2 的矩阵在每次完成其第 4 维循环时重复,在其他地方更多。

如果有人能提出一个与我当前的函数一样简洁的解决方案,我将不胜感激!

现在我将不得不实现一个包含大量 for 循环的大型 switch 语句,并将维数限制在 5 个左右。

【问题讨论】:

请注意它被称为 dimension 而不是 dimention 一定要递归吗? 修正了拼写!不,它不一定是递归的,但我找不到一个非递归修复的干净示例。 尝试将其展平为一维。让大多数事情变得更容易。 @skeller 矩阵是一个非常尴尬的字节数组,其中每个维度都有自己的位步长。我正在尝试将其复制到一个特征矩阵,你是对的,可以展平。 【参考方案1】:

如果我理解正确,您想迭代“n 维矩阵”(实际上不称为“矩阵”)的所有元素,并且需要一种获取所有索引的方法。我会从这样的开始:

#include <iostream>
#include <vector>

struct multi_index 
    std::vector<size_t> dimensions;
    std::vector<size_t> current;
    multi_index(std::vector<size_t> dim) : dimensions(dim), current(dim.size()) 
    void print() 
        for (const auto& e : current) std::cout << e << " ";
        std::cout << "\n";
    
    bool increment()         
        for (size_t i=0;i<dimensions.size();++i) 
            current[i] += 1;
            if (current[i] >= dimensions[i]) 
                current[i] = 0;
             else 
                return true;
            
        
        return false;
    
;


int main() 
    multi_index x1,2,3;
    do 
        x.print();
     while (x.increment());    

然后根据具体需求进行调整。例如,向量可能是std::arrays,并且取决于实际的容器,您可能需要一种简单的方法来访问容器中的元素,而无需编写data[ x[0] ][ x[1] ][ x[2] ]

请注意,通常最好将数据扁平化为一维数据结构,然后仅映射索引。特别是当尺寸固定时,您可以从数据局部性中受益。虽然上面允许您编写一个循环来遍历所有维度,但您需要反向映射,即多个索引到一个平面索引。以下内容未经过彻底测试,仅用于概述思路:

#include <vector>
#include <utility>
template <typename T>
struct multi_dim_array 
    std::vector<T> data;
    std::vector<size_t> dimensions;
    multi_dim_array(std::vector<size_t> dim) 
        size_t total = 1;
        for (const auto& d : dim) total *= d;
        data.resize(total);
    
    T& get(std::initializer_list<int> index) 
        return data[flatten_index(index)];
    
    size_t flatten_index(std::initializer_list<int> index) 
        size_t flat = 0;
        size_t sub_size = 1;
        for (auto x = std::make_pair(0u,index.begin());
             x.first < dimensions.size() && x.second != index.end();
             ++x.first, ++x.second) 
            flat += (*x.second) * sub_size;
            sub_size *= dimensions[x.first];
        
        return flat;
    
;
int main()
    multi_dim_array<int> x2,2,3;
    x.get(1,1,1) = 5;

一旦将多维数据存储在平面数组/向量中,编写一个简单的循环来迭代所有元素应该很简单。

PS:老实说我并没有费心在你的代码中找到问题,递归让我头晕:P

【讨论】:

像魅力一样工作!非常感谢你!递归是可怕的。我要将您的代码添加到我的帖子中:) @parkywolfy 不要添加页面上已有的代码。接受答案是 Stack Overflow 的工作方式。当我们看到绿色复选标记时,我们知道是您接受了这个答案。 @perkywolfy 是的,“将您的代码添加到我的帖子中”如果您的意思是您的问题,那么请不要这样做。 Questions 是用来提问的,asnwers 是用来回答的。你的问题很好,如果你把这段代码添加到你的问题中,这将是一个糟糕的问题,我的回答将是多余的 @parkywolfy 严格来说,矩阵有两个维度。具有更多维度的结构宁愿称为张量,这是将矩阵概念推广到 n 维度 @parkywolfy 我觉得答案中缺少了一些东西,我添加了一些关于如何展平多维的内容。顺便说一句,我警告过你;)看到下一行的 我感到很痛苦,所以我真的不想接受你的编辑。我的意思是它的品味问题,但它绝对不是我的【参考方案2】:

我不确定我是否理解了这个问题。如果你想访问多维数组的每个元素一次,下面的代码应该可以完成这项工作(在我的例子中,计数器计算每个元素被访问的次数,所以最后计数应该等于 dimensionSizes):

void print_counter(std::vector<int>& counters)

    for (unsigned int i = 0; i < counters.size(); ++i)
        std::cout << counters[i] << " ";
    std::cout << std::endl;


void iterate_over_n_dimensions(std::vector<int>& counters,  std::vector<int> dimensionSizes,    int currentDimension)

    for (long i = 0; i < dimensionSizes[currentDimension]; i++)
    
        counters[currentDimension] ++;
        print_counter(counters);
    
    if (currentDimension + 1 != dimensionSizes.size())
    
        iterate_over_n_dimensions(counters,
            dimensionSizes,
            currentDimension + 1);
    




int main(int argc, const char * argv[])

    std::vector<int> dimensionSizes =  1, 4, 4 ;
    std::vector<int> counters(dimensionSizes.size(), 0);

    iterate_over_n_dimensions(counters, dimensionSizes, 0);

    return 0;

输出是:

1 0 0
1 1 0
1 2 0
1 3 0
1 4 0
1 4 1
1 4 2
1 4 3
1 4 4

【讨论】:

这并不能解决我的问题,因为我想访问每个元素一次,而不仅仅是“计算每个元素被访问的次数”。我也不能使用算法的输出,因为当我想使用计数器作为坐标访问每个元素时,它超出了维度的范围。 它只会访问每个元素一次,如果没有,计数器的末尾应该有不同的值 这个答案甚至没有访问每个元素一次,看看你的输出。该算法应生成以 0, 0, 0 开头并以 0, 3, 3 结尾的数字的每个排列。 好吧,我还没有完全理解你的问题

以上是关于在 C++ 中迭代具有可变维度大小的 N 维矩阵的主要内容,如果未能解决你的问题,请参考以下文章

创建具有可变维数的 C++ 向量

线性空间维度的公式是啥?

机器学习之——正规方程法

C++中犰狳矩阵维度的动态参数化

python数据分析Numpy

如何在 C++ 中声明 10000 x 100000 大小的整数矩阵