This has bothered me for a while. A lot of times I find myself making a large buffer to hold a "maximum" amount of data. This helps me avoid dynamically allocating and deallocating a buffer each time the size of the next data set changes.
For instance say I have an array that is way too large for its actual useful size, but I know the length of the useful data.
int amountOfData = 9;
char data1D[100] = some data that is only 9 bytes long stored in a 100 byte array
Lets say I have an algorithm I want to run on this data set that uses 2D array indexing. So I want to be able to access the data as follows:
cout << "I am accessing this data as a 2D array: " << data1D[0][1] << endl;
Lets say for this algorithm I know that the xlength and ylength of the 2D array are going to be:
int xlength = 3;
int ylength = 3;
for this iteration, because amountOfData = 9
. However, the lengths may be different for the next iteration. Ie. they could be xlength = 4
and ylength = 4
given amountOfData = 16
.
I want to do some kind of casting that allows me cast the 1D array using 2D array indexing. I know how long my initial 1D length is, which tells me how long my 2D xlength
and ylength
are, so this should be easy to do without using new
or malloc
as long as the initial 100 bytes is long enough to hold any useful data set to me.
I realize that:
char** data2d = (char**) data1D;
will not work because the compiler doesn't know the size of the second dimension. But I will know what it is at runtime!
What is the underlying reason for this being the way it is? Are there any workarounds? Am I missing anything?
Once you only know the length of your array at runtime, I guess it is better to solve this problem not using an 2D array, but emulating it by using functions. For example, in C:
char data1D[1000] = {0};
unsigned int getElement(unsigned int x, unsigned int y,
unsigned int xMax, unsigned int yMax)
{
// Do some error tests
return ((unsigned int *) data1D)[x*xMax + y];
}
The reason the cast does not work is you are essentially trying to convert a 2 dimensional array to a pointer to an array of pointers that each point to an array of characters.
On option is to create a couple of adapter classes that allow you to access the data as if it were an actual two dimensional array. This will simplify access to both extents of the array and can be extended for usage with the standard library.
#include <iostream>
#include <sstream>
#include <utility>
template <typename Type, size_t DataSize>
class MDArray
{
public:
struct SDArray
{
SDArray(Type* data, size_t size) : data_(data), size_(size) {}
SDArray(const SDArray& o) : data_(o.data), size_(o.size_) {}
size_t size() const { return size_; };
Type& operator[](size_t index)
{
if(index >= size_)
throw std::out_of_range("Index out of range");
return data_[index];
}
Type operator[](size_t index) const
{
if(index >= size_)
throw std::out_of_range("Index out of range");
return data_[index];
}
private:
SDArray& operator=(const SDArray&);
Type* const data_;
const size_t size_;
};
MDArray(const Type *data, size_t size, size_t dimX, size_t dimY)
: dimX_(dimX), dimY_(dimY)
{
if(dimX * dimY > DataSize)
throw std::invalid_argument("array dimensions greater than data size");
if(dimX * dimY != size)
throw std::invalid_argument("data size mismatch");
initdata(data, size);
}
size_t size() const { return dimX_; };
size_t sizeX() const { return dimX_; };
size_t sizeY() const { return dimY_; };
SDArray operator[](const size_t &index)
{
if(index >= dimY_)
throw std::out_of_range("Index out of range");
return SDArray(data_ + (dimY_ * index), dimX_);
}
const SDArray operator[](const size_t &index) const
{
if(index >= dimY_)
throw std::out_of_range("Index out of range");
return SDArray(data_ + (dimY_ * index), dimX_);
}
private:
void initdata(const Type* data, size_t size)
{
std::copy(data, data + size, data_);
}
MDArray(const MDArray&);
MDArray operator=(const MDArray&);
Type data_[DataSize];
const size_t dimX_;
const size_t dimY_;
};
int main()
{
char data[] = "123456789";
MDArray<char, 100> md(data, 9, 3, 3);
for(size_t y = 0; y < md.sizeY(); y++)
{
for(size_t x = 0; x < md.sizeX(); x++)
{
std::cout << " " << md[y][x];
}
std::cout << std::endl;
}
std::cout << "-------" << std::endl;
for(size_t y = 0; y < md.size(); y++)
{
const auto& sd = md[y];
for(size_t x = 0; x < sd.size(); x++)
{
std::cout << " " << sd[x];
}
std::cout << std::endl;
}
std::cout << "-------" << std::endl;
for(size_t y = 0; y < md.size(); y++)
{
auto sd = md[y];
for(size_t x = 0; x < sd.size(); x++)
{
std::cout << " " << sd[x];
}
std::cout << std::endl;
}
}
If you know your row/column length (depending on row or column major and what not)... I believe it's something like...
char get_value(char *arr, int row_len, int x, int y) {
return arr[x * row_len + y];
}
... for treating a 1D array as 2D.
Another thing for 2D dynamic C arrays.
char **arr = (char **)malloc(row_size * sizeof(char *));
int x;
for (x = 0; x < row_size; ++x) {
arr[x] = (char *)malloc(col_size * sizeof(char));
}
I could have my columns and rows mixed though...
Like everyone else has said, vectors are nice since you're using C++:
auto matrix_like_thing = std::vector<std::vector<char> >(rows, std::vector<char>(cols, '\0'));
matrix_like_thing[0][4] = 't';
If you are using C++, you can build a simple wrapper to simplify an access, for example:
template <typename T>
class A2D {
T *m_buf;
size_t m_n;
size_t m_m;
public:
A2D(T *buf, const size_t &n, const size_t &m)
: m_buf(buf), m_n(n), m_m(m) { }
~A2D() { }
T& operator()(const size_t &i, const size_t &j)
{
return *(this->m_buf + i * this->m_m + j);
}
};
Usage:
int main()
{
int *a = new int[16];
for ( int i = 0; i < 16; ++i ) {
a[i] = i;
}
A2D<int> b(a, 4, 4);
for ( int i = 0; i < 4; ++i ) {
for ( int j = 0; j < 4; ++j ) {
std::cout << b(i, j) << ' ';
}
std::cout << '\n';
}
}
With C you can do the similar things with procedures or macros. Importantly, do not forget to control preallocated memory (1D array)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With