Help with expanding array generation beyond 2d

Hi all,

I've written a simple script that goes through and utilizes ascii character codes to fill the column of a 2D array of specified size with the alphabet. If it extends beyond Z, it resets.

My question is this: I want to generalize this program even further and allow the user to specify how many dimensions they want the array to be, how could I do this?

My issue is that in order to add dimensions, I'd have to add loops, and I don't know of a way to auto generate a loop without actually writing it... Perhaps I've been thinking about this for too long.

Thanks in advance!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  	char array[2][5]{};
	int sizey = sizeof(array)/sizeof(array[0]);
	int sizex = sizeof(array[0]);
	int ascii = 65;
	for (int j = 0; j < sizex; j++) {
		for (int i = 0; i < sizey; i++) {
			array[i][j] = ascii;
			ascii++;
			if (ascii == 91)
			ascii = 65;
		}
	}
	for (int down = 0; down < sizey; down++) {
		for (int across = 0; across < sizex; across++)
			cout << array[down][across] << ' ';
		cout << endl;
	}
The only way to do this is to "simulate" a multi-dimensional array with a 1D array. As for an unknown number of nested loops, recursion can solve that.

Here's a quick-and-dirty example, probably over-complicated in some ways and definitely not fully worked out.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>
#include <initializer_list>

template <typename T>
void print(const T *a, size_t d, size_t *sz, size_t *strides) {
    if (d == 1)
        for (size_t i = 0; i < *sz; i++)
            std::cout << a[i] << ' ';
    else
        for (size_t i = 0; i < *sz; i++)
            print(a + i * *strides, d - 1, sz + 1, strides + 1);
    std::cout << '\n';
}

template <typename T>
void fill(T *a, size_t d, size_t *sz, size_t *strides,
          T first, T last, T &ch) {
    if (d == 1)
        for (size_t i = 0; i < *sz; i++) {
            a[i] = ch++;
            if (ch > last)
                ch = first;
        }
    else
        for (size_t i = 0; i < *sz; i++)
            fill(a + i * *strides, d - 1, sz + 1, strides + 1,
                 first, last, ch);
}

template <typename T>
class MultiArray {
    T      *data;
    size_t  nDims;
    size_t *sizes;
    size_t *strides;
public:
    MultiArray(std::initializer_list<int> sz) {
        nDims = sz.size();
        sizes = new size_t[nDims];
        size_t i = 0;
        for (auto x: sz)
            sizes[i++] = x;
        strides = new size_t[nDims];
        strides[nDims - 1] = sizes[nDims - 1];
        for (size_t i = nDims - 1; i-- > 0; )
            strides[i] = strides[i + 1] * sizes[i];
        data = new T[strides[0]];
    }
    ~MultiArray() {
        delete [] data;
        delete [] strides;
        delete [] sizes;
    }
    void fill(T first, T last);
    void print();
};

template <typename T>
void MultiArray<T>::print() {
    ::print(data, nDims, sizes, strides + 1);
}

template <typename T>
void MultiArray<T>::fill(T first, T last) {
    T ch = first;
    ::fill(data, nDims, sizes, strides + 1, first, last, ch);
}

int main() {
    MultiArray<char> ma({2,3,4,5});
    ma.fill('A', 'Z');
    ma.print();

    MultiArray<int> ma2({3,2,4});
    ma2.fill(0, 9);
    ma2.print();
}

In the following example, a Hypermatrix is a generation of a matrix, where a Hypermatrix<T> of order 0 contains a single T, and a Hypermatrix<T> of order o and size s contains s-many Hypermatrix<T>, each of order o - 1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <typename T>
class Hypermatrix{
    //...
public:
    //...
    int order() const;

    //Note: if m.order() > 0, then m[0].order() == m.order() - 1
    //If m.order() == 0 then m[0] is an error.
    Hypermatrix<T> &operator[](size_t i);

    //Note: m.size() is an error if m.order() == 0
    size_t size();

    //Note: *m is an error if m.order() > 0
    T &operator*();
};

template <typename T>
void iterate(Hypermatrix<T> &m, std::function<void(T &)> &callback){
    if (!m.order()){
        callback(*m);
        return;
    }
    for (size_t i = 0; i < m.size(); i++)
        iterate(m[i], callback);
}
@helios, can you flesh that out a little into a minimal runnable example?
Sure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <vector>
#include <functional>
#include <iostream>
#include <iomanip>
#include <memory>

template <typename T>
class Hypermatrix{
    std::shared_ptr<std::vector<T>> data;
    T *first;
    std::vector<size_t> dimensions;
    size_t pitch;
    
    static void assume(bool x){
        if (!x)
            throw std::exception();
    }
    
    std::vector<size_t> slice() const{
        assume(this->dimensions.size());
        return std::vector<size_t>(this->dimensions.begin() + 1, this->dimensions.end());
    }
    void set_pitch(){
        this->pitch = 1;
        for (size_t i = 1; i < this->dimensions.size(); i++)
            this->pitch *= this->dimensions[i];
    }
    Hypermatrix(const std::shared_ptr<std::vector<T>> &data, T *first, std::vector<size_t> &&dimensions):
            data(data),
            first(first),
            dimensions(std::move(dimensions)){
        this->set_pitch();
    }
public:
    Hypermatrix(std::vector<size_t> &&dimensions): dimensions(std::move(dimensions)){
        assume(this->dimensions.size());
        this->set_pitch();
        size_t size = this->pitch * this->size();
        assume(size);
        this->data.reset(new std::vector<T>(size));
        this->first = &(*this->data)[0];
    }
    Hypermatrix(const Hypermatrix<T> &other) = default;
    Hypermatrix &operator=(const Hypermatrix<T> &other) = default;
    Hypermatrix(Hypermatrix<T> &&other) = default;
    Hypermatrix &operator=(Hypermatrix<T> &&other) = default;
    
    size_t order() const{
        return this->dimensions.size();
    }
    size_t size() const{
        assume(this->order());
        return this->dimensions.front();
    }
    Hypermatrix<T> operator[](size_t index){
        assume(index < this->size());
        return Hypermatrix<T>(this->data, this->first + index * this->pitch, this->slice());
    }
    T &operator*(){
        assume(!this->order());
        return *this->first;
    }
    
    //These two overloads allow doing m[i] = foo and foo = m[i] when m.order() == 1
    const Hypermatrix &operator=(const T &other){
        **this = other;
    }
    operator T(){
        return **this;
    }
};

template <typename T, typename F>
void iterate(Hypermatrix<T> m, const F &callback){
    if (!m.order()){
        callback(*m);
        return;
    }
    for (size_t i = 0; i < m.size(); i++)
        iterate(m[i], callback);
}

void display(Hypermatrix<int> &m){
    //Note: this doesn't use iterate() because I want the output to be readable.
    for (size_t i = 0; i < m.size(); i++){
        auto m2 = m[i];
        for (size_t j = 0; j < m2.size(); j++){
            auto m3 = m2[j];
            for (size_t k = 0; k < m3.size(); k++)
                std::cout << std::setw(4) << m3[k] << ' ';
            std::cout << std::endl;
        }
        std::cout << std::endl;
    }
}

int main(){
    try{
        Hypermatrix<int> matrix({3, 4, 5});
        iterate(matrix, [](int &x){ x = 42; });
        while (true){
            display(matrix);
            int x, y, z, v;
            std::cout << "Enter three coordinates and a value to change: ";
            std::cin >> x >> y >> z >> v;
            try{
                matrix[x][y][z] = v;
            }catch (std::exception &){
                std::cerr << "There was an error.\n";
            }
        }
    }catch (std::exception &e){
        std::cerr << "error\n";
    }
    return 0;
}


PS: Obviously in this example it would be much easier to iterate the underlying vector, but that's merely a consequence of the particular data layout I chose. The basic idea behind iterate() works regardless of the internal structure of the object.
Last edited on
@helios, thanks for the example. It's very interesting.
Topic archived. No new replies allowed.