0

I've been thinking if it is possible to actually let the user decide how many "dimensions" an array should have, based on a number given.

Let n be the number of dimensions, the user will type in its value. It will create an array with n dimensions.

Example: for n=5, it will create an array called list, like that: int list[size1][size2][size3][size4][size5]. size variables will still be mentioned by the user, but that's actually part 2.

I want to know if I can add more dimensions to an array, after I have declared it. And if not, I want to find a solution to this problem.

  • You seem to be trying to ask two questions here. It's easy to create matrixes with variable dimensions. Adding dimensions is a bit more tricky. –  May 16 '18 at 22:56
  • The dimensionality of an array is specified by its declaration, so by definition, no. It's likely you are trying to solve a problem and are asking about the solution you have thought of instead of your actual problem: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – xaxxon May 16 '18 at 23:08
  • You can get behavior like this by using a `std::vector` for your n-dimensional array. Instead of `vec[z][y][x]`, you'd be using something like `vec[compute_index(x, y, z, xSize, ySize, zSize)]` (although you'd need to use vectors for the arguments to `compute_index`, as you also have varying number of arguments) – Justin May 16 '18 at 23:10
  • It's not too difficult, if you already know how to use dynamic allocation (e.g. `int *Z = new int[5];`). – Beta May 16 '18 at 23:29
  • Related: https://stackoverflow.com/q/11169418/1025391 – moooeeeep May 17 '18 at 11:13

3 Answers3

2

The C++ language does not have provision for variable-sized or variable-dimensioned arrays.

You can, however, create a class to encapsulate these behaviors.

The important characteristic is the dimensions. You can use a std::vector<int> to track the number of elements per dimension; for example, {3, 4, 5} to represent a three-dimensional matrix where the rank of the innermost dimension is 3, the middle 4, and the outer 5.

Use a templated vector or deque to allocate space for the elements. The number of elements required is the product of the dimension ranks. (You can use std::accumulate with a multiplication operator to compute this over your ranks vector.)

Next, you'll need a method that takes some object (say, a vector of int) that provides all the indices into the MD-array necessary to access an element. You can provide overloads that take a variable number of arguments using some fancy template metaprogramming.

All of this is overkill outside of some very specialized uses, such as: you are writing Mathematica-like software that allows users to play with these things.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
1

You may be interested in an array class I implemented a few months ago that aims to provide a syntax for arrays that mimics that of matlab arrays. It utilizes initilizer_list syntax to allow for arbitrary dimensional arrays to be created using

Array<double> array({10, 20, 30});

You can then access and modify individual elements using

double d = array[{1, 2, 3}];
array[{1, 2, 3}] = 10;

And even slice the matrix up into pieces using

array.getSlice({___, 3, 4});

where "___" is used as a wildcard.

See more on: http://www.second-quantization.com/Array.html

Implementation: https://github.com/dafer45/TBTK/blob/master/Lib/include/Utilities/TBTK/Array.h

  • Identifiers containing `__` are reserved for implementation use; you should call your wildcard something else – M.M May 17 '18 at 02:13
  • There are three underscores rather than two and the identifier is not in the global namespace. Is it still problematic? In fact, the three underscores are syntactic sugar for the longer identifier IDX_ALL. – Kristofer Björnson May 17 '18 at 09:44
  • Thanks! I will likely change to \_w\_ in the future then, which seems to be fine as long as it is not in the global namespace. However, I'm perplexed by the reason why any combination of double underscores is reserved in any namespace. It seems to me that there are many cases where multiple adjacent underscores in the middle of a name should be without risk of ever clashing with anything else. I aslo wonder why I have never seen a compiler warning about this if it is in the standard? – Kristofer Björnson May 17 '18 at 11:45
  • It gives the implementation freedom to use #defines without risk of program clash. The standard doesn't require compilers to warn, it's just UB with no diagnostic required – M.M May 17 '18 at 12:10
0

Solution for object let's the user choose the number of dimensions. A little robust, my C++ maybe is not the best, but it was fun implementing. nvector<T> represents resizable (in dimensions and count of elements in each dimension) array of element of T type, although only some resize functions are implemented. narray<T> is the same, but the number of dimensions is not resizable. This works around the idea of recalculating index position of a multidimensional array using a single continuous array.

#include <cstdio>
#include <vector>
#include <iostream>
#include <cstddef>
#include <cstdarg>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <memory>
#include <cstring>
using namespace std;

template<typename T>
class narray {
public:
    static size_t compute_size(initializer_list<size_t>& dims) {
        return accumulate(dims.begin(), dims.end(), 1, multiplies<size_t>());
    }
    static size_t compute_size(vector<size_t>& dims) {
        return accumulate(dims.begin(), dims.end(), 1, multiplies<size_t>());
    }
    static size_t compute_distance(vector<size_t>& dims) {
        return dims.size() > 1 ? dims[1] : 1;
    }
    static vector<size_t> remove_one_dim(vector<size_t> dims_) {
        return vector<size_t>(dims_.begin() + 1, dims_.end());
    }

    narray(initializer_list<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    narray(vector<size_t> dims, T* data) :
        dims_(dims), data_(data) {}

    T operator*() {
        return *data_;
    }
    T* operator&() {
        return data_;
    }
    void operator=(T v) {
        if (dims_.size() != 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        *data_ = v;
    }
    void operator=(initializer_list<T> v) {
        if (v.size() > size())
            throw runtime_error(__PRETTY_FUNCTION__);
        copy(v.begin(), v.end(), data_);
    }
    T* data() {
        return data_;
    }
    T* data_last() {
        return &data()[compute_size(dims_)];
    }
    size_t size() {
        return compute_size(dims_);
    }
    size_t size(size_t idx) {
        return dims_[idx];
    }
    narray<T> operator[](size_t idx) {
        if (dims_.size() == 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        return narray<T>(remove_one_dim(dims_),
                &data_[idx * compute_distance(dims_)]);
    }

    class iterator {
    public:
        iterator(initializer_list<size_t>& dims, T* data) :
            dims_(dims), data_(data) { }
        iterator(vector<size_t>& dims, T* data) :
            dims_(dims), data_(data) { }
        iterator operator++() {
            iterator i = *this;
            data_ += compute_distance(dims_);
            return i;
        }
        narray<T> operator*() {
            return narray<T>(remove_one_dim(dims_), data_);
        }
        bool operator!=(const iterator& rhs) {
            if (dims_ != rhs.dims_)
                throw runtime_error(__PRETTY_FUNCTION__);
            return data_ != rhs.data_;
        }
    private:
        vector<size_t> dims_;
        T* data_;
    };

    iterator begin() {
        return iterator(dims_, data());
    }
    iterator end() {
        return iterator(dims_, data_last());
    }
private:
    vector<size_t> dims_;
    T* data_;
};

template<typename T>
class nvector {
public:
    nvector(initializer_list<size_t> dims) :
        dims_(dims), data_(narray<T>::compute_size(dims)) {}
    nvector(vector<size_t> dims) :
        dims_(dims), data_(narray<T>::compute_size(dims)) {}
    nvector(initializer_list<size_t> dims, T* data) :
        dims_(dims), data_(data) {}
    nvector(vector<size_t> dims, T* data) :
        dims_(dims), data_(data) {}

    T* data() {
        return data_.data();
    }
    T* data_last() {
        return &data()[narray<T>::compute_size(dims_)];
    }
    size_t size() {
        return narray<T>::compute_size(dims_);
    }
    narray<T> operator&() {
        return narray<T>(dims_, data());
    }
    narray<T> operator[](size_t idx) {
        if (dims_.size() == 0)
            throw runtime_error(__PRETTY_FUNCTION__);
        return narray<T>(narray<T>::remove_one_dim(dims_),
                &data()[idx * narray<T>::compute_distance(dims_)]);
    }
    void operator=(initializer_list<T> v) {
        if (v.size() > size())
            throw runtime_error(__PRETTY_FUNCTION__);
        copy(v.begin(), v.end(), data_.begin());
    }
    auto begin() {
        return typename narray<T>::iterator(dims_, data());
    }
    auto end() {
        return typename narray<T>::iterator(dims_, data_last());
    }

    // add and remove dimensions
    void dimension_push_back(size_t dimsize) {
        dims_.push_back(dimsize);
        data_.resize(size());
    }
    void dimension_pop_back() {
        dims_.pop_back();
        data_.resize(size());
    }
    // TODO: resize dimension of index idx?

private:
    vector<size_t> dims_;
    vector<T> data_;
};

int main()
{
    nvector<int> A({2, 3});
    A = { 1,2,3, 4,5,6 };
    assert(A.size() == 6);
    assert(&A[0] == &A.data()[0]);
    assert(&A[0][0] == &A.data()[0]);
    assert(&A[1] == &A.data()[3]);
    assert(&A[0][1] == &A.data()[1]);
    assert(&A[1][1] == &A.data()[4]);

    cout << "Currently array has " << A.size() << " elements: " << endl;
    for(narray<int> arr1 : A) { // we iterate over arrays/dimensions
        for(narray<int> arr2 : arr1) { // the last array has no dimensions
            cout << "elem: " << *arr2 << endl;
        }
    }
    cout << endl;

    // assigment example
    cout << "Now it is 4:  " << *A[1][0] << endl;
    A[1][0] = 10;
    cout << "Now it is 10: " << *A[1][0] << endl;

    return 0;
}

This code needs still much more work. It works only as a simple example. Maybe use shared_ptr in narray? Implement better exceptions? So creating an array of n=5 dimensions with sizes size1, size2, size3, size4 and size5 would like this:

 narray<int> arr({size1, size2, size3, size4, size5});
 arr[0][1][2][3][4] = 5; // yay!
KamilCuk
  • 120,984
  • 8
  • 59
  • 111