Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access a 1D array as a 2D array in C++

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?

like image 354
dinkelk Avatar asked Jul 02 '13 02:07

dinkelk


4 Answers

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];
}
like image 197
Amadeus Avatar answered Oct 18 '22 05:10

Amadeus


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;
    }
}
like image 23
Captain Obvlious Avatar answered Oct 18 '22 07:10

Captain Obvlious


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';
like image 1
scaryrawr Avatar answered Oct 18 '22 07:10

scaryrawr


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)

like image 1
Alexander Mihailov Avatar answered Oct 18 '22 07:10

Alexander Mihailov