8

Say I've got an N-dimensional boost::multi_array (of type int for simplicity), where N is known at compile time but can vary (i.e. is a non-type template parameter). Let's assume that all dimensions have equal size m.

typedef boost::multi_array<int, N> tDataArray;
boost::array<tDataArray::index, N> shape;
shape.fill(m);
tDataArray A(shape);

Now I would like to loop over all entries in A, e.g. to print them. If N was 2 for example I think I would write something like this

  boost::array<tDataArray::index, 2> index;
  for ( int i = 0; i < m; i++ )
  {
    for ( int j = 0; j < m; j++ )
    {
      index = {{ i, j }};
      cout << A ( index ) << endl;
    }
  }

I've used an index object to access the elements as I think this is more flexible than the []-operator here.

But how could I write this without knowing the number of dimensions N. Is there any built-in way? The documentation of multi_array is not very clear on which types of iterators exist, etc. Or would I have to resort to some custom method with custom pointers, computing indices from the pointers, etc.? If so - any suggestions how such an algorithm could look like?

janitor048
  • 2,045
  • 9
  • 23
  • 29
  • 1
    Does this help? http://stackoverflow.com/questions/5572464/how-to-traverse-a-boostmulti-array – Sam DeHaan Mar 20 '12 at 15:21
  • 1
    @SamDeHaan Sort of - not all the way through, though. I just now realized that this example (which I've seen before) uses a custom version of `for_each` that is declared in another file of the multi_array examples. I did try std::for_each and boosts FOR_EACH and could not get it to work - I will look into this custom solution now. But I would still need a way to obtain the actual indices (possibly as an array of tDataArray::index) while looping over the array. This is because I need to some more than just printing.. – janitor048 Mar 20 '12 at 15:50
  • BTW: http://stackoverflow.com/questions/6434678/iterating-over-the-dimensions-of-a-boostmulti-array is also related. But even though the question is marked as resolved, it really isn't (as far as I can see it). – janitor048 Mar 20 '12 at 15:53
  • This http://groups.google.com/group/boost-list/browse_thread/thread/79ac50666a53483a# (2nd answer) looks interesting. Though essentially this would be back to "multi-dimensional arrays" via index arithmetics.. I'll try to figure out whether I can apply some of these suggestions to the problem at hand. – janitor048 Mar 20 '12 at 16:47

4 Answers4

5

Ok, based on the Google groups discussion already mentioned in one of the comments and on one of the examples from the library itself, here is a possible solution that lets you iterate over all values in the multi-array in a single loop and offers a way to retrieve the index for each of these elements (in case this is needed for some other stuff, as in my scenario).

#include <iostream>
#include <boost/multi_array.hpp>
#include <boost/array.hpp>

const unsigned short int DIM = 3;
typedef double tValue;
typedef boost::multi_array<tValue,DIM> tArray;
typedef tArray::index tIndex;
typedef boost::array<tIndex, DIM> tIndexArray;

tIndex getIndex(const tArray& m, const tValue* requestedElement, const unsigned short int direction)
{
  int offset = requestedElement - m.origin();
  return(offset / m.strides()[direction] % m.shape()[direction] +  m.index_bases()[direction]); 
}

tIndexArray getIndexArray( const tArray& m, const tValue* requestedElement )
{
  tIndexArray _index;
  for ( unsigned int dir = 0; dir < DIM; dir++ )
  {
    _index[dir] = getIndex( m, requestedElement, dir );
  }

  return _index;
}


int main()
{ 
  double* exampleData = new double[24];
  for ( int i = 0; i < 24; i++ ) { exampleData[i] = i; }

  tArray A( boost::extents[2][3][4] );
  A.assign(exampleData,exampleData+24);

  tValue* p = A.data();
  tIndexArray index;
  for ( int i = 0; i < A.num_elements(); i++ )
  {
    index = getIndexArray( A, p );
    std::cout << index[0] << " " << index[1] << " " << index[2] << " value = " << A(index) << "  check = " << *p << std::endl;
    ++p;
  }

  return 0;
}

The output should be

0 0 0 value = 0 check = 0
0 0 1 value = 1 check = 1
0 0 2 value = 2 check = 2
0 0 3 value = 3 check = 3
0 1 0 value = 4 check = 4
0 1 1 value = 5 check = 5
0 1 2 value = 6 check = 6
0 1 3 value = 7 check = 7
0 2 0 value = 8 check = 8
0 2 1 value = 9 check = 9
0 2 2 value = 10 check = 10
0 2 3 value = 11 check = 11
1 0 0 value = 12 check = 12
1 0 1 value = 13 check = 13
1 0 2 value = 14 check = 14
1 0 3 value = 15 check = 15
1 1 0 value = 16 check = 16
1 1 1 value = 17 check = 17
1 1 2 value = 18 check = 18
1 1 3 value = 19 check = 19
1 2 0 value = 20 check = 20
1 2 1 value = 21 check = 21
1 2 2 value = 22 check = 22
1 2 3 value = 23 check = 23

so the memory layout goes from the outer to the inner indices. Note that the getIndex function relies on the default memory layout provided by boost::multi_array. In case the array base or the storage ordering are changed, this would have to be adjusted.

janitor048
  • 2,045
  • 9
  • 23
  • 29
  • This is great - it was very unclear how to do this from the documentation. – David Doria Sep 17 '12 at 17:25
  • Actually I don't understand why the loop counter `i` does not appear in the body of the `num_elements()` loop? – David Doria Feb 24 '16 at 18:18
  • @DavidDoria He's incrementing `p`, which is just a raw pointer into the array and then reconstructing the indices based on that pointer. The loop counter is just to run the loop the correct number of times. – SirGuy May 10 '16 at 14:58
3

There is a lack of simple boost multi array examples. So here is a very simple example of how to fill a boost multi array using indexes and how to read all the entries using a single pointer.

typedef boost::multi_array<double, 2> array_type;
typedef array_type::index index;

array_type A(boost::extents[3][2]);

//  ------> x 
// | 0 2 4
// | 1 3 5
// v
// y
double value = 0;
for(index x = 0; x < 3; ++x) 
    for(index y = 0; y < 2; ++y)
        A[x][y] = value++;

double* it  = A.origin();
double* end = A.origin() + A.num_elements();
for(; it != end; ++it){
    std::cout << *it << " ";
}

// -> 0 1 2 3 4 5
Stefan
  • 31
  • 1
2

If you don't need the index, you can simply do:

  for (unsigned int i = 0; i < A.num_elements(); i++ )
  {
    tValue item = A.data()[i];
    std::cout << item << std::endl;
  }
David Doria
  • 9,873
  • 17
  • 85
  • 147
0

Based on the answers before I produced this nice overloaded version of the insertion operator for boost::multi_arrays

using namespace std;
using namespace boost::detail::multi_array;

template <typename T , unsigned long K>
ostream &operator<<( ostream &os , const boost::multi_array<T , K> &A )
{
 const T* p = A.data();
 for( boost::multi_array_types::size_type i = A.num_elements() ; i-- ; ++p )
 {
  os << "[ ";
  for( boost::multi_array_types::size_type k = 0 ; k < K ; ) {
   os << ( p - A.origin()  ) / A.strides()[ k ] % A.shape()[ k ]
         +  A.index_bases()[ k ];
   if( ++k < K )
    os << ", ";
    }
  os << " ] = " << *p << endl;
  }

 return os;
 }

It's just a streamlined version of answer 1, except it should work with any type T that has a working operator<<. I tested like

 typedef boost::multi_array<double, 3> array_type;
 typedef array_type::index index;

 index x = 3;
 index y = 2;
 index z = 3;

 array_type A( boost::extents[ x ][ y ][ z ] );

 // Assign values to the elements
 int values = 0;
 for( index i = 0 ; i < x ; ++i ) 
  for( index j = 0 ; j < y ; ++j )
   for( index k = 0 ; k < z ; ++k )
    A[ i ][ j ][ k ] = values++;

 // print the results
 cout << A << endl;

and it seems to work:

[ 0, 0, 0 ] = 0
[ 0, 0, 1 ] = 1
[ 0, 0, 2 ] = 2
[ 0, 1, 0 ] = 3
[ 0, 1, 1 ] = 4
[ 0, 1, 2 ] = 5
[ 1, 0, 0 ] = 6
[ 1, 0, 1 ] = 7
[ 1, 0, 2 ] = 8
[ 1, 1, 0 ] = 9
[ 1, 1, 1 ] = 10
[ 1, 1, 2 ] = 11
[ 2, 0, 0 ] = 12
[ 2, 0, 1 ] = 13
[ 2, 0, 2 ] = 14
[ 2, 1, 0 ] = 15
[ 2, 1, 1 ] = 16
[ 2, 1, 2 ] = 17

Hope this is useful to somebody, and thanks a lot for the original answers: it was very useful to me.

  • This answer is good in that you're using the template's value, K, to deduce the number of dimensions in the function. This is not a very powerful way for the function to be able to do iteration work, because the function is unable to investigate the index, however not all functions would need that. So I'd upvote your answer if you made it more concise. When writing SO questions/answers, try not to become too "realistic" in the example - they become unnecessarily complicated. Simplest is best. – Elliott Jun 07 '20 at 18:49