How to Use a Std::Valarray to Store/Manipulate a Contiguous 2D Array

How can I use a std::valarray to store/manipulate a contiguous 2D array?

Off the top of my head:

template <class element_type>
class matrix
{
public:
matrix(size_t width, size_t height): m_stride(width), m_height(height), m_storage(width*height) { }

element_type &operator()(size_t row, size_t column)
{
// column major
return m_storage[std::slice(column, m_height, m_stride)][row];

// row major
return m_storage[std::slice(row, m_stride, m_height)][column];
}

private:
std::valarray<element_type> m_storage;
size_t m_stride;
size_t m_height;
};

std::valarray provides many interesting ways to access elements, via slices, masks, multidimentional slices, or an indirection table. See std::slice_array, std::gslice_array, std::mask_array, and std::indirect_array for more details.

Accessing column slices of 2D valarrays

First off, you don't have a 2D valarray. You have a valarray of valarrays, a difference you should not ignore.

x = g[m][n];

only looks like an array-style access. It's really closer to

temp = g[m];
x = temp[n];

A valarray's datastore is a nice contiguous block of memory, but if you have an M by N structure, you have M+1 valarrays potentially scattered throughout memory. This can turn into a nightmare of performance-killing cache misses.

You are going to have to decide which is more important to be fast, row slicing or column slicing, because only one will be going with the flow of memory and the other require a cache-thrashing copy against the grain.

Currently

g[1][std::slice(0,10,1)];

works because it is slicing a contiguous block of memory, and

g[std::slice(0,1,3)][0]

fails because it must reach across M distinct valarrays to gather the slice and std::slice can't do that. You will have to manually copy the elements you want from each of the valarrays that make up the column. Sucks, huh?

So what do you do?

You fake it! Muhuhahahahahahahaha!

Don't make a valarray of valarrays. Make one big valarray of size MxN. So say goodbye to

std::valarray<std::valarray<int> > g(std::valarray<int>(10),4);

and hello to

std::valarray<int>(10*4);

Now you can take advantage of std::slice's stride parameter to grab every tenth element

std::slice(column_to_slice,4,10);

And as an added bonus you now have one contiguous block of memory so at least some of that cache-grinding abuse should be mitigated. You're still smurfed if the stride is too large.

I whole-heartedly recommend wrapping this in an object to make access and management easier. Something like this, except you use the valarray instead of the raw pointer.

dynamic allocating memory for 3d array that uses minimum memory and in contiguous form in c++?

You can write a wrapper around a std::vector and overload operator() to access matrix elements. The elements are stored contiguously in the 1D std::vector and operator() converts 3D indices into a 1D index into the std::vector. If the matrix was 2D, this is how the mapping from 2D to 1D would look like:

| 1 2 3 |
| 4 5 6 | ---> [1 2 3 4 5 6 7 8 9]
| 7 8 9 |

This ordering is called row major.

Here's an example of a class that overloads operator() to convert 3D indices into a row-major 1D index:

#include <iostream>
#include <vector>

template <class T>
class Matrix3D
{
public:
Matrix3D(size_t m, size_t n, size_t o)
: m_(m), n_(n), o_(o), data_(m*n*o) {}

T& operator()(size_t i, size_t j, size_t k)
{
return data_[(i * n_ * o_) + (j * o_) + k];
}

const T& operator()(size_t i, size_t j, size_t k) const
{
return data_[(i * n_ * o_) + (j * o_) + k];
}

private:
std::vector<T> data_;
size_t m_, n_, o_;
};

int main()
{
Matrix3D<float> m(4, 3, 2);
m(0,0,0) = 12.3f;
m(3,2,1) = 45.6f;
std::cout << m(0,0,0) << " " << m(3,2,1) << "\n";
}

The Boost.MultiArray library does essentially the same thing as this (and much more), but can be used for any dimension N.

Pointer of a 2D array in C++?

Are the dimensions of the matrix known at compile time? If so:

template <size_t n>
void rotateMatrix(int (&matrix)[n][n])
{
...
}

How to initialize 3D array in C++

The array in your question has only one element, so you only need one value to completely initialise it. You need three sets of braces, one for each dimension of the array.

int min[1][1][1] = {{{100}}};

A clearer example might be:

int arr[2][3][4] = { { {1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4} },
{ {1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4} } };

As you can see, there are two groups, each containing three groups of 4 numbers.



Related Topics



Leave a reply



Submit