Generate multidimensional vector on demand.

Hello people,

Can I generate a vector whose dimensionality is defined in runtime?

That is:
vector = generate_vector(dimensions);

Where dimensions = {2,3,6};

Then vector.size() is 2, vector[].size() is 3, and vector[][].size() is 6.

If it is possible, I just want to know which is the correct language tool
I must use in order to reach such result.

Thanks
yes, you can do this.

the easy way to do it is to allocate a single dimension and define its dimensions yourself with correct math to do the access into say, a 1d vector<int>. You still need a class / wrapper to track the sizes of each dimension and some other minor tasks, so its not totally free, but its still pretty tight.

The hard way is to make a user defined container class that has this functionality, which is not too complicated but its infinitely more complex than the above solution which has literally hundreds of advantages over this approach (including quick reshape (matlab term), linear access to all data if needed, memory/caching advantages, general performance (speed), simplicity of code, exotic math operations (consider matrix multiply which reallocates dimensions, or transposes which flip dimensions, etc) and many more.


Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <random>
#include <vector>
#include <chrono>
#include <algorithm>

constexpr auto low_bound = 2;
constexpr auto up_bound = 5;


int main()
{
    auto seed = std::chrono::system_clock::now().time_since_epoch().count();//seed
    std::default_random_engine dre(seed);//engine
    std::uniform_int_distribution<int> di(low_bound,up_bound);//distribution

    std::vector<int> data(3);
    std::generate(data.begin(), data.end(), [&]{ return di(dre);});

    std::vector<std::vector<std::vector<int>>> my3DVec(data[0], std::vector<std::vector<int>>(data[1], std::vector<int>(data[2])));
    //my3DVec is a [data[0]][data[1]][data[2]] vector


}
and the 3D vector can also be initialized with a particular value like 0 if required:
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
#include <iostream>
#include <random>
#include <vector>
#include <chrono>
#include <algorithm>

constexpr auto low_bound = 2;
constexpr auto up_bound = 5;


int main()
{
    auto seed = std::chrono::system_clock::now().time_since_epoch().count();//seed
    std::default_random_engine dre(seed);//engine
    std::uniform_int_distribution<int> di(low_bound,up_bound);//distribution

    std::vector<int> data(3);
    std::generate(data.begin(), data.end(), [&]{ return di(dre);});
    for (const auto& elem : data)std::cout << elem << " ";
    std::cout << "\n\n";

    std::vector<std::vector<std::vector<int>>> my3DVec(data[0], std::vector<std::vector<int>>(data[1], std::vector<int>(data[2], 0)));
    //my3DVec is a [data[0]][data[1]][data[2]] vector
    for (const auto& elem1 : my3DVec)
    {
        for (const auto& elem2 : elem1)
        {
            for (const auto& elem3 : elem2)
            {
                std::cout << elem3 << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}
/* sample output:
5 3 3

0 0 0
0 0 0
0 0 0

0 0 0
0 0 0
0 0 0

0 0 0
0 0 0
0 0 0

0 0 0
0 0 0
0 0 0

0 0 0
0 0 0
0 0 0
*/

that was not at all what I got from the question, I thought he wanted an N deep vector via user input...

as in the user put in 3, he gets vector<vector<vector
or if put in 5... vector<vector<vector<vector<vector<...

that is, I took "dimensions" to be of indeterminate length...

jonnin - i think your interpretation is right, i don't know why i came to the conclusion it was only a 3D vector of indeterminate size! perhaps i took it off the OP example and ran with it
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
namespace detail
{
    template< typename T, std::size_t NDIMS > struct vector_builder
    {
        using type = std::vector< typename vector_builder<T,NDIMS-1>::type > ;

        static type make( std::vector<std::size_t> dims, const T& v = {} )
        {
            const auto vec = std::vector<T>( dims.empty() ? 0 : dims.back(), v ) ;
            if( !dims.empty() ) dims.pop_back() ;
            return vector_builder< std::vector<T>, NDIMS-1 >::make( dims, vec ) ;
        }
    };

    template< typename T > struct vector_builder<T,1>
    {
        using type = std::vector<T> ;

        static type make( std::vector<std::size_t> dims, const T& v = {} )
        { return type( dims.empty() ? 0 : dims.back(), v ) ; }
    };
}

template< typename T, typename... SIZE_T >
auto make_vector( const T& v, SIZE_T... dims )
{
    static_assert( sizeof...(dims) != 0, "invalid dimension" ) ;
    std::vector<std::size_t> vec_dims{ dims... } ;
    return detail::vector_builder< T, sizeof...(dims) >::make( vec_dims, v ) ;
}

http://coliru.stacked-crooked.com/a/a087641b4c4b7e8e
http://rextester.com/OGTM97105
Thanks people and excuse my delay please,

Dear JLBorges, You ended up with what I really was asking.
Thank you very much!
I dug in the code as much as I could and I modified it to get the following function.
The template now receives a one-dimensional vector as argument -instead of a single value- and
distributes the values of this vector in the new multidimensional vector.
Here is the code:

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
namespace detail
{
	template< typename T, std::size_t NDIMS > struct vector_builder
	{
		using type = std::vector< typename vector_builder<T,NDIMS-1>::type > ;

		static type make( std::vector<std::size_t> dims, std::vector<T>& v = {} )
		{
			if ( !dims.empty() ) {
				std::vector<std::vector<T>> vec ;
				vec.resize(v.size()/dims.back());
				size_t offset = 0;
				for	(auto& s : vec) {
					std::vector<T> aux(v.begin()+offset,v.begin()+offset+v.size()/vec.size());
					s = aux;
					offset += v.size()/vec.size();
				}
				dims.pop_back() ;
				return vector_builder< std::vector<T>, NDIMS-1 >::make( dims, vec ) ;
			}
			else {
				throw std::domain_error(
				"Something is wrong!\n"
				"this shouldn't be happening!\n" ) ;
			}
		}
	};

	template< typename T > struct vector_builder<T,1>
	{
		using type = std::vector<T> ;

		static type make( std::vector<std::size_t>, std::vector<T>& v = {} )
		{
			return type( v.begin(), v.end() ) ;
		}
	};
}

template< typename T, typename... SIZE_T >
auto make_vector( std::vector<T>& v, SIZE_T... dims )
{
	static_assert( sizeof...(dims) != 0, "invalid dimension" ) ;
	std::vector<std::size_t> vec_dims{ dims... } ;
	return detail::vector_builder< T, sizeof...(dims) >::make( vec_dims, v ) ;
}


And a test here: http://coliru.stacked-crooked.com/a/5bfd7a52f013b28f
Any suggestions and/or corrections are more than welcomed.

One more request:

Can you give me the right syntax in order to make "make_vector" to receive dims as a std::vector<size_t> instead of individually?

Thanks a lot!
If there are too few initialisers, for example:
1
2
3
4
5
6
7
8
9
10
int main()
{
    std::size_t i = 2, j = 3, k = 4 ;
    // std::cin >> i >. j >> k ;

    std::vector<int>    v = {1,2,3,4,5}; // *** too few initialisers (six are required)

    auto vec2 = make_vector( v, i, j ); //, k ) ;
    // ...
}

the code, as it is would engender undefined behaviour.
http://coliru.stacked-crooked.com/a/84084805283e1f12

There would also be undefined behaviour if one of the dimensions is zero
vec.resize(v.size()/dims.back()) ; // *** dims.back() == 0

A minor refinement would be to pass the vector containing the initialisers by value
(and then move lvalues into the function).

I would favour factoring the code into two separate functions:
1. make_vector, which creates the vector with the appropriate dimensions (default initialised)
2. fill_vector, which fills the innermost vectors with the initialisers

And then, a wrapper make_filled_vector which chains the calls to make_vector and fill_vector



> Can you give me the right syntax in order to make "make_vector" to receive dims as a
> std::vector<size_t> instead of individually?

We are programming in a statically typed language; the type of the result of make_vector must be known at compile time (and that depends on the number of dimensions). The size of a std::vector<size_t> is technically known only at run-time.

There is no elegant way to do this; one would have to resort to the equivalent of switching on the number of dimensions, and wrapping the result in a std::any or std::experimental::any
Last edited on
I would favour factoring the code into two separate functions:
1. make_vector, which creates the vector with the appropriate dimensions (default initialised)
2. fill_vector, which fills the innermost vectors with the initialisers


Once again you are right JLBorges.
The code is much cleaner in this way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
void	fill_vector( T& s, std::vector<T>& vec )
{
	T aux = vec.front();
	s = aux;
	std::vector<T> auxVec(vec.begin()+1,vec.end());
	vec = auxVec;
}


template<typename T, typename V>
void	fill_vector( std::vector<T>& v, std::vector<V>& vec )
{
	for (auto& s : v)
		fill_vector(s, vec);
}


http://coliru.stacked-crooked.com/a/a07865b4df391c20

Still remain working on the suggestions you gave me.

The template that will use these two templates has to receive the one-dimensional vector by value.

Thank you very much
Topic archived. No new replies allowed.