4

I have a function in C++ (call it "weightmatrix") that takes a vector of size n as input, which contains a series of numbers a, b, c..., and returns an n-dimensional vector made up of subvectors of size c, b, a...

That was complicated sounding. Basically, in practice it should look like this:

vector<int> vector_sizes = {2, 3, 1}; 
cout << "Resulting matrix: " << weightmatrix(vector_sizes); // Function takes vector of size 3
/* Returns the following 3 dimensional matrix:
   { { {0, 0}, 
       {0, 0}, 
       {0, 0} }, 
    {0, 0, 0} }
*/

It's a weird one, I know. I just need to generate a vector without knowing beforehand how many dimensions it will be. Any help or advice you could throw in my way would be awesome.

L.C.J
  • 2,993
  • 4
  • 15
  • 16
  • `std::vector>> vec3d;` maybe? – super Jan 26 '18 at 06:11
  • Or is the number of dimensions also unknown? – super Jan 26 '18 at 06:13
  • Unknown to the program, yes. Say I have the number of dimensions saved as a variable. This variable could be anything from 1 to 5 and up. So is there a way I can generate the matrix just based on this number? – L.C.J Jan 26 '18 at 06:16
  • 3
    I think you are going to need to create a class that internally keeps a one-dimensional vector and resizes it to be the product of all the dimensions. Then you could have a variadic template function that accepts coordinates into the dimensions and does the math to locate a single cell. – Galik Jan 26 '18 at 06:22
  • @L.C.J could you please expand a bit on the vector you want to get? I mean when `vector_sizes = {2, 3, 1};`, why is the 3D matrix as the one you showed us? – gsamaras Jan 26 '18 at 07:15
  • I doubt you'll want to bring in a large library for this but just putting it out there, OpenCV's Mat class does this and more. – kmdreko Jan 26 '18 at 07:47

2 Answers2

4

Here is a solution using a template MultiVector class that returns a MultiVectorView from it's operator[].

The underlying data is stored in a plain std::vector but can be accessed with the vec[x][y][z] syntax.

There is no checking for correct usage, and only very basic functionality is implemented but it gives an idea how it could be done.

#include <iostream>
#include <vector>

template <typename T>
class MultiVector;

template <typename T>
class MultiVectorView {
public:
    MultiVectorView(MultiVector<T>& vec_, int index_, int dimension_) : vec(vec_), index(index_), dimension(dimension_) {}

    MultiVector<T>& vec;
    int index;
    int dimension;

    MultiVectorView& operator[](std::size_t n_index) {
        int index_multiplyer = 1;
        for (int i=0; i<dimension; ++i)
            index_multiplyer *= vec.dimensions[i];
        index += n_index*index_multiplyer;
        dimension++;
        return *this;
    }

    operator T() {
        return vec.content[index];
    }

    MultiVectorView& operator=(T val) {
        vec.content[index] = val;
        return *this;
    }
};

template <typename T>
class MultiVector {
public:
    MultiVector(std::vector<int> dimensions_) : dimensions(dimensions_) {
        int size = dimensions[0];
        for (int i = 1; i<dimensions.size(); ++i)
            size *= dimensions[i];

        content.resize(size);
    }

    MultiVectorView<T> operator[](std::size_t index) {
        return MultiVectorView<T>(*this, index, 1);
    }

    std::vector<T> content;
    std::vector<int> dimensions;
};

int main() {
    std::vector<int> v = {2,3,2};
    MultiVector<int> test(v);

    int tmp = 0;
    for (int x = 0; x < v[0]; ++x)
        for (int y = 0; y < v[1]; ++y)
            for (int z = 0; z < v[2]; ++z) {
                test[x][y][z] = tmp++;
            }

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

    int b = test[1][2][1];

    std::cout << std::endl << "b = " << b << std::endl << "test[0][1][1] = " << test[0][1][1] << std::endl;
}
super
  • 12,335
  • 2
  • 19
  • 29
  • Y'all are a brilliant bunch, really. I'm gonna go with this one for relative simplicity. Much appreciated. – L.C.J Jan 27 '18 at 19:25
3

I took the hint of Galik to make a small sample:

#include <cassert>
#include <iostream>
#include <vector>

template <typename ELEM>
class NDArrayT {
  private:
    // dimensions
    std::vector<size_t> _dims;
    // data
    std::vector<ELEM> _data;

  public:
    NDArrayT(const std::vector<size_t> &dims):
      _dims(dims)
    {
      size_t size = _dims.empty() ? 0 : 1;
      for (size_t dim : _dims) size *= dim;
      _data.resize(size);
    }
    NDArrayT(
      const std::vector<size_t> &dims,
      const std::vector<ELEM> &data):
      NDArrayT<ELEM>(dims)
    {
      assert(_data.size() == data.size());
      std::copy(data.begin(), data.end(), _data.begin());
    }

    ELEM& operator[](const std::vector<size_t> &indices)
    {
      size_t i = 0, j = 0;
      for (size_t n = _dims.size(); j < n; ++j) {
        i *= _dims[j]; i += indices[j];
      }
      return _data[i];
    }

    const ELEM& operator[](const std::vector<size_t> &indices) const
    {
      size_t i = 0, j = 0;
      for (size_t n = _dims.size(); j < n; ++j) {
        i *= _dims[j]; i += indices[j];
      }
      return _data[i];
    }
};

using namespace std;

ostream& operator<<(ostream &out, const vector<size_t> &values)
{
  const char *sep = "";
  for (size_t value : values) {
    out << sep << value; sep = ", ";
  }
  return out;
}

bool inc(vector<size_t> &indices, const vector<size_t> &dims)
{
  for (size_t i = indices.size(); i--;) {
    if (++indices[i] < dims[i]) return false;
    indices[i] = 0;
  }
  return true; // overflow
}

int main()
{
  // build sample data
  vector<double> data(2 * 3 * 4);
  for (size_t i = data.size(); i--;) data[i] = (double)i;
  // build sample array
  typedef NDArrayT<double> NDArrayDouble;
  const vector<size_t> dims = { 2, 3, 4 };
  NDArrayDouble a(dims, data);
  // print sample array (check subscript)
  vector<size_t> indices(dims.size(), 0);
  do {
    cout << "a[" << indices << "]: " << a[indices] << endl;
  } while (!inc(indices, dims));
  // done
  return 0;
}

Compiled and tested on ideone. Output is:

a[0, 0, 0]: 0
a[0, 0, 1]: 1
a[0, 0, 2]: 2
a[0, 0, 3]: 3
a[0, 1, 0]: 4
a[0, 1, 1]: 5
a[0, 1, 2]: 6
a[0, 1, 3]: 7
a[0, 2, 0]: 8
a[0, 2, 1]: 9
a[0, 2, 2]: 10
a[0, 2, 3]: 11
a[1, 0, 0]: 12
a[1, 0, 1]: 13
a[1, 0, 2]: 14
a[1, 0, 3]: 15
a[1, 1, 0]: 16
a[1, 1, 1]: 17
a[1, 1, 2]: 18
a[1, 1, 3]: 19
a[1, 2, 0]: 20
a[1, 2, 1]: 21
a[1, 2, 2]: 22
a[1, 2, 3]: 23

The "arithmetic" to manage multi-dimensional arrays in contiguous memory is actually quite simple. I guess, the most "revolutionary" idea of this sample is the operator[]() which uses a std::vector<size_t> to provide the indices for each dimension.

While I was writing this down, a lot of alternatives for indexing came in my mind. – There is much space for fantasy...

E.g. for linear (one-dimensional) access, a second operator[] for size_t might be provided as well.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • You could improve `operator[]` by making it a template accepting any (correctly sized) range `template ELEM& operator[](T indices) { using std::begin; size_t acc = 0; auto index = [&acc](size_t dim, size_t idx) { return acc = idx + (acc * dim); }; auto discard = [](size_t, size_t j) { return j; }; size_t i = std::inner_product(begin(_dims), end(_dims), begin(indices), 0, discard, index); return _data[i]; }` – Caleth Jan 26 '18 at 11:43
  • @Caleth A nice idea but a little bit hard to read (as comment). You could show/explain it in an own answer. The idea in [super's answer](https://stackoverflow.com/a/48458939/7478597) (allowing "nesting" of `operator[]`) is also nice. Somebody else suggested an `at()` method as variadic template which I liked also. (Too sad, it seems to be deleted now.) As I already wrote: "much space for fantasy..." :-) – Scheff's Cat Jan 26 '18 at 11:53
  • Wow, wasn't expecting all of that. Thanks for lending your brain. – L.C.J Jan 27 '18 at 19:24